blob: 90cabb63f92d705e96e007fd83e5346ec149d3b4 [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};
Alice Wang1bf3d782022-09-28 07:56:36 +000031use apkverify::{get_public_key_der, verify, V4Signature};
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;
Inseob Kim197748b2021-12-01 19:49:00 +090035use itertools::sorted;
Andrew Scull684590f2022-01-27 16:14:26 +000036use log::{error, info};
Alan Stokes0d1ef782022-09-27 13:46:35 +010037use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
Jooyung Han634e2d72021-06-10 16:27:38 +090038use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
Andrew Sculla0d1b1a2022-05-24 19:32:47 +000039use openssl::sha::Sha512;
Jooyung Han4a9b3bf2021-09-10 17:19:00 +090040use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
Andrew Scull34916a72022-01-30 21:34:24 +000041use rand::Fill;
Andrew Walbran7eb5ca42022-08-08 15:33:34 +000042use rpcbinder::get_vsock_rpc_interface;
Jiyong Parkbb4a9872021-09-06 15:59:21 +090043use rustutils::system_properties;
Joel Galenson482704c2021-07-29 15:53:53 -070044use rustutils::system_properties::PropertyWatcher;
Andrew Scullb2f44472022-01-21 14:41:34 +000045use std::convert::TryInto;
Inseob Kim197748b2021-12-01 19:49:00 +090046use std::fs::{self, create_dir, File, OpenOptions};
Inseob Kim11f40d02022-06-13 17:16:00 +090047use std::io::Write;
Inseob Kimc7d28c72021-10-25 14:28:10 +000048use std::os::unix::io::{FromRawFd, IntoRawFd};
Jooyung Hanf48ceb42021-06-01 18:00:04 +090049use std::path::Path;
Inseob Kim217038e2021-11-25 11:15:06 +090050use std::process::{Child, Command, Stdio};
Jiyong Park8611a6c2021-07-09 18:17:44 +090051use std::str;
Jiyong Parkbb4a9872021-09-06 15:59:21 +090052use std::time::{Duration, SystemTime};
Jiyong Park8611a6c2021-07-09 18:17:44 +090053use vsock::VsockStream;
Jooyung Han634e2d72021-06-10 16:27:38 +090054
55const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
Inseob Kim197748b2021-12-01 19:49:00 +090056const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
57const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
58const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
59const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
60const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
Jooyung Han19c1d6c2021-08-06 14:08:16 +090061const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
Inseob Kim217038e2021-11-25 11:15:06 +090062const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
63const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
Andrew Scullab72ec52022-03-14 09:10:52 +000064const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
65const AVF_NEW_INSTANCE: &str = "/sys/firmware/devicetree/base/chosen/avf,new-instance";
Inseob Kime379e7d2022-07-22 18:55:18 +090066const DEBUG_MICRODROID_NO_VERIFIED_BOOT: &str =
67 "/sys/firmware/devicetree/base/virtualization/guest/debug-microdroid,no-verified-boot";
Jooyung Han347d9f22021-05-28 00:05:14 +090068
Inseob Kim1b95f2e2021-08-19 13:17:40 +090069/// The CID representing the host VM
70const VMADDR_CID_HOST: u32 = 2;
71
Jiyong Parkbb4a9872021-09-06 15:59:21 +090072const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
Andrew Scull65ddfc42022-02-14 21:03:58 +000073const APP_DEBUGGABLE_PROP: &str = "ro.boot.microdroid.app_debuggable";
Jiyong Parkbb4a9872021-09-06 15:59:21 +090074
Inseob Kim11f40d02022-06-13 17:16:00 +090075// SYNC WITH virtualizationservice/src/crosvm.rs
76const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
77
Jooyung Handd0a1732021-11-23 15:26:20 +090078#[derive(thiserror::Error, Debug)]
79enum MicrodroidError {
Inseob Kim11f40d02022-06-13 17:16:00 +090080 #[error("Cannot connect to virtualization service: {0}")]
81 FailedToConnectToVirtualizationService(String),
Jooyung Handd0a1732021-11-23 15:26:20 +090082 #[error("Payload has changed: {0}")]
83 PayloadChanged(String),
84 #[error("Payload verification has failed: {0}")]
85 PayloadVerificationFailed(String),
Jooyung Han5c6d4172021-12-06 14:17:52 +090086 #[error("Payload config is invalid: {0}")]
87 InvalidConfig(String),
Jooyung Handd0a1732021-11-23 15:26:20 +090088}
89
Alan Stokes2bead0d2022-09-05 16:58:34 +010090fn translate_error(err: &Error) -> (ErrorCode, String) {
Jooyung Handd0a1732021-11-23 15:26:20 +090091 if let Some(e) = err.downcast_ref::<MicrodroidError>() {
92 match e {
Alan Stokes2bead0d2022-09-05 16:58:34 +010093 MicrodroidError::PayloadChanged(msg) => (ErrorCode::PAYLOAD_CHANGED, msg.to_string()),
Jooyung Handd0a1732021-11-23 15:26:20 +090094 MicrodroidError::PayloadVerificationFailed(msg) => {
Alan Stokes2bead0d2022-09-05 16:58:34 +010095 (ErrorCode::PAYLOAD_VERIFICATION_FAILED, msg.to_string())
Jooyung Handd0a1732021-11-23 15:26:20 +090096 }
Alan Stokes2bead0d2022-09-05 16:58:34 +010097 MicrodroidError::InvalidConfig(msg) => {
98 (ErrorCode::PAYLOAD_CONFIG_INVALID, msg.to_string())
99 }
Inseob Kim11f40d02022-06-13 17:16:00 +0900100
101 // Connection failure won't be reported to VS; return the default value
102 MicrodroidError::FailedToConnectToVirtualizationService(msg) => {
Alan Stokes2bead0d2022-09-05 16:58:34 +0100103 (ErrorCode::UNKNOWN, msg.to_string())
Inseob Kim11f40d02022-06-13 17:16:00 +0900104 }
Jooyung Handd0a1732021-11-23 15:26:20 +0900105 }
106 } else {
Alan Stokes2bead0d2022-09-05 16:58:34 +0100107 (ErrorCode::UNKNOWN, err.to_string())
Jooyung Handd0a1732021-11-23 15:26:20 +0900108 }
109}
110
Inseob Kim11f40d02022-06-13 17:16:00 +0900111fn write_death_reason_to_serial(err: &Error) -> Result<()> {
112 let death_reason = if let Some(e) = err.downcast_ref::<MicrodroidError>() {
113 match e {
114 MicrodroidError::FailedToConnectToVirtualizationService(_) => {
115 "MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE"
116 }
117 MicrodroidError::PayloadChanged(_) => "MICRODROID_PAYLOAD_HAS_CHANGED",
118 MicrodroidError::PayloadVerificationFailed(_) => {
119 "MICRODROID_PAYLOAD_VERIFICATION_FAILED"
120 }
121 MicrodroidError::InvalidConfig(_) => "MICRODROID_INVALID_PAYLOAD_CONFIG",
122 }
123 } else {
124 "MICRODROID_UNKNOWN_RUNTIME_ERROR"
125 };
126
127 let death_reason_bytes = death_reason.as_bytes();
128 let mut sent_total = 0;
129 while sent_total < death_reason_bytes.len() {
130 // TODO(b/220071963): Sometimes, sending more than 16 bytes at once makes MM hang.
131 let begin = sent_total;
132 let end = std::cmp::min(begin.saturating_add(16), death_reason_bytes.len());
133 OpenOptions::new()
134 .read(false)
135 .write(true)
136 .open(FAILURE_SERIAL_DEVICE)?
137 .write_all(&death_reason_bytes[begin..end])?;
138 sent_total = end;
139 }
140
141 Ok(())
142}
143
Inseob Kim1b95f2e2021-08-19 13:17:40 +0900144fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
Andrew Walbran7eb5ca42022-08-08 15:33:34 +0000145 get_vsock_rpc_interface(VMADDR_CID_HOST, VM_BINDER_SERVICE_PORT as u32)
Andrew Walbranc4ce7872022-07-29 11:26:41 +0000146 .context("Cannot connect to RPC service")
Inseob Kim1b95f2e2021-08-19 13:17:40 +0900147}
148
Inseob Kim437f1052022-06-21 11:30:22 +0900149fn main() -> Result<()> {
150 scopeguard::defer! {
151 info!("Shutting down...");
Jooyung Hanbfe086f2021-10-28 10:15:45 +0900152 if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
153 error!("failed to shutdown {:?}", e);
154 }
Jooyung Han311b1202021-09-14 22:00:16 +0900155 }
Inseob Kim437f1052022-06-21 11:30:22 +0900156
157 try_main().map_err(|e| {
158 error!("Failed with {:?}.", e);
159 if let Err(e) = write_death_reason_to_serial(&e) {
160 error!("Failed to write death reason {:?}", e);
161 }
162 e
163 })
Jooyung Han311b1202021-09-14 22:00:16 +0900164}
165
166fn try_main() -> Result<()> {
Andrew Sculle127cff2022-02-15 15:34:04 +0000167 let _ = kernlog::init();
Jooyung Han347d9f22021-05-28 00:05:14 +0900168 info!("started.");
169
Jiyong Park202856e2022-08-22 16:04:26 +0900170 load_crashkernel_if_supported().context("Failed to load crashkernel")?;
171
Inseob Kim11f40d02022-06-13 17:16:00 +0900172 let service = get_vms_rpc_binder()
173 .context("cannot connect to VirtualMachineService")
174 .map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900175 match try_run_payload(&service) {
176 Ok(code) => {
177 info!("notifying payload finished");
178 service.notifyPayloadFinished(code)?;
179 if code == 0 {
180 info!("task successfully finished");
181 } else {
182 error!("task exited with exit code: {}", code);
183 }
184 Ok(())
185 }
186 Err(err) => {
Jooyung Han5c6d4172021-12-06 14:17:52 +0900187 let (error_code, message) = translate_error(&err);
188 service.notifyError(error_code, &message)?;
189 Err(err)
190 }
Jooyung Handd0a1732021-11-23 15:26:20 +0900191 }
192}
193
Inseob Kimb2519c52022-04-14 02:10:09 +0900194fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
Andrew Scullb2f44472022-01-21 14:41:34 +0000195 // Calculate compound digests of code and authorities
Andrew Sculla0d1b1a2022-05-24 19:32:47 +0000196 let mut code_hash_ctx = Sha512::new();
197 let mut authority_hash_ctx = Sha512::new();
Andrew Scullb2f44472022-01-21 14:41:34 +0000198 code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
199 authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
Inseob Kimb2519c52022-04-14 02:10:09 +0900200 for extra_apk in &verified_data.extra_apks_data {
Andrew Scullb2f44472022-01-21 14:41:34 +0000201 code_hash_ctx.update(extra_apk.root_hash.as_ref());
202 authority_hash_ctx.update(extra_apk.pubkey.as_ref());
203 }
Inseob Kimb2519c52022-04-14 02:10:09 +0900204 for apex in &verified_data.apex_data {
Andrew Scullb2f44472022-01-21 14:41:34 +0000205 code_hash_ctx.update(apex.root_digest.as_ref());
206 authority_hash_ctx.update(apex.public_key.as_ref());
207 }
Andrew Sculla0d1b1a2022-05-24 19:32:47 +0000208 let code_hash = code_hash_ctx.finish();
209 let authority_hash = authority_hash_ctx.finish();
Andrew Scullb2f44472022-01-21 14:41:34 +0000210
211 // {
212 // -70002: "Microdroid payload",
213 // -71000: payload_config_path
214 // }
215 let mut config_desc = vec![
216 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72, 0x6f,
217 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01, 0x15, 0x57,
218 ];
219 let config_path_bytes = payload_config_path.as_bytes();
220 encode_header(3, config_path_bytes.len().try_into().unwrap(), &mut config_desc)?;
221 config_desc.extend_from_slice(config_path_bytes);
222
Andrew Scull65ddfc42022-02-14 21:03:58 +0000223 // Check app debuggability, conervatively assuming it is debuggable
224 let app_debuggable = system_properties::read_bool(APP_DEBUGGABLE_PROP, true)?;
225
Andrew Scullb2f44472022-01-21 14:41:34 +0000226 // Send the details to diced
227 let diced =
228 wait_for_interface::<dyn IDiceMaintenance>("android.security.dice.IDiceMaintenance")
229 .context("IDiceMaintenance service not found")?;
230 diced
231 .demoteSelf(&[InputValues {
232 codeHash: code_hash,
233 config: Config { desc: config_desc },
234 authorityHash: authority_hash,
235 authorityDescriptor: None,
Andrew Scull65ddfc42022-02-14 21:03:58 +0000236 mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
Inseob Kimb2519c52022-04-14 02:10:09 +0900237 hidden: verified_data.salt.clone().try_into().unwrap(),
Andrew Scullb2f44472022-01-21 14:41:34 +0000238 }])
239 .context("IDiceMaintenance::demoteSelf failed")?;
240 Ok(())
241}
242
Andrew Scullab72ec52022-03-14 09:10:52 +0000243fn is_strict_boot() -> bool {
244 Path::new(AVF_STRICT_BOOT).exists()
245}
246
247fn is_new_instance() -> bool {
248 Path::new(AVF_NEW_INSTANCE).exists()
249}
250
Inseob Kime379e7d2022-07-22 18:55:18 +0900251fn is_verified_boot() -> bool {
252 !Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
253}
254
Jooyung Han5c6d4172021-12-06 14:17:52 +0900255fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
Jooyung Han311b1202021-09-14 22:00:16 +0900256 let metadata = load_metadata().context("Failed to load payload metadata")?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900257
Jooyung Han311b1202021-09-14 22:00:16 +0900258 let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900259 let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900260
Andrew Scullab72ec52022-03-14 09:10:52 +0000261 if is_strict_boot() {
262 // Provisioning must happen on the first boot and never again.
263 if is_new_instance() {
264 ensure!(
265 saved_data.is_none(),
266 MicrodroidError::InvalidConfig("Found instance data on first boot.".to_string())
267 );
268 } else {
269 ensure!(
270 saved_data.is_some(),
271 MicrodroidError::InvalidConfig("Instance data not found.".to_string())
272 );
273 };
274 }
275
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900276 // Verify the payload before using it.
Inseob Kim11f40d02022-06-13 17:16:00 +0900277 let verified_data = verify_payload(&metadata, saved_data.as_ref())
278 .context("Payload verification failed")
279 .map_err(|e| MicrodroidError::PayloadVerificationFailed(e.to_string()))?;
Inseob Kime379e7d2022-07-22 18:55:18 +0900280
281 // In case identity is ignored (by debug policy), we should reuse existing payload data, even
282 // when the payload is changed. This is to keep the derived secret same as before.
283 let verified_data = if let Some(saved_data) = saved_data {
284 if !is_verified_boot() {
285 if saved_data != verified_data {
286 info!("Detected an update of the payload, but continue (regarding debug policy)")
287 }
288 } else {
289 ensure!(
290 saved_data == verified_data,
291 MicrodroidError::PayloadChanged(String::from(
292 "Detected an update of the payload which isn't supported yet."
293 ))
294 );
295 info!("Saved data is verified.");
296 }
297 saved_data
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900298 } else {
Jooyung Han7a343f92021-09-08 22:53:11 +0900299 info!("Saving verified data.");
300 instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
Inseob Kime379e7d2022-07-22 18:55:18 +0900301 verified_data
302 };
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900303
Alan Stokes0d1ef782022-09-27 13:46:35 +0100304 let payload_config_path = match metadata.payload {
305 Some(PayloadMetadata::config_path(p)) => p,
306 _ => bail!("Unsupported payload config"),
307 };
308
Inseob Kimb2519c52022-04-14 02:10:09 +0900309 // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
310 info!("DICE derivation for payload");
Alan Stokes0d1ef782022-09-27 13:46:35 +0100311 dice_derivation(&verified_data, &payload_config_path)?;
Inseob Kimb2519c52022-04-14 02:10:09 +0900312
Jooyung Hana6d11eb2021-09-10 11:48:05 +0900313 // Before reading a file from the APK, start zipfuse
Andrew Scullcc339a12022-07-04 12:44:19 +0000314 let noexec = false;
Inseob Kim217038e2021-11-25 11:15:06 +0900315 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000316 noexec,
Inseob Kim217038e2021-11-25 11:15:06 +0900317 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
318 Path::new("/dev/block/mapper/microdroid-apk"),
319 Path::new("/mnt/apk"),
320 )
321 .context("Failed to run zipfuse")?;
Jiyong Park21ce2c52021-08-28 02:32:17 +0900322
Jooyung Han5c6d4172021-12-06 14:17:52 +0900323 ensure!(
Alan Stokes0d1ef782022-09-27 13:46:35 +0100324 !payload_config_path.is_empty(),
Jooyung Han5c6d4172021-12-06 14:17:52 +0900325 MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
326 );
Inseob Kim197748b2021-12-01 19:49:00 +0900327
Alan Stokes0d1ef782022-09-27 13:46:35 +0100328 let config = load_config(Path::new(&payload_config_path))?;
Shikha Panwar6f03c942022-04-13 20:26:50 +0000329
Alan Stokes01b3ef02022-09-22 17:43:24 +0100330 let task = config
331 .task
332 .as_ref()
333 .ok_or_else(|| MicrodroidError::InvalidConfig("No task in VM config".to_string()))?;
334
Inseob Kim197748b2021-12-01 19:49:00 +0900335 if config.extra_apks.len() != verified_data.extra_apks_data.len() {
336 return Err(anyhow!(
337 "config expects {} extra apks, but found only {}",
338 config.extra_apks.len(),
339 verified_data.extra_apks_data.len()
340 ));
341 }
342 mount_extra_apks(&config)?;
Jooyung Han634e2d72021-06-10 16:27:38 +0900343
Jooyung Han5c6d4172021-12-06 14:17:52 +0900344 // Wait until apex config is done. (e.g. linker configuration for apexes)
345 // TODO(jooyung): wait until sys.boot_completed?
346 wait_for_apex_config_done()?;
347
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900348 // Start tombstone_transmit if enabled
349 if config.export_tombstones {
Alan Stokes01b3ef02022-09-22 17:43:24 +0100350 control_service("start", "tombstone_transmit")?;
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900351 } else {
Alan Stokes01b3ef02022-09-22 17:43:24 +0100352 control_service("stop", "tombstoned")?;
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900353 }
354
Alan Stokes01b3ef02022-09-22 17:43:24 +0100355 // Start authfs if enabled
356 if config.enable_authfs {
357 control_service("start", "authfs_service")?;
358 }
359
Victor Hsieh8d006ba2022-05-02 09:42:02 -0700360 system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
Alan Stokes01b3ef02022-09-22 17:43:24 +0100361 exec_task(task, service)
362}
363
364fn control_service(action: &str, service: &str) -> Result<()> {
365 system_properties::write(&format!("ctl.{}", action), service)
366 .with_context(|| format!("Failed to {} {}", action, service))
Jooyung Han347d9f22021-05-28 00:05:14 +0900367}
368
Inseob Kim217038e2021-11-25 11:15:06 +0900369struct ApkDmverityArgument<'a> {
370 apk: &'a str,
371 idsig: &'a str,
372 name: &'a str,
Inseob Kim197748b2021-12-01 19:49:00 +0900373 saved_root_hash: Option<&'a RootHash>,
Inseob Kim217038e2021-11-25 11:15:06 +0900374}
375
376fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
377 let mut cmd = Command::new(APKDMVERITY_BIN);
378
379 cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
380
381 for argument in args {
382 cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
Inseob Kim197748b2021-12-01 19:49:00 +0900383 if let Some(root_hash) = argument.saved_root_hash {
384 cmd.arg(&to_hex_string(root_hash));
385 } else {
386 cmd.arg("none");
387 }
Inseob Kim217038e2021-11-25 11:15:06 +0900388 }
389
390 cmd.spawn().context("Spawn apkdmverity")
391}
392
Andrew Scullcc339a12022-07-04 12:44:19 +0000393fn run_zipfuse(noexec: bool, option: &str, zip_path: &Path, mount_dir: &Path) -> Result<Child> {
394 let mut cmd = Command::new(ZIPFUSE_BIN);
395 if noexec {
396 cmd.arg("--noexec");
397 }
398 cmd.arg("-o")
Inseob Kim217038e2021-11-25 11:15:06 +0900399 .arg(option)
400 .arg(zip_path)
401 .arg(mount_dir)
402 .stdin(Stdio::null())
403 .stdout(Stdio::null())
404 .stderr(Stdio::null())
405 .spawn()
406 .context("Spawn zipfuse")
407}
408
Inseob Kime379e7d2022-07-22 18:55:18 +0900409fn write_apex_payload_data(
410 saved_data: Option<&MicrodroidData>,
411 apex_data_from_payload: &[ApexData],
412) -> Result<()> {
413 if let Some(saved_apex_data) = saved_data.map(|d| &d.apex_data) {
414 // We don't support APEX updates. (assuming that update will change root digest)
415 ensure!(
416 saved_apex_data == apex_data_from_payload,
417 MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
418 );
419 let apex_metadata = to_metadata(apex_data_from_payload);
420 // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
421 // metadata instead of the default one (/dev/block/by-name/payload-metadata)
422 OpenOptions::new()
423 .create_new(true)
424 .write(true)
425 .open("/apex/vm-payload-metadata")
426 .context("Failed to open /apex/vm-payload-metadata")
427 .and_then(|f| write_metadata(&apex_metadata, f))?;
428 }
429 Ok(())
430}
431
Jooyung Han7a343f92021-09-08 22:53:11 +0900432// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
433// when the root_hash values from the idsig file and the instance disk are different. This function
434// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
435// saved to the instance disk.
436fn verify_payload(
437 metadata: &Metadata,
438 saved_data: Option<&MicrodroidData>,
439) -> Result<MicrodroidData> {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900440 let start_time = SystemTime::now();
441
Inseob Kim197748b2021-12-01 19:49:00 +0900442 // Verify main APK
Jooyung Han7a343f92021-09-08 22:53:11 +0900443 let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
Inseob Kim197748b2021-12-01 19:49:00 +0900444 let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
Jiyong Parkf7dea252021-09-08 01:42:54 +0900445 let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900446
Jiyong Parkf7dea252021-09-08 01:42:54 +0900447 // 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 +0900448 // instead of the value read from the idsig file.
Inseob Kim197748b2021-12-01 19:49:00 +0900449 let main_apk_argument = {
450 ApkDmverityArgument {
451 apk: MAIN_APK_PATH,
452 idsig: MAIN_APK_IDSIG_PATH,
453 name: MAIN_APK_DEVICE_NAME,
454 saved_root_hash: if root_hash_trustful {
455 Some(root_hash_from_idsig.as_ref())
456 } else {
457 None
458 },
459 }
460 };
461 let mut apkdmverity_arguments = vec![main_apk_argument];
462
463 // Verify extra APKs
464 // For now, we can't read the payload config, so glob APKs and idsigs.
465 // Later, we'll see if it matches with the payload config.
466
467 // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
468 // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
469 let extra_apks =
470 sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
471 let extra_idsigs =
472 sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
473 if extra_apks.len() != extra_idsigs.len() {
474 return Err(anyhow!(
475 "Extra apks/idsigs mismatch: {} apks but {} idsigs",
476 extra_apks.len(),
477 extra_idsigs.len()
478 ));
479 }
480 let extra_apks_count = extra_apks.len();
481
482 let (extra_apk_names, extra_root_hashes_from_idsig): (Vec<_>, Vec<_>) = extra_idsigs
483 .iter()
484 .enumerate()
485 .map(|(i, extra_idsig)| {
486 (
487 format!("extra-apk-{}", i),
Alice Wang89cff012022-09-26 10:05:16 +0000488 get_apk_root_hash_from_idsig(extra_idsig)
Inseob Kim197748b2021-12-01 19:49:00 +0900489 .expect("Can't find root hash from extra idsig"),
490 )
491 })
492 .unzip();
493
494 let saved_extra_root_hashes: Vec<_> = saved_data
495 .map(|d| d.extra_apks_data.iter().map(|apk_data| &apk_data.root_hash).collect())
496 .unwrap_or_else(Vec::new);
497 let extra_root_hashes_trustful: Vec<_> = extra_root_hashes_from_idsig
498 .iter()
499 .enumerate()
500 .map(|(i, root_hash_from_idsig)| {
501 saved_extra_root_hashes.get(i).copied() == Some(root_hash_from_idsig)
502 })
503 .collect();
504
505 for i in 0..extra_apks_count {
506 apkdmverity_arguments.push({
507 ApkDmverityArgument {
508 apk: extra_apks[i].to_str().unwrap(),
509 idsig: extra_idsigs[i].to_str().unwrap(),
510 name: &extra_apk_names[i],
511 saved_root_hash: if extra_root_hashes_trustful[i] {
512 Some(&extra_root_hashes_from_idsig[i])
513 } else {
514 None
515 },
516 }
517 });
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900518 }
519
520 // Start apkdmverity and wait for the dm-verify block
Inseob Kim197748b2021-12-01 19:49:00 +0900521 let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900522
Jooyung Hanc8deb472021-09-13 13:48:25 +0900523 // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
524 // APEX payload.
Jooyung Han7a343f92021-09-08 22:53:11 +0900525 let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
Inseob Kime379e7d2022-07-22 18:55:18 +0900526
527 // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
528 // Skip writing it if the debug policy ignoring identity is on
529 if is_verified_boot() {
530 write_apex_payload_data(saved_data, &apex_data_from_payload)?;
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900531 }
Inseob Kime379e7d2022-07-22 18:55:18 +0900532
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900533 // Start apexd to activate APEXes
534 system_properties::write("ctl.start", "apexd-vm")?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900535
Inseob Kim217038e2021-11-25 11:15:06 +0900536 // TODO(inseob): add timeout
537 apkdmverity_child.wait()?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900538
Jiyong Parkf7dea252021-09-08 01:42:54 +0900539 // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900540 // 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 +0900541 // 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 +0900542 // of the VM or APK was updated in the host.
543 // TODO(jooyung): consider multithreading to make this faster
Inseob Kim197748b2021-12-01 19:49:00 +0900544 let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
545 let extra_apks_data = extra_root_hashes_from_idsig
546 .into_iter()
547 .enumerate()
548 .map(|(i, extra_root_hash)| {
549 let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
550 let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
551 Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
552 })
553 .collect::<Result<Vec<_>>>()?;
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900554
555 info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
556
Andrew Scull34916a72022-01-30 21:34:24 +0000557 // Use the salt from a verified instance, or generate a salt for a new instance.
558 let salt = if let Some(saved_data) = saved_data {
559 saved_data.salt.clone()
560 } else {
561 let mut salt = vec![0u8; 64];
562 salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
563 salt
564 };
565
Jiyong Parkf7dea252021-09-08 01:42:54 +0900566 // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
567 // fully verifying the APK or by comparing it with the saved root_hash.
Jooyung Han7a343f92021-09-08 22:53:11 +0900568 Ok(MicrodroidData {
Andrew Scull34916a72022-01-30 21:34:24 +0000569 salt,
Inseob Kim197748b2021-12-01 19:49:00 +0900570 apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
571 extra_apks_data,
Jooyung Han7a343f92021-09-08 22:53:11 +0900572 apex_data: apex_data_from_payload,
573 })
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900574}
575
Inseob Kim197748b2021-12-01 19:49:00 +0900576fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
577 // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
578 for i in 0..config.extra_apks.len() {
579 let mount_dir = format!("/mnt/extra-apk/{}", i);
580 create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
581
582 // don't wait, just detach
Andrew Scullcc339a12022-07-04 12:44:19 +0000583 let noexec = true;
Inseob Kim197748b2021-12-01 19:49:00 +0900584 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000585 noexec,
Inseob Kim197748b2021-12-01 19:49:00 +0900586 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
587 Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
588 Path::new(&mount_dir),
589 )
590 .context("Failed to zipfuse extra apks")?;
591 }
592
593 Ok(())
594}
595
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900596// Waits until linker config is generated
597fn wait_for_apex_config_done() -> Result<()> {
598 let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
599 loop {
600 prop.wait()?;
Andrew Scull4b5369f2022-01-28 13:47:20 +0000601 if system_properties::read_bool(APEX_CONFIG_DONE_PROP, false)? {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900602 break;
603 }
604 }
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900605 Ok(())
606}
607
Alice Wang89cff012022-09-26 10:05:16 +0000608fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
609 Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
Jiyong Park21ce2c52021-08-28 02:32:17 +0900610}
611
Inseob Kim197748b2021-12-01 19:49:00 +0900612fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
613 if !root_hash_trustful {
614 verify(apk).context(MicrodroidError::PayloadVerificationFailed(format!(
615 "failed to verify {}",
616 apk
617 )))
618 } else {
619 get_public_key_der(apk)
620 }
621}
622
Jooyung Han634e2d72021-06-10 16:27:38 +0900623fn load_config(path: &Path) -> Result<VmPayloadConfig> {
624 info!("loading config from {:?}...", path);
625 let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
626 Ok(serde_json::from_reader(file)?)
627}
628
Jiyong Park202856e2022-08-22 16:04:26 +0900629/// Loads the crashkernel into memory using kexec if the VM is loaded with `crashkernel=' parameter
630/// in the cmdline.
631fn load_crashkernel_if_supported() -> Result<()> {
632 let supported = std::fs::read_to_string("/proc/cmdline")?.contains(" crashkernel=");
633 info!("ramdump supported: {}", supported);
634 if supported {
635 let status = Command::new("/system/bin/kexec_load").status()?;
636 if !status.success() {
637 return Err(anyhow!("Failed to load crashkernel: {:?}", status));
638 }
639 }
640 Ok(())
641}
642
Jiyong Park8611a6c2021-07-09 18:17:44 +0900643/// Executes the given task. Stdout of the task is piped into the vsock stream to the
644/// virtualizationservice in the host side.
Jooyung Han5c6d4172021-12-06 14:17:52 +0900645fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
Jiyong Park8611a6c2021-07-09 18:17:44 +0900646 info!("executing main task {:?}...", task);
Inseob Kim86ca0162021-10-20 02:21:02 +0000647 let mut command = build_command(task)?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900648
649 info!("notifying payload started");
Inseob Kimc7d28c72021-10-25 14:28:10 +0000650 service.notifyPayloadStarted()?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900651
Inseob Kim86ca0162021-10-20 02:21:02 +0000652 let exit_status = command.spawn()?.wait()?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900653 exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
Jooyung Han347d9f22021-05-28 00:05:14 +0900654}
Jooyung Han634e2d72021-06-10 16:27:38 +0900655
656fn build_command(task: &Task) -> Result<Command> {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900657 const VMADDR_CID_HOST: u32 = 2;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900658
659 let mut command = match task.type_ {
Jooyung Han634e2d72021-06-10 16:27:38 +0900660 TaskType::Executable => {
661 let mut command = Command::new(&task.command);
662 command.args(&task.args);
663 command
664 }
665 TaskType::MicrodroidLauncher => {
666 let mut command = Command::new("/system/bin/microdroid_launcher");
667 command.arg(find_library_path(&task.command)?).args(&task.args);
668 command
669 }
Inseob Kim7f61fe72021-08-20 20:50:47 +0900670 };
671
Inseob Kimd0587562021-09-01 21:27:32 +0900672 match VsockStream::connect_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32) {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900673 Ok(stream) => {
674 // SAFETY: the ownership of the underlying file descriptor is transferred from stream
675 // to the file object, and then into the Command object. When the command is finished,
676 // the file descriptor is closed.
677 let file = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
678 command
679 .stdin(Stdio::from(file.try_clone()?))
680 .stdout(Stdio::from(file.try_clone()?))
681 .stderr(Stdio::from(file));
682 }
683 Err(e) => {
684 error!("failed to connect to virtualization service: {}", e);
685 // Don't fail hard here. Even if we failed to connect to the virtualizationservice,
686 // we keep executing the task. This can happen if the owner of the VM doesn't register
687 // callback to accept the stream. Use /dev/null as the stream so that the task can
688 // make progress without waiting for someone to consume the output.
689 command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
690 }
691 }
692
693 Ok(command)
Jooyung Han634e2d72021-06-10 16:27:38 +0900694}
695
696fn find_library_path(name: &str) -> Result<String> {
697 let mut watcher = PropertyWatcher::new("ro.product.cpu.abilist")?;
698 let value = watcher.read(|_name, value| Ok(value.trim().to_string()))?;
699 let abi = value.split(',').next().ok_or_else(|| anyhow!("no abilist"))?;
700 let path = format!("/mnt/apk/lib/{}/{}", abi, name);
701
702 let metadata = fs::metadata(&path)?;
703 if !metadata.is_file() {
704 bail!("{} is not a file", &path);
705 }
706
707 Ok(path)
708}
Jiyong Park21ce2c52021-08-28 02:32:17 +0900709
710fn to_hex_string(buf: &[u8]) -> String {
711 buf.iter().map(|b| format!("{:02X}", b)).collect()
712}