blob: 30521f6383ac5fca3462d00f2d1bf1579ae721eb [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};
Jooyung Han4a9b3bf2021-09-10 17:19:00 +090037use microdroid_metadata::{write_metadata, Metadata};
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
Inseob Kimb2519c52022-04-14 02:10:09 +0900304 // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
305 info!("DICE derivation for payload");
306 dice_derivation(&verified_data, &metadata.payload_config_path)?;
307
Jooyung Hana6d11eb2021-09-10 11:48:05 +0900308 // Before reading a file from the APK, start zipfuse
Andrew Scullcc339a12022-07-04 12:44:19 +0000309 let noexec = false;
Inseob Kim217038e2021-11-25 11:15:06 +0900310 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000311 noexec,
Inseob Kim217038e2021-11-25 11:15:06 +0900312 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
313 Path::new("/dev/block/mapper/microdroid-apk"),
314 Path::new("/mnt/apk"),
315 )
316 .context("Failed to run zipfuse")?;
Jiyong Park21ce2c52021-08-28 02:32:17 +0900317
Jooyung Han5c6d4172021-12-06 14:17:52 +0900318 ensure!(
319 !metadata.payload_config_path.is_empty(),
320 MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
321 );
Inseob Kim197748b2021-12-01 19:49:00 +0900322
Jooyung Han5c6d4172021-12-06 14:17:52 +0900323 let config = load_config(Path::new(&metadata.payload_config_path))?;
Shikha Panwar6f03c942022-04-13 20:26:50 +0000324
Alan Stokes01b3ef02022-09-22 17:43:24 +0100325 let task = config
326 .task
327 .as_ref()
328 .ok_or_else(|| MicrodroidError::InvalidConfig("No task in VM config".to_string()))?;
329
Inseob Kim197748b2021-12-01 19:49:00 +0900330 if config.extra_apks.len() != verified_data.extra_apks_data.len() {
331 return Err(anyhow!(
332 "config expects {} extra apks, but found only {}",
333 config.extra_apks.len(),
334 verified_data.extra_apks_data.len()
335 ));
336 }
337 mount_extra_apks(&config)?;
Jooyung Han634e2d72021-06-10 16:27:38 +0900338
Jooyung Han5c6d4172021-12-06 14:17:52 +0900339 // Wait until apex config is done. (e.g. linker configuration for apexes)
340 // TODO(jooyung): wait until sys.boot_completed?
341 wait_for_apex_config_done()?;
342
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900343 // Start tombstone_transmit if enabled
344 if config.export_tombstones {
Alan Stokes01b3ef02022-09-22 17:43:24 +0100345 control_service("start", "tombstone_transmit")?;
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900346 } else {
Alan Stokes01b3ef02022-09-22 17:43:24 +0100347 control_service("stop", "tombstoned")?;
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900348 }
349
Alan Stokes01b3ef02022-09-22 17:43:24 +0100350 // Start authfs if enabled
351 if config.enable_authfs {
352 control_service("start", "authfs_service")?;
353 }
354
Victor Hsieh8d006ba2022-05-02 09:42:02 -0700355 system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
Alan Stokes01b3ef02022-09-22 17:43:24 +0100356 exec_task(task, service)
357}
358
359fn control_service(action: &str, service: &str) -> Result<()> {
360 system_properties::write(&format!("ctl.{}", action), service)
361 .with_context(|| format!("Failed to {} {}", action, service))
Jooyung Han347d9f22021-05-28 00:05:14 +0900362}
363
Inseob Kim217038e2021-11-25 11:15:06 +0900364struct ApkDmverityArgument<'a> {
365 apk: &'a str,
366 idsig: &'a str,
367 name: &'a str,
Inseob Kim197748b2021-12-01 19:49:00 +0900368 saved_root_hash: Option<&'a RootHash>,
Inseob Kim217038e2021-11-25 11:15:06 +0900369}
370
371fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
372 let mut cmd = Command::new(APKDMVERITY_BIN);
373
374 cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
375
376 for argument in args {
377 cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
Inseob Kim197748b2021-12-01 19:49:00 +0900378 if let Some(root_hash) = argument.saved_root_hash {
379 cmd.arg(&to_hex_string(root_hash));
380 } else {
381 cmd.arg("none");
382 }
Inseob Kim217038e2021-11-25 11:15:06 +0900383 }
384
385 cmd.spawn().context("Spawn apkdmverity")
386}
387
Andrew Scullcc339a12022-07-04 12:44:19 +0000388fn run_zipfuse(noexec: bool, option: &str, zip_path: &Path, mount_dir: &Path) -> Result<Child> {
389 let mut cmd = Command::new(ZIPFUSE_BIN);
390 if noexec {
391 cmd.arg("--noexec");
392 }
393 cmd.arg("-o")
Inseob Kim217038e2021-11-25 11:15:06 +0900394 .arg(option)
395 .arg(zip_path)
396 .arg(mount_dir)
397 .stdin(Stdio::null())
398 .stdout(Stdio::null())
399 .stderr(Stdio::null())
400 .spawn()
401 .context("Spawn zipfuse")
402}
403
Inseob Kime379e7d2022-07-22 18:55:18 +0900404fn write_apex_payload_data(
405 saved_data: Option<&MicrodroidData>,
406 apex_data_from_payload: &[ApexData],
407) -> Result<()> {
408 if let Some(saved_apex_data) = saved_data.map(|d| &d.apex_data) {
409 // We don't support APEX updates. (assuming that update will change root digest)
410 ensure!(
411 saved_apex_data == apex_data_from_payload,
412 MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
413 );
414 let apex_metadata = to_metadata(apex_data_from_payload);
415 // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
416 // metadata instead of the default one (/dev/block/by-name/payload-metadata)
417 OpenOptions::new()
418 .create_new(true)
419 .write(true)
420 .open("/apex/vm-payload-metadata")
421 .context("Failed to open /apex/vm-payload-metadata")
422 .and_then(|f| write_metadata(&apex_metadata, f))?;
423 }
424 Ok(())
425}
426
Jooyung Han7a343f92021-09-08 22:53:11 +0900427// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
428// when the root_hash values from the idsig file and the instance disk are different. This function
429// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
430// saved to the instance disk.
431fn verify_payload(
432 metadata: &Metadata,
433 saved_data: Option<&MicrodroidData>,
434) -> Result<MicrodroidData> {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900435 let start_time = SystemTime::now();
436
Inseob Kim197748b2021-12-01 19:49:00 +0900437 // Verify main APK
Jooyung Han7a343f92021-09-08 22:53:11 +0900438 let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
Inseob Kim197748b2021-12-01 19:49:00 +0900439 let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
Jiyong Parkf7dea252021-09-08 01:42:54 +0900440 let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900441
Jiyong Parkf7dea252021-09-08 01:42:54 +0900442 // 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 +0900443 // instead of the value read from the idsig file.
Inseob Kim197748b2021-12-01 19:49:00 +0900444 let main_apk_argument = {
445 ApkDmverityArgument {
446 apk: MAIN_APK_PATH,
447 idsig: MAIN_APK_IDSIG_PATH,
448 name: MAIN_APK_DEVICE_NAME,
449 saved_root_hash: if root_hash_trustful {
450 Some(root_hash_from_idsig.as_ref())
451 } else {
452 None
453 },
454 }
455 };
456 let mut apkdmverity_arguments = vec![main_apk_argument];
457
458 // Verify extra APKs
459 // For now, we can't read the payload config, so glob APKs and idsigs.
460 // Later, we'll see if it matches with the payload config.
461
462 // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
463 // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
464 let extra_apks =
465 sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
466 let extra_idsigs =
467 sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
468 if extra_apks.len() != extra_idsigs.len() {
469 return Err(anyhow!(
470 "Extra apks/idsigs mismatch: {} apks but {} idsigs",
471 extra_apks.len(),
472 extra_idsigs.len()
473 ));
474 }
475 let extra_apks_count = extra_apks.len();
476
477 let (extra_apk_names, extra_root_hashes_from_idsig): (Vec<_>, Vec<_>) = extra_idsigs
478 .iter()
479 .enumerate()
480 .map(|(i, extra_idsig)| {
481 (
482 format!("extra-apk-{}", i),
Alice Wang89cff012022-09-26 10:05:16 +0000483 get_apk_root_hash_from_idsig(extra_idsig)
Inseob Kim197748b2021-12-01 19:49:00 +0900484 .expect("Can't find root hash from extra idsig"),
485 )
486 })
487 .unzip();
488
489 let saved_extra_root_hashes: Vec<_> = saved_data
490 .map(|d| d.extra_apks_data.iter().map(|apk_data| &apk_data.root_hash).collect())
491 .unwrap_or_else(Vec::new);
492 let extra_root_hashes_trustful: Vec<_> = extra_root_hashes_from_idsig
493 .iter()
494 .enumerate()
495 .map(|(i, root_hash_from_idsig)| {
496 saved_extra_root_hashes.get(i).copied() == Some(root_hash_from_idsig)
497 })
498 .collect();
499
500 for i in 0..extra_apks_count {
501 apkdmverity_arguments.push({
502 ApkDmverityArgument {
503 apk: extra_apks[i].to_str().unwrap(),
504 idsig: extra_idsigs[i].to_str().unwrap(),
505 name: &extra_apk_names[i],
506 saved_root_hash: if extra_root_hashes_trustful[i] {
507 Some(&extra_root_hashes_from_idsig[i])
508 } else {
509 None
510 },
511 }
512 });
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900513 }
514
515 // Start apkdmverity and wait for the dm-verify block
Inseob Kim197748b2021-12-01 19:49:00 +0900516 let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900517
Jooyung Hanc8deb472021-09-13 13:48:25 +0900518 // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
519 // APEX payload.
Jooyung Han7a343f92021-09-08 22:53:11 +0900520 let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
Inseob Kime379e7d2022-07-22 18:55:18 +0900521
522 // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
523 // Skip writing it if the debug policy ignoring identity is on
524 if is_verified_boot() {
525 write_apex_payload_data(saved_data, &apex_data_from_payload)?;
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900526 }
Inseob Kime379e7d2022-07-22 18:55:18 +0900527
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900528 // Start apexd to activate APEXes
529 system_properties::write("ctl.start", "apexd-vm")?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900530
Inseob Kim217038e2021-11-25 11:15:06 +0900531 // TODO(inseob): add timeout
532 apkdmverity_child.wait()?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900533
Jiyong Parkf7dea252021-09-08 01:42:54 +0900534 // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900535 // 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 +0900536 // 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 +0900537 // of the VM or APK was updated in the host.
538 // TODO(jooyung): consider multithreading to make this faster
Inseob Kim197748b2021-12-01 19:49:00 +0900539 let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
540 let extra_apks_data = extra_root_hashes_from_idsig
541 .into_iter()
542 .enumerate()
543 .map(|(i, extra_root_hash)| {
544 let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
545 let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
546 Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
547 })
548 .collect::<Result<Vec<_>>>()?;
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900549
550 info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
551
Andrew Scull34916a72022-01-30 21:34:24 +0000552 // Use the salt from a verified instance, or generate a salt for a new instance.
553 let salt = if let Some(saved_data) = saved_data {
554 saved_data.salt.clone()
555 } else {
556 let mut salt = vec![0u8; 64];
557 salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
558 salt
559 };
560
Jiyong Parkf7dea252021-09-08 01:42:54 +0900561 // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
562 // fully verifying the APK or by comparing it with the saved root_hash.
Jooyung Han7a343f92021-09-08 22:53:11 +0900563 Ok(MicrodroidData {
Andrew Scull34916a72022-01-30 21:34:24 +0000564 salt,
Inseob Kim197748b2021-12-01 19:49:00 +0900565 apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
566 extra_apks_data,
Jooyung Han7a343f92021-09-08 22:53:11 +0900567 apex_data: apex_data_from_payload,
568 })
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900569}
570
Inseob Kim197748b2021-12-01 19:49:00 +0900571fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
572 // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
573 for i in 0..config.extra_apks.len() {
574 let mount_dir = format!("/mnt/extra-apk/{}", i);
575 create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
576
577 // don't wait, just detach
Andrew Scullcc339a12022-07-04 12:44:19 +0000578 let noexec = true;
Inseob Kim197748b2021-12-01 19:49:00 +0900579 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000580 noexec,
Inseob Kim197748b2021-12-01 19:49:00 +0900581 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
582 Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
583 Path::new(&mount_dir),
584 )
585 .context("Failed to zipfuse extra apks")?;
586 }
587
588 Ok(())
589}
590
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900591// Waits until linker config is generated
592fn wait_for_apex_config_done() -> Result<()> {
593 let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
594 loop {
595 prop.wait()?;
Andrew Scull4b5369f2022-01-28 13:47:20 +0000596 if system_properties::read_bool(APEX_CONFIG_DONE_PROP, false)? {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900597 break;
598 }
599 }
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900600 Ok(())
601}
602
Alice Wang89cff012022-09-26 10:05:16 +0000603fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
604 Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
Jiyong Park21ce2c52021-08-28 02:32:17 +0900605}
606
Inseob Kim197748b2021-12-01 19:49:00 +0900607fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
608 if !root_hash_trustful {
609 verify(apk).context(MicrodroidError::PayloadVerificationFailed(format!(
610 "failed to verify {}",
611 apk
612 )))
613 } else {
614 get_public_key_der(apk)
615 }
616}
617
Jooyung Han634e2d72021-06-10 16:27:38 +0900618fn load_config(path: &Path) -> Result<VmPayloadConfig> {
619 info!("loading config from {:?}...", path);
620 let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
621 Ok(serde_json::from_reader(file)?)
622}
623
Jiyong Park202856e2022-08-22 16:04:26 +0900624/// Loads the crashkernel into memory using kexec if the VM is loaded with `crashkernel=' parameter
625/// in the cmdline.
626fn load_crashkernel_if_supported() -> Result<()> {
627 let supported = std::fs::read_to_string("/proc/cmdline")?.contains(" crashkernel=");
628 info!("ramdump supported: {}", supported);
629 if supported {
630 let status = Command::new("/system/bin/kexec_load").status()?;
631 if !status.success() {
632 return Err(anyhow!("Failed to load crashkernel: {:?}", status));
633 }
634 }
635 Ok(())
636}
637
Jiyong Park8611a6c2021-07-09 18:17:44 +0900638/// Executes the given task. Stdout of the task is piped into the vsock stream to the
639/// virtualizationservice in the host side.
Jooyung Han5c6d4172021-12-06 14:17:52 +0900640fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
Jiyong Park8611a6c2021-07-09 18:17:44 +0900641 info!("executing main task {:?}...", task);
Inseob Kim86ca0162021-10-20 02:21:02 +0000642 let mut command = build_command(task)?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900643
644 info!("notifying payload started");
Inseob Kimc7d28c72021-10-25 14:28:10 +0000645 service.notifyPayloadStarted()?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900646
Inseob Kim86ca0162021-10-20 02:21:02 +0000647 let exit_status = command.spawn()?.wait()?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900648 exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
Jooyung Han347d9f22021-05-28 00:05:14 +0900649}
Jooyung Han634e2d72021-06-10 16:27:38 +0900650
651fn build_command(task: &Task) -> Result<Command> {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900652 const VMADDR_CID_HOST: u32 = 2;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900653
654 let mut command = match task.type_ {
Jooyung Han634e2d72021-06-10 16:27:38 +0900655 TaskType::Executable => {
656 let mut command = Command::new(&task.command);
657 command.args(&task.args);
658 command
659 }
660 TaskType::MicrodroidLauncher => {
661 let mut command = Command::new("/system/bin/microdroid_launcher");
662 command.arg(find_library_path(&task.command)?).args(&task.args);
663 command
664 }
Inseob Kim7f61fe72021-08-20 20:50:47 +0900665 };
666
Inseob Kimd0587562021-09-01 21:27:32 +0900667 match VsockStream::connect_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32) {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900668 Ok(stream) => {
669 // SAFETY: the ownership of the underlying file descriptor is transferred from stream
670 // to the file object, and then into the Command object. When the command is finished,
671 // the file descriptor is closed.
672 let file = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
673 command
674 .stdin(Stdio::from(file.try_clone()?))
675 .stdout(Stdio::from(file.try_clone()?))
676 .stderr(Stdio::from(file));
677 }
678 Err(e) => {
679 error!("failed to connect to virtualization service: {}", e);
680 // Don't fail hard here. Even if we failed to connect to the virtualizationservice,
681 // we keep executing the task. This can happen if the owner of the VM doesn't register
682 // callback to accept the stream. Use /dev/null as the stream so that the task can
683 // make progress without waiting for someone to consume the output.
684 command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
685 }
686 }
687
688 Ok(command)
Jooyung Han634e2d72021-06-10 16:27:38 +0900689}
690
691fn find_library_path(name: &str) -> Result<String> {
692 let mut watcher = PropertyWatcher::new("ro.product.cpu.abilist")?;
693 let value = watcher.read(|_name, value| Ok(value.trim().to_string()))?;
694 let abi = value.split(',').next().ok_or_else(|| anyhow!("no abilist"))?;
695 let path = format!("/mnt/apk/lib/{}/{}", abi, name);
696
697 let metadata = fs::metadata(&path)?;
698 if !metadata.is_file() {
699 bail!("{} is not a file", &path);
700 }
701
702 Ok(path)
703}
Jiyong Park21ce2c52021-08-28 02:32:17 +0900704
705fn to_hex_string(buf: &[u8]) -> String {
706 buf.iter().map(|b| format!("{:02X}", b)).collect()
707}