• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Microdroid Manager
16 
17 mod instance;
18 mod ioutil;
19 mod payload;
20 
21 use crate::instance::{ApkData, InstanceDisk, MicrodroidData, RootHash};
22 use android_hardware_security_dice::aidl::android::hardware::security::dice::{
23     Config::Config, InputValues::InputValues, Mode::Mode,
24 };
25 use android_security_dice::aidl::android::security::dice::IDiceMaintenance::IDiceMaintenance;
26 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
27 use apkverify::{get_public_key_der, verify};
28 use binder::unstable_api::{new_spibinder, AIBinder};
29 use binder::{wait_for_interface, FromIBinder, Strong};
30 use diced_utils::cbor::encode_header;
31 use glob::glob;
32 use idsig::V4Signature;
33 use itertools::sorted;
34 use log::{error, info};
35 use microdroid_metadata::{write_metadata, Metadata};
36 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
37 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
38 use rand::Fill;
39 use ring::digest;
40 use rustutils::system_properties;
41 use rustutils::system_properties::PropertyWatcher;
42 use std::convert::TryInto;
43 use std::fs::{self, create_dir, File, OpenOptions};
44 use std::os::unix::io::{FromRawFd, IntoRawFd};
45 use std::path::Path;
46 use std::process::{Child, Command, Stdio};
47 use std::str;
48 use std::time::{Duration, SystemTime};
49 use vsock::VsockStream;
50 
51 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
52     ERROR_PAYLOAD_CHANGED, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_PAYLOAD_INVALID_CONFIG, ERROR_UNKNOWN, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
53 };
54 
55 const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
56 const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
57 const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
58 const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
59 const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
60 const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
61 const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
62 const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
63 const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
64 const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
65 const AVF_NEW_INSTANCE: &str = "/sys/firmware/devicetree/base/chosen/avf,new-instance";
66 
67 /// The CID representing the host VM
68 const VMADDR_CID_HOST: u32 = 2;
69 
70 const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
71 const LOGD_ENABLED_PROP: &str = "ro.boot.logd.enabled";
72 const APP_DEBUGGABLE_PROP: &str = "ro.boot.microdroid.app_debuggable";
73 
74 #[derive(thiserror::Error, Debug)]
75 enum MicrodroidError {
76     #[error("Payload has changed: {0}")]
77     PayloadChanged(String),
78     #[error("Payload verification has failed: {0}")]
79     PayloadVerificationFailed(String),
80     #[error("Payload config is invalid: {0}")]
81     InvalidConfig(String),
82 }
83 
translate_error(err: &Error) -> (i32, String)84 fn translate_error(err: &Error) -> (i32, String) {
85     if let Some(e) = err.downcast_ref::<MicrodroidError>() {
86         match e {
87             MicrodroidError::PayloadChanged(msg) => (ERROR_PAYLOAD_CHANGED, msg.to_string()),
88             MicrodroidError::PayloadVerificationFailed(msg) => {
89                 (ERROR_PAYLOAD_VERIFICATION_FAILED, msg.to_string())
90             }
91             MicrodroidError::InvalidConfig(msg) => (ERROR_PAYLOAD_INVALID_CONFIG, msg.to_string()),
92         }
93     } else {
94         (ERROR_UNKNOWN, err.to_string())
95     }
96 }
97 
get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>>98 fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
99     // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
100     // safely taken by new_spibinder.
101     let ibinder = unsafe {
102         new_spibinder(binder_rpc_unstable_bindgen::RpcClient(
103             VMADDR_CID_HOST,
104             VM_BINDER_SERVICE_PORT as u32,
105         ) as *mut AIBinder)
106     };
107     if let Some(ibinder) = ibinder {
108         <dyn IVirtualMachineService>::try_from(ibinder).context("Cannot connect to RPC service")
109     } else {
110         bail!("Invalid raw AIBinder")
111     }
112 }
113 
main()114 fn main() {
115     if let Err(e) = try_main() {
116         error!("Failed with {:?}. Shutting down...", e);
117         if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
118             error!("failed to shutdown {:?}", e);
119         }
120         std::process::exit(1);
121     }
122 }
123 
try_main() -> Result<()>124 fn try_main() -> Result<()> {
125     let _ = kernlog::init();
126     info!("started.");
127 
128     let service = get_vms_rpc_binder().context("cannot connect to VirtualMachineService")?;
129     match try_run_payload(&service) {
130         Ok(code) => {
131             info!("notifying payload finished");
132             service.notifyPayloadFinished(code)?;
133             if code == 0 {
134                 info!("task successfully finished");
135             } else {
136                 error!("task exited with exit code: {}", code);
137             }
138             Ok(())
139         }
140         Err(err) => {
141             error!("task terminated: {:?}", err);
142             let (error_code, message) = translate_error(&err);
143             service.notifyError(error_code, &message)?;
144             Err(err)
145         }
146     }
147 }
148 
dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()>149 fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
150     // Calculate compound digests of code and authorities
151     let mut code_hash_ctx = digest::Context::new(&digest::SHA512);
152     let mut authority_hash_ctx = digest::Context::new(&digest::SHA512);
153     code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
154     authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
155     for extra_apk in &verified_data.extra_apks_data {
156         code_hash_ctx.update(extra_apk.root_hash.as_ref());
157         authority_hash_ctx.update(extra_apk.pubkey.as_ref());
158     }
159     for apex in &verified_data.apex_data {
160         code_hash_ctx.update(apex.root_digest.as_ref());
161         authority_hash_ctx.update(apex.public_key.as_ref());
162     }
163     let code_hash = code_hash_ctx.finish().as_ref().try_into().unwrap();
164     let authority_hash = authority_hash_ctx.finish().as_ref().try_into().unwrap();
165 
166     // {
167     //   -70002: "Microdroid payload",
168     //   -71000: payload_config_path
169     // }
170     let mut config_desc = vec![
171         0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72, 0x6f,
172         0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01, 0x15, 0x57,
173     ];
174     let config_path_bytes = payload_config_path.as_bytes();
175     encode_header(3, config_path_bytes.len().try_into().unwrap(), &mut config_desc)?;
176     config_desc.extend_from_slice(config_path_bytes);
177 
178     // Check app debuggability, conervatively assuming it is debuggable
179     let app_debuggable = system_properties::read_bool(APP_DEBUGGABLE_PROP, true)?;
180 
181     // Send the details to diced
182     let diced =
183         wait_for_interface::<dyn IDiceMaintenance>("android.security.dice.IDiceMaintenance")
184             .context("IDiceMaintenance service not found")?;
185     diced
186         .demoteSelf(&[InputValues {
187             codeHash: code_hash,
188             config: Config { desc: config_desc },
189             authorityHash: authority_hash,
190             authorityDescriptor: None,
191             mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
192             hidden: verified_data.salt.clone().try_into().unwrap(),
193         }])
194         .context("IDiceMaintenance::demoteSelf failed")?;
195     Ok(())
196 }
197 
is_strict_boot() -> bool198 fn is_strict_boot() -> bool {
199     Path::new(AVF_STRICT_BOOT).exists()
200 }
201 
is_new_instance() -> bool202 fn is_new_instance() -> bool {
203     Path::new(AVF_NEW_INSTANCE).exists()
204 }
205 
try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32>206 fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
207     let metadata = load_metadata().context("Failed to load payload metadata")?;
208 
209     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
210     let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
211 
212     if is_strict_boot() {
213         // Provisioning must happen on the first boot and never again.
214         if is_new_instance() {
215             ensure!(
216                 saved_data.is_none(),
217                 MicrodroidError::InvalidConfig("Found instance data on first boot.".to_string())
218             );
219         } else {
220             ensure!(
221                 saved_data.is_some(),
222                 MicrodroidError::InvalidConfig("Instance data not found.".to_string())
223             );
224         };
225     }
226 
227     // Verify the payload before using it.
228     let verified_data =
229         verify_payload(&metadata, saved_data.as_ref()).context("Payload verification failed")?;
230     if let Some(saved_data) = saved_data {
231         ensure!(
232             saved_data == verified_data,
233             MicrodroidError::PayloadChanged(String::from(
234                 "Detected an update of the payload which isn't supported yet."
235             ))
236         );
237         info!("Saved data is verified.");
238     } else {
239         info!("Saving verified data.");
240         instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
241     }
242 
243     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
244     info!("DICE derivation for payload");
245     dice_derivation(&verified_data, &metadata.payload_config_path)?;
246 
247     // Before reading a file from the APK, start zipfuse
248     run_zipfuse(
249         "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
250         Path::new("/dev/block/mapper/microdroid-apk"),
251         Path::new("/mnt/apk"),
252     )
253     .context("Failed to run zipfuse")?;
254 
255     ensure!(
256         !metadata.payload_config_path.is_empty(),
257         MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
258     );
259 
260     let config = load_config(Path::new(&metadata.payload_config_path))?;
261 
262     // Start tombstone_transmit if enabled
263     if config.export_tombstones {
264         system_properties::write("ctl.start", "tombstone_transmit")
265             .context("Failed to start tombstone_transmit")?;
266     }
267 
268     if config.extra_apks.len() != verified_data.extra_apks_data.len() {
269         return Err(anyhow!(
270             "config expects {} extra apks, but found only {}",
271             config.extra_apks.len(),
272             verified_data.extra_apks_data.len()
273         ));
274     }
275     mount_extra_apks(&config)?;
276 
277     // Wait until apex config is done. (e.g. linker configuration for apexes)
278     // TODO(jooyung): wait until sys.boot_completed?
279     wait_for_apex_config_done()?;
280 
281     ensure!(
282         config.task.is_some(),
283         MicrodroidError::InvalidConfig("No task in VM config".to_string())
284     );
285     exec_task(&config.task.unwrap(), service)
286 }
287 
288 struct ApkDmverityArgument<'a> {
289     apk: &'a str,
290     idsig: &'a str,
291     name: &'a str,
292     saved_root_hash: Option<&'a RootHash>,
293 }
294 
run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child>295 fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
296     let mut cmd = Command::new(APKDMVERITY_BIN);
297 
298     cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
299 
300     for argument in args {
301         cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
302         if let Some(root_hash) = argument.saved_root_hash {
303             cmd.arg(&to_hex_string(root_hash));
304         } else {
305             cmd.arg("none");
306         }
307     }
308 
309     cmd.spawn().context("Spawn apkdmverity")
310 }
311 
run_zipfuse(option: &str, zip_path: &Path, mount_dir: &Path) -> Result<Child>312 fn run_zipfuse(option: &str, zip_path: &Path, mount_dir: &Path) -> Result<Child> {
313     Command::new(ZIPFUSE_BIN)
314         .arg("-o")
315         .arg(option)
316         .arg(zip_path)
317         .arg(mount_dir)
318         .stdin(Stdio::null())
319         .stdout(Stdio::null())
320         .stderr(Stdio::null())
321         .spawn()
322         .context("Spawn zipfuse")
323 }
324 
325 // Verify payload before executing it. For APK payload, Full verification (which is slow) is done
326 // when the root_hash values from the idsig file and the instance disk are different. This function
327 // returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
328 // saved to the instance disk.
verify_payload( metadata: &Metadata, saved_data: Option<&MicrodroidData>, ) -> Result<MicrodroidData>329 fn verify_payload(
330     metadata: &Metadata,
331     saved_data: Option<&MicrodroidData>,
332 ) -> Result<MicrodroidData> {
333     let start_time = SystemTime::now();
334 
335     // Verify main APK
336     let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
337     let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
338     let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
339 
340     // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
341     // instead of the value read from the idsig file.
342     let main_apk_argument = {
343         ApkDmverityArgument {
344             apk: MAIN_APK_PATH,
345             idsig: MAIN_APK_IDSIG_PATH,
346             name: MAIN_APK_DEVICE_NAME,
347             saved_root_hash: if root_hash_trustful {
348                 Some(root_hash_from_idsig.as_ref())
349             } else {
350                 None
351             },
352         }
353     };
354     let mut apkdmverity_arguments = vec![main_apk_argument];
355 
356     // Verify extra APKs
357     // For now, we can't read the payload config, so glob APKs and idsigs.
358     // Later, we'll see if it matches with the payload config.
359 
360     // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
361     // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
362     let extra_apks =
363         sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
364     let extra_idsigs =
365         sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
366     if extra_apks.len() != extra_idsigs.len() {
367         return Err(anyhow!(
368             "Extra apks/idsigs mismatch: {} apks but {} idsigs",
369             extra_apks.len(),
370             extra_idsigs.len()
371         ));
372     }
373     let extra_apks_count = extra_apks.len();
374 
375     let (extra_apk_names, extra_root_hashes_from_idsig): (Vec<_>, Vec<_>) = extra_idsigs
376         .iter()
377         .enumerate()
378         .map(|(i, extra_idsig)| {
379             (
380                 format!("extra-apk-{}", i),
381                 get_apk_root_hash_from_idsig(extra_idsig.to_str().unwrap())
382                     .expect("Can't find root hash from extra idsig"),
383             )
384         })
385         .unzip();
386 
387     let saved_extra_root_hashes: Vec<_> = saved_data
388         .map(|d| d.extra_apks_data.iter().map(|apk_data| &apk_data.root_hash).collect())
389         .unwrap_or_else(Vec::new);
390     let extra_root_hashes_trustful: Vec<_> = extra_root_hashes_from_idsig
391         .iter()
392         .enumerate()
393         .map(|(i, root_hash_from_idsig)| {
394             saved_extra_root_hashes.get(i).copied() == Some(root_hash_from_idsig)
395         })
396         .collect();
397 
398     for i in 0..extra_apks_count {
399         apkdmverity_arguments.push({
400             ApkDmverityArgument {
401                 apk: extra_apks[i].to_str().unwrap(),
402                 idsig: extra_idsigs[i].to_str().unwrap(),
403                 name: &extra_apk_names[i],
404                 saved_root_hash: if extra_root_hashes_trustful[i] {
405                     Some(&extra_root_hashes_from_idsig[i])
406                 } else {
407                     None
408                 },
409             }
410         });
411     }
412 
413     // Start apkdmverity and wait for the dm-verify block
414     let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
415 
416     // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
417     // APEX payload.
418     let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
419     if let Some(saved_data) = saved_data.map(|d| &d.apex_data) {
420         // We don't support APEX updates. (assuming that update will change root digest)
421         ensure!(
422             saved_data == &apex_data_from_payload,
423             MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
424         );
425         let apex_metadata = to_metadata(&apex_data_from_payload);
426         // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
427         // metadata instead of the default one (/dev/block/by-name/payload-metadata)
428         OpenOptions::new()
429             .create_new(true)
430             .write(true)
431             .open("/apex/vm-payload-metadata")
432             .context("Failed to open /apex/vm-payload-metadata")
433             .and_then(|f| write_metadata(&apex_metadata, f))?;
434     }
435     // Start apexd to activate APEXes
436     system_properties::write("ctl.start", "apexd-vm")?;
437 
438     // TODO(inseob): add timeout
439     apkdmverity_child.wait()?;
440 
441     // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
442     // the APK file and therefore can be very slow if the APK is large. Note that this step is
443     // taken only when the root_hash is un-trustful which can be either when this is the first boot
444     // of the VM or APK was updated in the host.
445     // TODO(jooyung): consider multithreading to make this faster
446     let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
447     let extra_apks_data = extra_root_hashes_from_idsig
448         .into_iter()
449         .enumerate()
450         .map(|(i, extra_root_hash)| {
451             let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
452             let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
453             Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
454         })
455         .collect::<Result<Vec<_>>>()?;
456 
457     info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
458 
459     // Use the salt from a verified instance, or generate a salt for a new instance.
460     let salt = if let Some(saved_data) = saved_data {
461         saved_data.salt.clone()
462     } else {
463         let mut salt = vec![0u8; 64];
464         salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
465         salt
466     };
467 
468     // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
469     // fully verifying the APK or by comparing it with the saved root_hash.
470     Ok(MicrodroidData {
471         salt,
472         apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
473         extra_apks_data,
474         apex_data: apex_data_from_payload,
475     })
476 }
477 
mount_extra_apks(config: &VmPayloadConfig) -> Result<()>478 fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
479     // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
480     for i in 0..config.extra_apks.len() {
481         let mount_dir = format!("/mnt/extra-apk/{}", i);
482         create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
483 
484         // don't wait, just detach
485         run_zipfuse(
486             "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
487             Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
488             Path::new(&mount_dir),
489         )
490         .context("Failed to zipfuse extra apks")?;
491     }
492 
493     Ok(())
494 }
495 
496 // Waits until linker config is generated
wait_for_apex_config_done() -> Result<()>497 fn wait_for_apex_config_done() -> Result<()> {
498     let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
499     loop {
500         prop.wait()?;
501         if system_properties::read_bool(APEX_CONFIG_DONE_PROP, false)? {
502             break;
503         }
504     }
505     Ok(())
506 }
507 
get_apk_root_hash_from_idsig(path: &str) -> Result<Box<RootHash>>508 fn get_apk_root_hash_from_idsig(path: &str) -> Result<Box<RootHash>> {
509     let mut idsig = File::open(path)?;
510     let idsig = V4Signature::from(&mut idsig)?;
511     Ok(idsig.hashing_info.raw_root_hash)
512 }
513 
get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>>514 fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
515     if !root_hash_trustful {
516         verify(apk).context(MicrodroidError::PayloadVerificationFailed(format!(
517             "failed to verify {}",
518             apk
519         )))
520     } else {
521         get_public_key_der(apk)
522     }
523 }
524 
load_config(path: &Path) -> Result<VmPayloadConfig>525 fn load_config(path: &Path) -> Result<VmPayloadConfig> {
526     info!("loading config from {:?}...", path);
527     let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
528     Ok(serde_json::from_reader(file)?)
529 }
530 
531 /// Executes the given task. Stdout of the task is piped into the vsock stream to the
532 /// virtualizationservice in the host side.
exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32>533 fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
534     info!("executing main task {:?}...", task);
535     let mut command = build_command(task)?;
536 
537     info!("notifying payload started");
538     service.notifyPayloadStarted()?;
539 
540     // Start logging if enabled
541     // TODO(b/200914564) set filterspec if debug_level is app_only
542     if system_properties::read_bool(LOGD_ENABLED_PROP, false)? {
543         system_properties::write("ctl.start", "seriallogging")?;
544     }
545 
546     let exit_status = command.spawn()?.wait()?;
547     exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
548 }
549 
build_command(task: &Task) -> Result<Command>550 fn build_command(task: &Task) -> Result<Command> {
551     const VMADDR_CID_HOST: u32 = 2;
552 
553     let mut command = match task.type_ {
554         TaskType::Executable => {
555             let mut command = Command::new(&task.command);
556             command.args(&task.args);
557             command
558         }
559         TaskType::MicrodroidLauncher => {
560             let mut command = Command::new("/system/bin/microdroid_launcher");
561             command.arg(find_library_path(&task.command)?).args(&task.args);
562             command
563         }
564     };
565 
566     match VsockStream::connect_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32) {
567         Ok(stream) => {
568             // SAFETY: the ownership of the underlying file descriptor is transferred from stream
569             // to the file object, and then into the Command object. When the command is finished,
570             // the file descriptor is closed.
571             let file = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
572             command
573                 .stdin(Stdio::from(file.try_clone()?))
574                 .stdout(Stdio::from(file.try_clone()?))
575                 .stderr(Stdio::from(file));
576         }
577         Err(e) => {
578             error!("failed to connect to virtualization service: {}", e);
579             // Don't fail hard here. Even if we failed to connect to the virtualizationservice,
580             // we keep executing the task. This can happen if the owner of the VM doesn't register
581             // callback to accept the stream. Use /dev/null as the stream so that the task can
582             // make progress without waiting for someone to consume the output.
583             command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
584         }
585     }
586 
587     Ok(command)
588 }
589 
find_library_path(name: &str) -> Result<String>590 fn find_library_path(name: &str) -> Result<String> {
591     let mut watcher = PropertyWatcher::new("ro.product.cpu.abilist")?;
592     let value = watcher.read(|_name, value| Ok(value.trim().to_string()))?;
593     let abi = value.split(',').next().ok_or_else(|| anyhow!("no abilist"))?;
594     let path = format!("/mnt/apk/lib/{}/{}", abi, name);
595 
596     let metadata = fs::metadata(&path)?;
597     if !metadata.is_file() {
598         bail!("{} is not a file", &path);
599     }
600 
601     Ok(path)
602 }
603 
to_hex_string(buf: &[u8]) -> String604 fn to_hex_string(buf: &[u8]) -> String {
605     buf.iter().map(|b| format!("{:02X}", b)).collect()
606 }
607