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