blob: e3ad4956579ffd52537e65f8a93eb0d1ccfc3d31 [file] [log] [blame]
Jooyung Han347d9f22021-05-28 00:05:14 +09001// 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
Jiyong Park21ce2c52021-08-28 02:32:17 +090017mod instance;
Jooyung Hanf48ceb42021-06-01 18:00:04 +090018mod ioutil;
Jooyung Han7a343f92021-09-08 22:53:11 +090019mod payload;
Jooyung Han347d9f22021-05-28 00:05:14 +090020
Inseob Kime379e7d2022-07-22 18:55:18 +090021use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
Andrew Scullb2f44472022-01-21 14:41:34 +000022use android_hardware_security_dice::aidl::android::hardware::security::dice::{
23 Config::Config, InputValues::InputValues, Mode::Mode,
24};
25use android_security_dice::aidl::android::security::dice::IDiceMaintenance::IDiceMaintenance;
Alan Stokes2bead0d2022-09-05 16:58:34 +010026use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
27use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
28 VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
29};
Jooyung Handd0a1732021-11-23 15:26:20 +090030use anyhow::{anyhow, bail, ensure, Context, Error, Result};
Jiyong Parka41535b2021-09-10 19:31:48 +090031use apkverify::{get_public_key_der, verify};
Andrew Walbranc4ce7872022-07-29 11:26:41 +000032use binder::{wait_for_interface, Strong};
Andrew Scullb2f44472022-01-21 14:41:34 +000033use diced_utils::cbor::encode_header;
Inseob Kim197748b2021-12-01 19:49:00 +090034use glob::glob;
Jiyong Park21ce2c52021-08-28 02:32:17 +090035use idsig::V4Signature;
Inseob Kim197748b2021-12-01 19:49:00 +090036use itertools::sorted;
Andrew Scull684590f2022-01-27 16:14:26 +000037use log::{error, info};
Jooyung Han4a9b3bf2021-09-10 17:19:00 +090038use microdroid_metadata::{write_metadata, Metadata};
Jooyung Han634e2d72021-06-10 16:27:38 +090039use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
Andrew Sculla0d1b1a2022-05-24 19:32:47 +000040use openssl::sha::Sha512;
Jooyung Han4a9b3bf2021-09-10 17:19:00 +090041use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
Andrew Scull34916a72022-01-30 21:34:24 +000042use rand::Fill;
Andrew Walbran7eb5ca42022-08-08 15:33:34 +000043use rpcbinder::get_vsock_rpc_interface;
Jiyong Parkbb4a9872021-09-06 15:59:21 +090044use rustutils::system_properties;
Joel Galenson482704c2021-07-29 15:53:53 -070045use rustutils::system_properties::PropertyWatcher;
Andrew Scullb2f44472022-01-21 14:41:34 +000046use std::convert::TryInto;
Inseob Kim197748b2021-12-01 19:49:00 +090047use std::fs::{self, create_dir, File, OpenOptions};
Inseob Kim11f40d02022-06-13 17:16:00 +090048use std::io::Write;
Inseob Kimc7d28c72021-10-25 14:28:10 +000049use std::os::unix::io::{FromRawFd, IntoRawFd};
Jooyung Hanf48ceb42021-06-01 18:00:04 +090050use std::path::Path;
Inseob Kim217038e2021-11-25 11:15:06 +090051use std::process::{Child, Command, Stdio};
Jiyong Park8611a6c2021-07-09 18:17:44 +090052use std::str;
Jiyong Parkbb4a9872021-09-06 15:59:21 +090053use std::time::{Duration, SystemTime};
Jiyong Park8611a6c2021-07-09 18:17:44 +090054use vsock::VsockStream;
Jooyung Han634e2d72021-06-10 16:27:38 +090055
56const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
Inseob Kim197748b2021-12-01 19:49:00 +090057const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
58const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
59const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
60const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
61const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
Jooyung Han19c1d6c2021-08-06 14:08:16 +090062const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
Inseob Kim217038e2021-11-25 11:15:06 +090063const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
64const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
Andrew Scullab72ec52022-03-14 09:10:52 +000065const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
66const AVF_NEW_INSTANCE: &str = "/sys/firmware/devicetree/base/chosen/avf,new-instance";
Inseob Kime379e7d2022-07-22 18:55:18 +090067const DEBUG_MICRODROID_NO_VERIFIED_BOOT: &str =
68 "/sys/firmware/devicetree/base/virtualization/guest/debug-microdroid,no-verified-boot";
Jooyung Han347d9f22021-05-28 00:05:14 +090069
Inseob Kim1b95f2e2021-08-19 13:17:40 +090070/// The CID representing the host VM
71const VMADDR_CID_HOST: u32 = 2;
72
Jiyong Parkbb4a9872021-09-06 15:59:21 +090073const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
Andrew Scull65ddfc42022-02-14 21:03:58 +000074const APP_DEBUGGABLE_PROP: &str = "ro.boot.microdroid.app_debuggable";
Jiyong Parkbb4a9872021-09-06 15:59:21 +090075
Inseob Kim11f40d02022-06-13 17:16:00 +090076// SYNC WITH virtualizationservice/src/crosvm.rs
77const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
78
Jooyung Handd0a1732021-11-23 15:26:20 +090079#[derive(thiserror::Error, Debug)]
80enum MicrodroidError {
Inseob Kim11f40d02022-06-13 17:16:00 +090081 #[error("Cannot connect to virtualization service: {0}")]
82 FailedToConnectToVirtualizationService(String),
Jooyung Handd0a1732021-11-23 15:26:20 +090083 #[error("Payload has changed: {0}")]
84 PayloadChanged(String),
85 #[error("Payload verification has failed: {0}")]
86 PayloadVerificationFailed(String),
Jooyung Han5c6d4172021-12-06 14:17:52 +090087 #[error("Payload config is invalid: {0}")]
88 InvalidConfig(String),
Jooyung Handd0a1732021-11-23 15:26:20 +090089}
90
Alan Stokes2bead0d2022-09-05 16:58:34 +010091fn translate_error(err: &Error) -> (ErrorCode, String) {
Jooyung Handd0a1732021-11-23 15:26:20 +090092 if let Some(e) = err.downcast_ref::<MicrodroidError>() {
93 match e {
Alan Stokes2bead0d2022-09-05 16:58:34 +010094 MicrodroidError::PayloadChanged(msg) => (ErrorCode::PAYLOAD_CHANGED, msg.to_string()),
Jooyung Handd0a1732021-11-23 15:26:20 +090095 MicrodroidError::PayloadVerificationFailed(msg) => {
Alan Stokes2bead0d2022-09-05 16:58:34 +010096 (ErrorCode::PAYLOAD_VERIFICATION_FAILED, msg.to_string())
Jooyung Handd0a1732021-11-23 15:26:20 +090097 }
Alan Stokes2bead0d2022-09-05 16:58:34 +010098 MicrodroidError::InvalidConfig(msg) => {
99 (ErrorCode::PAYLOAD_CONFIG_INVALID, msg.to_string())
100 }
Inseob Kim11f40d02022-06-13 17:16:00 +0900101
102 // Connection failure won't be reported to VS; return the default value
103 MicrodroidError::FailedToConnectToVirtualizationService(msg) => {
Alan Stokes2bead0d2022-09-05 16:58:34 +0100104 (ErrorCode::UNKNOWN, msg.to_string())
Inseob Kim11f40d02022-06-13 17:16:00 +0900105 }
Jooyung Handd0a1732021-11-23 15:26:20 +0900106 }
107 } else {
Alan Stokes2bead0d2022-09-05 16:58:34 +0100108 (ErrorCode::UNKNOWN, err.to_string())
Jooyung Handd0a1732021-11-23 15:26:20 +0900109 }
110}
111
Inseob Kim11f40d02022-06-13 17:16:00 +0900112fn write_death_reason_to_serial(err: &Error) -> Result<()> {
113 let death_reason = if let Some(e) = err.downcast_ref::<MicrodroidError>() {
114 match e {
115 MicrodroidError::FailedToConnectToVirtualizationService(_) => {
116 "MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE"
117 }
118 MicrodroidError::PayloadChanged(_) => "MICRODROID_PAYLOAD_HAS_CHANGED",
119 MicrodroidError::PayloadVerificationFailed(_) => {
120 "MICRODROID_PAYLOAD_VERIFICATION_FAILED"
121 }
122 MicrodroidError::InvalidConfig(_) => "MICRODROID_INVALID_PAYLOAD_CONFIG",
123 }
124 } else {
125 "MICRODROID_UNKNOWN_RUNTIME_ERROR"
126 };
127
128 let death_reason_bytes = death_reason.as_bytes();
129 let mut sent_total = 0;
130 while sent_total < death_reason_bytes.len() {
131 // TODO(b/220071963): Sometimes, sending more than 16 bytes at once makes MM hang.
132 let begin = sent_total;
133 let end = std::cmp::min(begin.saturating_add(16), death_reason_bytes.len());
134 OpenOptions::new()
135 .read(false)
136 .write(true)
137 .open(FAILURE_SERIAL_DEVICE)?
138 .write_all(&death_reason_bytes[begin..end])?;
139 sent_total = end;
140 }
141
142 Ok(())
143}
144
Inseob Kim1b95f2e2021-08-19 13:17:40 +0900145fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
Andrew Walbran7eb5ca42022-08-08 15:33:34 +0000146 get_vsock_rpc_interface(VMADDR_CID_HOST, VM_BINDER_SERVICE_PORT as u32)
Andrew Walbranc4ce7872022-07-29 11:26:41 +0000147 .context("Cannot connect to RPC service")
Inseob Kim1b95f2e2021-08-19 13:17:40 +0900148}
149
Inseob Kim437f1052022-06-21 11:30:22 +0900150fn main() -> Result<()> {
151 scopeguard::defer! {
152 info!("Shutting down...");
Jooyung Hanbfe086f2021-10-28 10:15:45 +0900153 if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
154 error!("failed to shutdown {:?}", e);
155 }
Jooyung Han311b1202021-09-14 22:00:16 +0900156 }
Inseob Kim437f1052022-06-21 11:30:22 +0900157
158 try_main().map_err(|e| {
159 error!("Failed with {:?}.", e);
160 if let Err(e) = write_death_reason_to_serial(&e) {
161 error!("Failed to write death reason {:?}", e);
162 }
163 e
164 })
Jooyung Han311b1202021-09-14 22:00:16 +0900165}
166
167fn try_main() -> Result<()> {
Andrew Sculle127cff2022-02-15 15:34:04 +0000168 let _ = kernlog::init();
Jooyung Han347d9f22021-05-28 00:05:14 +0900169 info!("started.");
170
Jiyong Park202856e2022-08-22 16:04:26 +0900171 load_crashkernel_if_supported().context("Failed to load crashkernel")?;
172
Inseob Kim11f40d02022-06-13 17:16:00 +0900173 let service = get_vms_rpc_binder()
174 .context("cannot connect to VirtualMachineService")
175 .map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900176 match try_run_payload(&service) {
177 Ok(code) => {
178 info!("notifying payload finished");
179 service.notifyPayloadFinished(code)?;
180 if code == 0 {
181 info!("task successfully finished");
182 } else {
183 error!("task exited with exit code: {}", code);
184 }
185 Ok(())
186 }
187 Err(err) => {
Jooyung Han5c6d4172021-12-06 14:17:52 +0900188 let (error_code, message) = translate_error(&err);
189 service.notifyError(error_code, &message)?;
190 Err(err)
191 }
Jooyung Handd0a1732021-11-23 15:26:20 +0900192 }
193}
194
Inseob Kimb2519c52022-04-14 02:10:09 +0900195fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
Andrew Scullb2f44472022-01-21 14:41:34 +0000196 // Calculate compound digests of code and authorities
Andrew Sculla0d1b1a2022-05-24 19:32:47 +0000197 let mut code_hash_ctx = Sha512::new();
198 let mut authority_hash_ctx = Sha512::new();
Andrew Scullb2f44472022-01-21 14:41:34 +0000199 code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
200 authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
Inseob Kimb2519c52022-04-14 02:10:09 +0900201 for extra_apk in &verified_data.extra_apks_data {
Andrew Scullb2f44472022-01-21 14:41:34 +0000202 code_hash_ctx.update(extra_apk.root_hash.as_ref());
203 authority_hash_ctx.update(extra_apk.pubkey.as_ref());
204 }
Inseob Kimb2519c52022-04-14 02:10:09 +0900205 for apex in &verified_data.apex_data {
Andrew Scullb2f44472022-01-21 14:41:34 +0000206 code_hash_ctx.update(apex.root_digest.as_ref());
207 authority_hash_ctx.update(apex.public_key.as_ref());
208 }
Andrew Sculla0d1b1a2022-05-24 19:32:47 +0000209 let code_hash = code_hash_ctx.finish();
210 let authority_hash = authority_hash_ctx.finish();
Andrew Scullb2f44472022-01-21 14:41:34 +0000211
212 // {
213 // -70002: "Microdroid payload",
214 // -71000: payload_config_path
215 // }
216 let mut config_desc = vec![
217 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72, 0x6f,
218 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01, 0x15, 0x57,
219 ];
220 let config_path_bytes = payload_config_path.as_bytes();
221 encode_header(3, config_path_bytes.len().try_into().unwrap(), &mut config_desc)?;
222 config_desc.extend_from_slice(config_path_bytes);
223
Andrew Scull65ddfc42022-02-14 21:03:58 +0000224 // Check app debuggability, conervatively assuming it is debuggable
225 let app_debuggable = system_properties::read_bool(APP_DEBUGGABLE_PROP, true)?;
226
Andrew Scullb2f44472022-01-21 14:41:34 +0000227 // Send the details to diced
228 let diced =
229 wait_for_interface::<dyn IDiceMaintenance>("android.security.dice.IDiceMaintenance")
230 .context("IDiceMaintenance service not found")?;
231 diced
232 .demoteSelf(&[InputValues {
233 codeHash: code_hash,
234 config: Config { desc: config_desc },
235 authorityHash: authority_hash,
236 authorityDescriptor: None,
Andrew Scull65ddfc42022-02-14 21:03:58 +0000237 mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
Inseob Kimb2519c52022-04-14 02:10:09 +0900238 hidden: verified_data.salt.clone().try_into().unwrap(),
Andrew Scullb2f44472022-01-21 14:41:34 +0000239 }])
240 .context("IDiceMaintenance::demoteSelf failed")?;
241 Ok(())
242}
243
Andrew Scullab72ec52022-03-14 09:10:52 +0000244fn is_strict_boot() -> bool {
245 Path::new(AVF_STRICT_BOOT).exists()
246}
247
248fn is_new_instance() -> bool {
249 Path::new(AVF_NEW_INSTANCE).exists()
250}
251
Inseob Kime379e7d2022-07-22 18:55:18 +0900252fn is_verified_boot() -> bool {
253 !Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
254}
255
Jooyung Han5c6d4172021-12-06 14:17:52 +0900256fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
Jooyung Han311b1202021-09-14 22:00:16 +0900257 let metadata = load_metadata().context("Failed to load payload metadata")?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900258
Jooyung Han311b1202021-09-14 22:00:16 +0900259 let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900260 let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900261
Andrew Scullab72ec52022-03-14 09:10:52 +0000262 if is_strict_boot() {
263 // Provisioning must happen on the first boot and never again.
264 if is_new_instance() {
265 ensure!(
266 saved_data.is_none(),
267 MicrodroidError::InvalidConfig("Found instance data on first boot.".to_string())
268 );
269 } else {
270 ensure!(
271 saved_data.is_some(),
272 MicrodroidError::InvalidConfig("Instance data not found.".to_string())
273 );
274 };
275 }
276
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900277 // Verify the payload before using it.
Inseob Kim11f40d02022-06-13 17:16:00 +0900278 let verified_data = verify_payload(&metadata, saved_data.as_ref())
279 .context("Payload verification failed")
280 .map_err(|e| MicrodroidError::PayloadVerificationFailed(e.to_string()))?;
Inseob Kime379e7d2022-07-22 18:55:18 +0900281
282 // In case identity is ignored (by debug policy), we should reuse existing payload data, even
283 // when the payload is changed. This is to keep the derived secret same as before.
284 let verified_data = if let Some(saved_data) = saved_data {
285 if !is_verified_boot() {
286 if saved_data != verified_data {
287 info!("Detected an update of the payload, but continue (regarding debug policy)")
288 }
289 } else {
290 ensure!(
291 saved_data == verified_data,
292 MicrodroidError::PayloadChanged(String::from(
293 "Detected an update of the payload which isn't supported yet."
294 ))
295 );
296 info!("Saved data is verified.");
297 }
298 saved_data
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900299 } else {
Jooyung Han7a343f92021-09-08 22:53:11 +0900300 info!("Saving verified data.");
301 instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
Inseob Kime379e7d2022-07-22 18:55:18 +0900302 verified_data
303 };
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900304
Inseob Kimb2519c52022-04-14 02:10:09 +0900305 // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
306 info!("DICE derivation for payload");
307 dice_derivation(&verified_data, &metadata.payload_config_path)?;
308
Jooyung Hana6d11eb2021-09-10 11:48:05 +0900309 // Before reading a file from the APK, start zipfuse
Andrew Scullcc339a12022-07-04 12:44:19 +0000310 let noexec = false;
Inseob Kim217038e2021-11-25 11:15:06 +0900311 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000312 noexec,
Inseob Kim217038e2021-11-25 11:15:06 +0900313 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
314 Path::new("/dev/block/mapper/microdroid-apk"),
315 Path::new("/mnt/apk"),
316 )
317 .context("Failed to run zipfuse")?;
Jiyong Park21ce2c52021-08-28 02:32:17 +0900318
Jooyung Han5c6d4172021-12-06 14:17:52 +0900319 ensure!(
320 !metadata.payload_config_path.is_empty(),
321 MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
322 );
Inseob Kim197748b2021-12-01 19:49:00 +0900323
Jooyung Han5c6d4172021-12-06 14:17:52 +0900324 let config = load_config(Path::new(&metadata.payload_config_path))?;
Shikha Panwar6f03c942022-04-13 20:26:50 +0000325
Inseob Kim197748b2021-12-01 19:49:00 +0900326 if config.extra_apks.len() != verified_data.extra_apks_data.len() {
327 return Err(anyhow!(
328 "config expects {} extra apks, but found only {}",
329 config.extra_apks.len(),
330 verified_data.extra_apks_data.len()
331 ));
332 }
333 mount_extra_apks(&config)?;
Jooyung Han634e2d72021-06-10 16:27:38 +0900334
Jooyung Han5c6d4172021-12-06 14:17:52 +0900335 // Wait until apex config is done. (e.g. linker configuration for apexes)
336 // TODO(jooyung): wait until sys.boot_completed?
337 wait_for_apex_config_done()?;
338
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900339 // Start tombstone_transmit if enabled
340 if config.export_tombstones {
341 system_properties::write("ctl.start", "tombstone_transmit")
342 .context("Failed to start tombstone_transmit")?;
343 } else {
344 system_properties::write("ctl.stop", "tombstoned").context("Failed to stop tombstoned")?;
345 }
346
Jooyung Han5c6d4172021-12-06 14:17:52 +0900347 ensure!(
348 config.task.is_some(),
349 MicrodroidError::InvalidConfig("No task in VM config".to_string())
350 );
Victor Hsieh8d006ba2022-05-02 09:42:02 -0700351 system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900352 exec_task(&config.task.unwrap(), service)
Jooyung Han347d9f22021-05-28 00:05:14 +0900353}
354
Inseob Kim217038e2021-11-25 11:15:06 +0900355struct ApkDmverityArgument<'a> {
356 apk: &'a str,
357 idsig: &'a str,
358 name: &'a str,
Inseob Kim197748b2021-12-01 19:49:00 +0900359 saved_root_hash: Option<&'a RootHash>,
Inseob Kim217038e2021-11-25 11:15:06 +0900360}
361
362fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
363 let mut cmd = Command::new(APKDMVERITY_BIN);
364
365 cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
366
367 for argument in args {
368 cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
Inseob Kim197748b2021-12-01 19:49:00 +0900369 if let Some(root_hash) = argument.saved_root_hash {
370 cmd.arg(&to_hex_string(root_hash));
371 } else {
372 cmd.arg("none");
373 }
Inseob Kim217038e2021-11-25 11:15:06 +0900374 }
375
376 cmd.spawn().context("Spawn apkdmverity")
377}
378
Andrew Scullcc339a12022-07-04 12:44:19 +0000379fn run_zipfuse(noexec: bool, option: &str, zip_path: &Path, mount_dir: &Path) -> Result<Child> {
380 let mut cmd = Command::new(ZIPFUSE_BIN);
381 if noexec {
382 cmd.arg("--noexec");
383 }
384 cmd.arg("-o")
Inseob Kim217038e2021-11-25 11:15:06 +0900385 .arg(option)
386 .arg(zip_path)
387 .arg(mount_dir)
388 .stdin(Stdio::null())
389 .stdout(Stdio::null())
390 .stderr(Stdio::null())
391 .spawn()
392 .context("Spawn zipfuse")
393}
394
Inseob Kime379e7d2022-07-22 18:55:18 +0900395fn write_apex_payload_data(
396 saved_data: Option<&MicrodroidData>,
397 apex_data_from_payload: &[ApexData],
398) -> Result<()> {
399 if let Some(saved_apex_data) = saved_data.map(|d| &d.apex_data) {
400 // We don't support APEX updates. (assuming that update will change root digest)
401 ensure!(
402 saved_apex_data == apex_data_from_payload,
403 MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
404 );
405 let apex_metadata = to_metadata(apex_data_from_payload);
406 // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
407 // metadata instead of the default one (/dev/block/by-name/payload-metadata)
408 OpenOptions::new()
409 .create_new(true)
410 .write(true)
411 .open("/apex/vm-payload-metadata")
412 .context("Failed to open /apex/vm-payload-metadata")
413 .and_then(|f| write_metadata(&apex_metadata, f))?;
414 }
415 Ok(())
416}
417
Jooyung Han7a343f92021-09-08 22:53:11 +0900418// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
419// when the root_hash values from the idsig file and the instance disk are different. This function
420// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
421// saved to the instance disk.
422fn verify_payload(
423 metadata: &Metadata,
424 saved_data: Option<&MicrodroidData>,
425) -> Result<MicrodroidData> {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900426 let start_time = SystemTime::now();
427
Inseob Kim197748b2021-12-01 19:49:00 +0900428 // Verify main APK
Jooyung Han7a343f92021-09-08 22:53:11 +0900429 let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
Inseob Kim197748b2021-12-01 19:49:00 +0900430 let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
Jiyong Parkf7dea252021-09-08 01:42:54 +0900431 let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900432
Jiyong Parkf7dea252021-09-08 01:42:54 +0900433 // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900434 // instead of the value read from the idsig file.
Inseob Kim197748b2021-12-01 19:49:00 +0900435 let main_apk_argument = {
436 ApkDmverityArgument {
437 apk: MAIN_APK_PATH,
438 idsig: MAIN_APK_IDSIG_PATH,
439 name: MAIN_APK_DEVICE_NAME,
440 saved_root_hash: if root_hash_trustful {
441 Some(root_hash_from_idsig.as_ref())
442 } else {
443 None
444 },
445 }
446 };
447 let mut apkdmverity_arguments = vec![main_apk_argument];
448
449 // Verify extra APKs
450 // For now, we can't read the payload config, so glob APKs and idsigs.
451 // Later, we'll see if it matches with the payload config.
452
453 // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
454 // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
455 let extra_apks =
456 sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
457 let extra_idsigs =
458 sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
459 if extra_apks.len() != extra_idsigs.len() {
460 return Err(anyhow!(
461 "Extra apks/idsigs mismatch: {} apks but {} idsigs",
462 extra_apks.len(),
463 extra_idsigs.len()
464 ));
465 }
466 let extra_apks_count = extra_apks.len();
467
468 let (extra_apk_names, extra_root_hashes_from_idsig): (Vec<_>, Vec<_>) = extra_idsigs
469 .iter()
470 .enumerate()
471 .map(|(i, extra_idsig)| {
472 (
473 format!("extra-apk-{}", i),
474 get_apk_root_hash_from_idsig(extra_idsig.to_str().unwrap())
475 .expect("Can't find root hash from extra idsig"),
476 )
477 })
478 .unzip();
479
480 let saved_extra_root_hashes: Vec<_> = saved_data
481 .map(|d| d.extra_apks_data.iter().map(|apk_data| &apk_data.root_hash).collect())
482 .unwrap_or_else(Vec::new);
483 let extra_root_hashes_trustful: Vec<_> = extra_root_hashes_from_idsig
484 .iter()
485 .enumerate()
486 .map(|(i, root_hash_from_idsig)| {
487 saved_extra_root_hashes.get(i).copied() == Some(root_hash_from_idsig)
488 })
489 .collect();
490
491 for i in 0..extra_apks_count {
492 apkdmverity_arguments.push({
493 ApkDmverityArgument {
494 apk: extra_apks[i].to_str().unwrap(),
495 idsig: extra_idsigs[i].to_str().unwrap(),
496 name: &extra_apk_names[i],
497 saved_root_hash: if extra_root_hashes_trustful[i] {
498 Some(&extra_root_hashes_from_idsig[i])
499 } else {
500 None
501 },
502 }
503 });
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900504 }
505
506 // Start apkdmverity and wait for the dm-verify block
Inseob Kim197748b2021-12-01 19:49:00 +0900507 let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900508
Jooyung Hanc8deb472021-09-13 13:48:25 +0900509 // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
510 // APEX payload.
Jooyung Han7a343f92021-09-08 22:53:11 +0900511 let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
Inseob Kime379e7d2022-07-22 18:55:18 +0900512
513 // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
514 // Skip writing it if the debug policy ignoring identity is on
515 if is_verified_boot() {
516 write_apex_payload_data(saved_data, &apex_data_from_payload)?;
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900517 }
Inseob Kime379e7d2022-07-22 18:55:18 +0900518
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900519 // Start apexd to activate APEXes
520 system_properties::write("ctl.start", "apexd-vm")?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900521
Inseob Kim217038e2021-11-25 11:15:06 +0900522 // TODO(inseob): add timeout
523 apkdmverity_child.wait()?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900524
Jiyong Parkf7dea252021-09-08 01:42:54 +0900525 // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900526 // the APK file and therefore can be very slow if the APK is large. Note that this step is
Jiyong Parkf7dea252021-09-08 01:42:54 +0900527 // taken only when the root_hash is un-trustful which can be either when this is the first boot
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900528 // of the VM or APK was updated in the host.
529 // TODO(jooyung): consider multithreading to make this faster
Inseob Kim197748b2021-12-01 19:49:00 +0900530 let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
531 let extra_apks_data = extra_root_hashes_from_idsig
532 .into_iter()
533 .enumerate()
534 .map(|(i, extra_root_hash)| {
535 let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
536 let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
537 Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
538 })
539 .collect::<Result<Vec<_>>>()?;
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900540
541 info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
542
Andrew Scull34916a72022-01-30 21:34:24 +0000543 // Use the salt from a verified instance, or generate a salt for a new instance.
544 let salt = if let Some(saved_data) = saved_data {
545 saved_data.salt.clone()
546 } else {
547 let mut salt = vec![0u8; 64];
548 salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
549 salt
550 };
551
Jiyong Parkf7dea252021-09-08 01:42:54 +0900552 // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
553 // fully verifying the APK or by comparing it with the saved root_hash.
Jooyung Han7a343f92021-09-08 22:53:11 +0900554 Ok(MicrodroidData {
Andrew Scull34916a72022-01-30 21:34:24 +0000555 salt,
Inseob Kim197748b2021-12-01 19:49:00 +0900556 apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
557 extra_apks_data,
Jooyung Han7a343f92021-09-08 22:53:11 +0900558 apex_data: apex_data_from_payload,
559 })
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900560}
561
Inseob Kim197748b2021-12-01 19:49:00 +0900562fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
563 // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
564 for i in 0..config.extra_apks.len() {
565 let mount_dir = format!("/mnt/extra-apk/{}", i);
566 create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
567
568 // don't wait, just detach
Andrew Scullcc339a12022-07-04 12:44:19 +0000569 let noexec = true;
Inseob Kim197748b2021-12-01 19:49:00 +0900570 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000571 noexec,
Inseob Kim197748b2021-12-01 19:49:00 +0900572 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
573 Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
574 Path::new(&mount_dir),
575 )
576 .context("Failed to zipfuse extra apks")?;
577 }
578
579 Ok(())
580}
581
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900582// Waits until linker config is generated
583fn wait_for_apex_config_done() -> Result<()> {
584 let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
585 loop {
586 prop.wait()?;
Andrew Scull4b5369f2022-01-28 13:47:20 +0000587 if system_properties::read_bool(APEX_CONFIG_DONE_PROP, false)? {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900588 break;
589 }
590 }
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900591 Ok(())
592}
593
Inseob Kim197748b2021-12-01 19:49:00 +0900594fn get_apk_root_hash_from_idsig(path: &str) -> Result<Box<RootHash>> {
595 let mut idsig = File::open(path)?;
Jiyong Park21ce2c52021-08-28 02:32:17 +0900596 let idsig = V4Signature::from(&mut idsig)?;
597 Ok(idsig.hashing_info.raw_root_hash)
598}
599
Inseob Kim197748b2021-12-01 19:49:00 +0900600fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
601 if !root_hash_trustful {
602 verify(apk).context(MicrodroidError::PayloadVerificationFailed(format!(
603 "failed to verify {}",
604 apk
605 )))
606 } else {
607 get_public_key_der(apk)
608 }
609}
610
Jooyung Han634e2d72021-06-10 16:27:38 +0900611fn load_config(path: &Path) -> Result<VmPayloadConfig> {
612 info!("loading config from {:?}...", path);
613 let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
614 Ok(serde_json::from_reader(file)?)
615}
616
Jiyong Park202856e2022-08-22 16:04:26 +0900617/// Loads the crashkernel into memory using kexec if the VM is loaded with `crashkernel=' parameter
618/// in the cmdline.
619fn load_crashkernel_if_supported() -> Result<()> {
620 let supported = std::fs::read_to_string("/proc/cmdline")?.contains(" crashkernel=");
621 info!("ramdump supported: {}", supported);
622 if supported {
623 let status = Command::new("/system/bin/kexec_load").status()?;
624 if !status.success() {
625 return Err(anyhow!("Failed to load crashkernel: {:?}", status));
626 }
627 }
628 Ok(())
629}
630
Jiyong Park8611a6c2021-07-09 18:17:44 +0900631/// Executes the given task. Stdout of the task is piped into the vsock stream to the
632/// virtualizationservice in the host side.
Jooyung Han5c6d4172021-12-06 14:17:52 +0900633fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
Jiyong Park8611a6c2021-07-09 18:17:44 +0900634 info!("executing main task {:?}...", task);
Inseob Kim86ca0162021-10-20 02:21:02 +0000635 let mut command = build_command(task)?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900636
637 info!("notifying payload started");
Inseob Kimc7d28c72021-10-25 14:28:10 +0000638 service.notifyPayloadStarted()?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900639
Inseob Kim86ca0162021-10-20 02:21:02 +0000640 let exit_status = command.spawn()?.wait()?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900641 exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
Jooyung Han347d9f22021-05-28 00:05:14 +0900642}
Jooyung Han634e2d72021-06-10 16:27:38 +0900643
644fn build_command(task: &Task) -> Result<Command> {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900645 const VMADDR_CID_HOST: u32 = 2;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900646
647 let mut command = match task.type_ {
Jooyung Han634e2d72021-06-10 16:27:38 +0900648 TaskType::Executable => {
649 let mut command = Command::new(&task.command);
650 command.args(&task.args);
651 command
652 }
653 TaskType::MicrodroidLauncher => {
654 let mut command = Command::new("/system/bin/microdroid_launcher");
655 command.arg(find_library_path(&task.command)?).args(&task.args);
656 command
657 }
Inseob Kim7f61fe72021-08-20 20:50:47 +0900658 };
659
Inseob Kimd0587562021-09-01 21:27:32 +0900660 match VsockStream::connect_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32) {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900661 Ok(stream) => {
662 // SAFETY: the ownership of the underlying file descriptor is transferred from stream
663 // to the file object, and then into the Command object. When the command is finished,
664 // the file descriptor is closed.
665 let file = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
666 command
667 .stdin(Stdio::from(file.try_clone()?))
668 .stdout(Stdio::from(file.try_clone()?))
669 .stderr(Stdio::from(file));
670 }
671 Err(e) => {
672 error!("failed to connect to virtualization service: {}", e);
673 // Don't fail hard here. Even if we failed to connect to the virtualizationservice,
674 // we keep executing the task. This can happen if the owner of the VM doesn't register
675 // callback to accept the stream. Use /dev/null as the stream so that the task can
676 // make progress without waiting for someone to consume the output.
677 command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
678 }
679 }
680
681 Ok(command)
Jooyung Han634e2d72021-06-10 16:27:38 +0900682}
683
684fn find_library_path(name: &str) -> Result<String> {
685 let mut watcher = PropertyWatcher::new("ro.product.cpu.abilist")?;
686 let value = watcher.read(|_name, value| Ok(value.trim().to_string()))?;
687 let abi = value.split(',').next().ok_or_else(|| anyhow!("no abilist"))?;
688 let path = format!("/mnt/apk/lib/{}/{}", abi, name);
689
690 let metadata = fs::metadata(&path)?;
691 if !metadata.is_file() {
692 bail!("{} is not a file", &path);
693 }
694
695 Ok(path)
696}
Jiyong Park21ce2c52021-08-28 02:32:17 +0900697
698fn to_hex_string(buf: &[u8]) -> String {
699 buf.iter().map(|b| format!("{:02X}", b)).collect()
700}