blob: e4694e8b5784a952f8c02cc7aafd3a691a4a3a27 [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
Jiyong Parkf7dea252021-09-08 01:42:54 +090021use crate::instance::{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;
Jooyung Handd0a1732021-11-23 15:26:20 +090026use anyhow::{anyhow, bail, ensure, Context, Error, Result};
Jiyong Parka41535b2021-09-10 19:31:48 +090027use apkverify::{get_public_key_der, verify};
Inseob Kim1b95f2e2021-08-19 13:17:40 +090028use binder::unstable_api::{new_spibinder, AIBinder};
Andrew Scullb2f44472022-01-21 14:41:34 +000029use binder::{wait_for_interface, FromIBinder, Strong};
30use diced_utils::cbor::encode_header;
Inseob Kim197748b2021-12-01 19:49:00 +090031use glob::glob;
Jiyong Park21ce2c52021-08-28 02:32:17 +090032use idsig::V4Signature;
Inseob Kim197748b2021-12-01 19:49:00 +090033use itertools::sorted;
Andrew Scull684590f2022-01-27 16:14:26 +000034use log::{error, info};
Jooyung Han4a9b3bf2021-09-10 17:19:00 +090035use microdroid_metadata::{write_metadata, Metadata};
Jooyung Han634e2d72021-06-10 16:27:38 +090036use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
Andrew Sculla0d1b1a2022-05-24 19:32:47 +000037use openssl::sha::Sha512;
Jooyung Han4a9b3bf2021-09-10 17:19:00 +090038use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
Andrew Scull34916a72022-01-30 21:34:24 +000039use rand::Fill;
Jiyong Parkbb4a9872021-09-06 15:59:21 +090040use rustutils::system_properties;
Joel Galenson482704c2021-07-29 15:53:53 -070041use rustutils::system_properties::PropertyWatcher;
Andrew Scullb2f44472022-01-21 14:41:34 +000042use std::convert::TryInto;
Inseob Kim197748b2021-12-01 19:49:00 +090043use std::fs::{self, create_dir, File, OpenOptions};
Inseob Kim11f40d02022-06-13 17:16:00 +090044use std::io::Write;
Inseob Kimc7d28c72021-10-25 14:28:10 +000045use std::os::unix::io::{FromRawFd, IntoRawFd};
Jooyung Hanf48ceb42021-06-01 18:00:04 +090046use std::path::Path;
Inseob Kim217038e2021-11-25 11:15:06 +090047use std::process::{Child, Command, Stdio};
Jiyong Park8611a6c2021-07-09 18:17:44 +090048use std::str;
Jiyong Parkbb4a9872021-09-06 15:59:21 +090049use std::time::{Duration, SystemTime};
Jiyong Park8611a6c2021-07-09 18:17:44 +090050use vsock::VsockStream;
Jooyung Han634e2d72021-06-10 16:27:38 +090051
Inseob Kimd0587562021-09-01 21:27:32 +090052use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
Jooyung Han5c6d4172021-12-06 14:17:52 +090053 ERROR_PAYLOAD_CHANGED, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_PAYLOAD_INVALID_CONFIG, ERROR_UNKNOWN, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
Inseob Kimd0587562021-09-01 21:27:32 +090054};
Inseob Kim1b95f2e2021-08-19 13:17:40 +090055
Jooyung Han634e2d72021-06-10 16:27:38 +090056const 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";
Jooyung Han347d9f22021-05-28 00:05:14 +090067
Inseob Kim1b95f2e2021-08-19 13:17:40 +090068/// The CID representing the host VM
69const VMADDR_CID_HOST: u32 = 2;
70
Jiyong Parkbb4a9872021-09-06 15:59:21 +090071const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
Andrew Scull65ddfc42022-02-14 21:03:58 +000072const APP_DEBUGGABLE_PROP: &str = "ro.boot.microdroid.app_debuggable";
Jiyong Parkbb4a9872021-09-06 15:59:21 +090073
Inseob Kim11f40d02022-06-13 17:16:00 +090074// SYNC WITH virtualizationservice/src/crosvm.rs
75const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
76
Jooyung Handd0a1732021-11-23 15:26:20 +090077#[derive(thiserror::Error, Debug)]
78enum MicrodroidError {
Inseob Kim11f40d02022-06-13 17:16:00 +090079 #[error("Cannot connect to virtualization service: {0}")]
80 FailedToConnectToVirtualizationService(String),
Jooyung Handd0a1732021-11-23 15:26:20 +090081 #[error("Payload has changed: {0}")]
82 PayloadChanged(String),
83 #[error("Payload verification has failed: {0}")]
84 PayloadVerificationFailed(String),
Jooyung Han5c6d4172021-12-06 14:17:52 +090085 #[error("Payload config is invalid: {0}")]
86 InvalidConfig(String),
Jooyung Handd0a1732021-11-23 15:26:20 +090087}
88
89fn translate_error(err: &Error) -> (i32, String) {
90 if let Some(e) = err.downcast_ref::<MicrodroidError>() {
91 match e {
92 MicrodroidError::PayloadChanged(msg) => (ERROR_PAYLOAD_CHANGED, msg.to_string()),
93 MicrodroidError::PayloadVerificationFailed(msg) => {
94 (ERROR_PAYLOAD_VERIFICATION_FAILED, msg.to_string())
95 }
Jooyung Han5c6d4172021-12-06 14:17:52 +090096 MicrodroidError::InvalidConfig(msg) => (ERROR_PAYLOAD_INVALID_CONFIG, msg.to_string()),
Inseob Kim11f40d02022-06-13 17:16:00 +090097
98 // Connection failure won't be reported to VS; return the default value
99 MicrodroidError::FailedToConnectToVirtualizationService(msg) => {
100 (ERROR_UNKNOWN, msg.to_string())
101 }
Jooyung Handd0a1732021-11-23 15:26:20 +0900102 }
103 } else {
104 (ERROR_UNKNOWN, err.to_string())
105 }
106}
107
Inseob Kim11f40d02022-06-13 17:16:00 +0900108fn write_death_reason_to_serial(err: &Error) -> Result<()> {
109 let death_reason = if let Some(e) = err.downcast_ref::<MicrodroidError>() {
110 match e {
111 MicrodroidError::FailedToConnectToVirtualizationService(_) => {
112 "MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE"
113 }
114 MicrodroidError::PayloadChanged(_) => "MICRODROID_PAYLOAD_HAS_CHANGED",
115 MicrodroidError::PayloadVerificationFailed(_) => {
116 "MICRODROID_PAYLOAD_VERIFICATION_FAILED"
117 }
118 MicrodroidError::InvalidConfig(_) => "MICRODROID_INVALID_PAYLOAD_CONFIG",
119 }
120 } else {
121 "MICRODROID_UNKNOWN_RUNTIME_ERROR"
122 };
123
124 let death_reason_bytes = death_reason.as_bytes();
125 let mut sent_total = 0;
126 while sent_total < death_reason_bytes.len() {
127 // TODO(b/220071963): Sometimes, sending more than 16 bytes at once makes MM hang.
128 let begin = sent_total;
129 let end = std::cmp::min(begin.saturating_add(16), death_reason_bytes.len());
130 OpenOptions::new()
131 .read(false)
132 .write(true)
133 .open(FAILURE_SERIAL_DEVICE)?
134 .write_all(&death_reason_bytes[begin..end])?;
135 sent_total = end;
136 }
137
138 Ok(())
139}
140
Inseob Kim1b95f2e2021-08-19 13:17:40 +0900141fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
142 // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
143 // safely taken by new_spibinder.
144 let ibinder = unsafe {
145 new_spibinder(binder_rpc_unstable_bindgen::RpcClient(
146 VMADDR_CID_HOST,
Inseob Kimd0587562021-09-01 21:27:32 +0900147 VM_BINDER_SERVICE_PORT as u32,
Inseob Kim1b95f2e2021-08-19 13:17:40 +0900148 ) as *mut AIBinder)
149 };
150 if let Some(ibinder) = ibinder {
151 <dyn IVirtualMachineService>::try_from(ibinder).context("Cannot connect to RPC service")
152 } else {
153 bail!("Invalid raw AIBinder")
154 }
155}
156
Inseob Kim437f1052022-06-21 11:30:22 +0900157fn main() -> Result<()> {
158 scopeguard::defer! {
159 info!("Shutting down...");
Jooyung Hanbfe086f2021-10-28 10:15:45 +0900160 if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
161 error!("failed to shutdown {:?}", e);
162 }
Jooyung Han311b1202021-09-14 22:00:16 +0900163 }
Inseob Kim437f1052022-06-21 11:30:22 +0900164
165 try_main().map_err(|e| {
166 error!("Failed with {:?}.", e);
167 if let Err(e) = write_death_reason_to_serial(&e) {
168 error!("Failed to write death reason {:?}", e);
169 }
170 e
171 })
Jooyung Han311b1202021-09-14 22:00:16 +0900172}
173
174fn try_main() -> Result<()> {
Andrew Sculle127cff2022-02-15 15:34:04 +0000175 let _ = kernlog::init();
Jooyung Han347d9f22021-05-28 00:05:14 +0900176 info!("started.");
177
Inseob Kim11f40d02022-06-13 17:16:00 +0900178 let service = get_vms_rpc_binder()
179 .context("cannot connect to VirtualMachineService")
180 .map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900181 match try_run_payload(&service) {
182 Ok(code) => {
183 info!("notifying payload finished");
184 service.notifyPayloadFinished(code)?;
185 if code == 0 {
186 info!("task successfully finished");
187 } else {
188 error!("task exited with exit code: {}", code);
189 }
190 Ok(())
191 }
192 Err(err) => {
193 error!("task terminated: {:?}", err);
194 let (error_code, message) = translate_error(&err);
195 service.notifyError(error_code, &message)?;
196 Err(err)
197 }
Jooyung Handd0a1732021-11-23 15:26:20 +0900198 }
199}
200
Inseob Kimb2519c52022-04-14 02:10:09 +0900201fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
Andrew Scullb2f44472022-01-21 14:41:34 +0000202 // Calculate compound digests of code and authorities
Andrew Sculla0d1b1a2022-05-24 19:32:47 +0000203 let mut code_hash_ctx = Sha512::new();
204 let mut authority_hash_ctx = Sha512::new();
Andrew Scullb2f44472022-01-21 14:41:34 +0000205 code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
206 authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
Inseob Kimb2519c52022-04-14 02:10:09 +0900207 for extra_apk in &verified_data.extra_apks_data {
Andrew Scullb2f44472022-01-21 14:41:34 +0000208 code_hash_ctx.update(extra_apk.root_hash.as_ref());
209 authority_hash_ctx.update(extra_apk.pubkey.as_ref());
210 }
Inseob Kimb2519c52022-04-14 02:10:09 +0900211 for apex in &verified_data.apex_data {
Andrew Scullb2f44472022-01-21 14:41:34 +0000212 code_hash_ctx.update(apex.root_digest.as_ref());
213 authority_hash_ctx.update(apex.public_key.as_ref());
214 }
Andrew Sculla0d1b1a2022-05-24 19:32:47 +0000215 let code_hash = code_hash_ctx.finish();
216 let authority_hash = authority_hash_ctx.finish();
Andrew Scullb2f44472022-01-21 14:41:34 +0000217
218 // {
219 // -70002: "Microdroid payload",
220 // -71000: payload_config_path
221 // }
222 let mut config_desc = vec![
223 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72, 0x6f,
224 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01, 0x15, 0x57,
225 ];
226 let config_path_bytes = payload_config_path.as_bytes();
227 encode_header(3, config_path_bytes.len().try_into().unwrap(), &mut config_desc)?;
228 config_desc.extend_from_slice(config_path_bytes);
229
Andrew Scull65ddfc42022-02-14 21:03:58 +0000230 // Check app debuggability, conervatively assuming it is debuggable
231 let app_debuggable = system_properties::read_bool(APP_DEBUGGABLE_PROP, true)?;
232
Andrew Scullb2f44472022-01-21 14:41:34 +0000233 // Send the details to diced
234 let diced =
235 wait_for_interface::<dyn IDiceMaintenance>("android.security.dice.IDiceMaintenance")
236 .context("IDiceMaintenance service not found")?;
237 diced
238 .demoteSelf(&[InputValues {
239 codeHash: code_hash,
240 config: Config { desc: config_desc },
241 authorityHash: authority_hash,
242 authorityDescriptor: None,
Andrew Scull65ddfc42022-02-14 21:03:58 +0000243 mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
Inseob Kimb2519c52022-04-14 02:10:09 +0900244 hidden: verified_data.salt.clone().try_into().unwrap(),
Andrew Scullb2f44472022-01-21 14:41:34 +0000245 }])
246 .context("IDiceMaintenance::demoteSelf failed")?;
247 Ok(())
248}
249
Andrew Scullab72ec52022-03-14 09:10:52 +0000250fn is_strict_boot() -> bool {
251 Path::new(AVF_STRICT_BOOT).exists()
252}
253
254fn is_new_instance() -> bool {
255 Path::new(AVF_NEW_INSTANCE).exists()
256}
257
Jooyung Han5c6d4172021-12-06 14:17:52 +0900258fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
Jooyung Han311b1202021-09-14 22:00:16 +0900259 let metadata = load_metadata().context("Failed to load payload metadata")?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900260
Jooyung Han311b1202021-09-14 22:00:16 +0900261 let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900262 let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900263
Andrew Scullab72ec52022-03-14 09:10:52 +0000264 if is_strict_boot() {
265 // Provisioning must happen on the first boot and never again.
266 if is_new_instance() {
267 ensure!(
268 saved_data.is_none(),
269 MicrodroidError::InvalidConfig("Found instance data on first boot.".to_string())
270 );
271 } else {
272 ensure!(
273 saved_data.is_some(),
274 MicrodroidError::InvalidConfig("Instance data not found.".to_string())
275 );
276 };
277 }
278
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900279 // Verify the payload before using it.
Inseob Kim11f40d02022-06-13 17:16:00 +0900280 let verified_data = verify_payload(&metadata, saved_data.as_ref())
281 .context("Payload verification failed")
282 .map_err(|e| MicrodroidError::PayloadVerificationFailed(e.to_string()))?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900283 if let Some(saved_data) = saved_data {
Jooyung Handd0a1732021-11-23 15:26:20 +0900284 ensure!(
285 saved_data == verified_data,
286 MicrodroidError::PayloadChanged(String::from(
287 "Detected an update of the payload which isn't supported yet."
288 ))
289 );
290 info!("Saved data is verified.");
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900291 } else {
Jooyung Han7a343f92021-09-08 22:53:11 +0900292 info!("Saving verified data.");
293 instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900294 }
295
Inseob Kimb2519c52022-04-14 02:10:09 +0900296 // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
297 info!("DICE derivation for payload");
298 dice_derivation(&verified_data, &metadata.payload_config_path)?;
299
Jooyung Hana6d11eb2021-09-10 11:48:05 +0900300 // Before reading a file from the APK, start zipfuse
Andrew Scullcc339a12022-07-04 12:44:19 +0000301 let noexec = false;
Inseob Kim217038e2021-11-25 11:15:06 +0900302 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000303 noexec,
Inseob Kim217038e2021-11-25 11:15:06 +0900304 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
305 Path::new("/dev/block/mapper/microdroid-apk"),
306 Path::new("/mnt/apk"),
307 )
308 .context("Failed to run zipfuse")?;
Jiyong Park21ce2c52021-08-28 02:32:17 +0900309
Jooyung Han5c6d4172021-12-06 14:17:52 +0900310 ensure!(
311 !metadata.payload_config_path.is_empty(),
312 MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
313 );
Inseob Kim197748b2021-12-01 19:49:00 +0900314
Jooyung Han5c6d4172021-12-06 14:17:52 +0900315 let config = load_config(Path::new(&metadata.payload_config_path))?;
Shikha Panwar6f03c942022-04-13 20:26:50 +0000316
Inseob Kim197748b2021-12-01 19:49:00 +0900317 if config.extra_apks.len() != verified_data.extra_apks_data.len() {
318 return Err(anyhow!(
319 "config expects {} extra apks, but found only {}",
320 config.extra_apks.len(),
321 verified_data.extra_apks_data.len()
322 ));
323 }
324 mount_extra_apks(&config)?;
Jooyung Han634e2d72021-06-10 16:27:38 +0900325
Jooyung Han5c6d4172021-12-06 14:17:52 +0900326 // Wait until apex config is done. (e.g. linker configuration for apexes)
327 // TODO(jooyung): wait until sys.boot_completed?
328 wait_for_apex_config_done()?;
329
Inseob Kimcd9c1dd2022-07-13 17:13:45 +0900330 // Start tombstone_transmit if enabled
331 if config.export_tombstones {
332 system_properties::write("ctl.start", "tombstone_transmit")
333 .context("Failed to start tombstone_transmit")?;
334 } else {
335 system_properties::write("ctl.stop", "tombstoned").context("Failed to stop tombstoned")?;
336 }
337
Jooyung Han5c6d4172021-12-06 14:17:52 +0900338 ensure!(
339 config.task.is_some(),
340 MicrodroidError::InvalidConfig("No task in VM config".to_string())
341 );
Victor Hsieh8d006ba2022-05-02 09:42:02 -0700342 system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900343 exec_task(&config.task.unwrap(), service)
Jooyung Han347d9f22021-05-28 00:05:14 +0900344}
345
Inseob Kim217038e2021-11-25 11:15:06 +0900346struct ApkDmverityArgument<'a> {
347 apk: &'a str,
348 idsig: &'a str,
349 name: &'a str,
Inseob Kim197748b2021-12-01 19:49:00 +0900350 saved_root_hash: Option<&'a RootHash>,
Inseob Kim217038e2021-11-25 11:15:06 +0900351}
352
353fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
354 let mut cmd = Command::new(APKDMVERITY_BIN);
355
356 cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
357
358 for argument in args {
359 cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
Inseob Kim197748b2021-12-01 19:49:00 +0900360 if let Some(root_hash) = argument.saved_root_hash {
361 cmd.arg(&to_hex_string(root_hash));
362 } else {
363 cmd.arg("none");
364 }
Inseob Kim217038e2021-11-25 11:15:06 +0900365 }
366
367 cmd.spawn().context("Spawn apkdmverity")
368}
369
Andrew Scullcc339a12022-07-04 12:44:19 +0000370fn run_zipfuse(noexec: bool, option: &str, zip_path: &Path, mount_dir: &Path) -> Result<Child> {
371 let mut cmd = Command::new(ZIPFUSE_BIN);
372 if noexec {
373 cmd.arg("--noexec");
374 }
375 cmd.arg("-o")
Inseob Kim217038e2021-11-25 11:15:06 +0900376 .arg(option)
377 .arg(zip_path)
378 .arg(mount_dir)
379 .stdin(Stdio::null())
380 .stdout(Stdio::null())
381 .stderr(Stdio::null())
382 .spawn()
383 .context("Spawn zipfuse")
384}
385
Jooyung Han7a343f92021-09-08 22:53:11 +0900386// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
387// when the root_hash values from the idsig file and the instance disk are different. This function
388// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
389// saved to the instance disk.
390fn verify_payload(
391 metadata: &Metadata,
392 saved_data: Option<&MicrodroidData>,
393) -> Result<MicrodroidData> {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900394 let start_time = SystemTime::now();
395
Inseob Kim197748b2021-12-01 19:49:00 +0900396 // Verify main APK
Jooyung Han7a343f92021-09-08 22:53:11 +0900397 let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
Inseob Kim197748b2021-12-01 19:49:00 +0900398 let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
Jiyong Parkf7dea252021-09-08 01:42:54 +0900399 let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900400
Jiyong Parkf7dea252021-09-08 01:42:54 +0900401 // 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 +0900402 // instead of the value read from the idsig file.
Inseob Kim197748b2021-12-01 19:49:00 +0900403 let main_apk_argument = {
404 ApkDmverityArgument {
405 apk: MAIN_APK_PATH,
406 idsig: MAIN_APK_IDSIG_PATH,
407 name: MAIN_APK_DEVICE_NAME,
408 saved_root_hash: if root_hash_trustful {
409 Some(root_hash_from_idsig.as_ref())
410 } else {
411 None
412 },
413 }
414 };
415 let mut apkdmverity_arguments = vec![main_apk_argument];
416
417 // Verify extra APKs
418 // For now, we can't read the payload config, so glob APKs and idsigs.
419 // Later, we'll see if it matches with the payload config.
420
421 // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
422 // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
423 let extra_apks =
424 sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
425 let extra_idsigs =
426 sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
427 if extra_apks.len() != extra_idsigs.len() {
428 return Err(anyhow!(
429 "Extra apks/idsigs mismatch: {} apks but {} idsigs",
430 extra_apks.len(),
431 extra_idsigs.len()
432 ));
433 }
434 let extra_apks_count = extra_apks.len();
435
436 let (extra_apk_names, extra_root_hashes_from_idsig): (Vec<_>, Vec<_>) = extra_idsigs
437 .iter()
438 .enumerate()
439 .map(|(i, extra_idsig)| {
440 (
441 format!("extra-apk-{}", i),
442 get_apk_root_hash_from_idsig(extra_idsig.to_str().unwrap())
443 .expect("Can't find root hash from extra idsig"),
444 )
445 })
446 .unzip();
447
448 let saved_extra_root_hashes: Vec<_> = saved_data
449 .map(|d| d.extra_apks_data.iter().map(|apk_data| &apk_data.root_hash).collect())
450 .unwrap_or_else(Vec::new);
451 let extra_root_hashes_trustful: Vec<_> = extra_root_hashes_from_idsig
452 .iter()
453 .enumerate()
454 .map(|(i, root_hash_from_idsig)| {
455 saved_extra_root_hashes.get(i).copied() == Some(root_hash_from_idsig)
456 })
457 .collect();
458
459 for i in 0..extra_apks_count {
460 apkdmverity_arguments.push({
461 ApkDmverityArgument {
462 apk: extra_apks[i].to_str().unwrap(),
463 idsig: extra_idsigs[i].to_str().unwrap(),
464 name: &extra_apk_names[i],
465 saved_root_hash: if extra_root_hashes_trustful[i] {
466 Some(&extra_root_hashes_from_idsig[i])
467 } else {
468 None
469 },
470 }
471 });
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900472 }
473
474 // Start apkdmverity and wait for the dm-verify block
Inseob Kim197748b2021-12-01 19:49:00 +0900475 let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900476
Jooyung Hanc8deb472021-09-13 13:48:25 +0900477 // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
478 // APEX payload.
Jooyung Han7a343f92021-09-08 22:53:11 +0900479 let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900480 if let Some(saved_data) = saved_data.map(|d| &d.apex_data) {
Jooyung Hanc8deb472021-09-13 13:48:25 +0900481 // We don't support APEX updates. (assuming that update will change root digest)
Jooyung Handd0a1732021-11-23 15:26:20 +0900482 ensure!(
483 saved_data == &apex_data_from_payload,
484 MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
485 );
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900486 let apex_metadata = to_metadata(&apex_data_from_payload);
Jooyung Hanc8deb472021-09-13 13:48:25 +0900487 // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
488 // metadata instead of the default one (/dev/block/by-name/payload-metadata)
Jooyung Han4a9b3bf2021-09-10 17:19:00 +0900489 OpenOptions::new()
490 .create_new(true)
491 .write(true)
492 .open("/apex/vm-payload-metadata")
493 .context("Failed to open /apex/vm-payload-metadata")
494 .and_then(|f| write_metadata(&apex_metadata, f))?;
495 }
496 // Start apexd to activate APEXes
497 system_properties::write("ctl.start", "apexd-vm")?;
Jooyung Han7a343f92021-09-08 22:53:11 +0900498
Inseob Kim217038e2021-11-25 11:15:06 +0900499 // TODO(inseob): add timeout
500 apkdmverity_child.wait()?;
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900501
Jiyong Parkf7dea252021-09-08 01:42:54 +0900502 // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900503 // 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 +0900504 // 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 +0900505 // of the VM or APK was updated in the host.
506 // TODO(jooyung): consider multithreading to make this faster
Inseob Kim197748b2021-12-01 19:49:00 +0900507 let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
508 let extra_apks_data = extra_root_hashes_from_idsig
509 .into_iter()
510 .enumerate()
511 .map(|(i, extra_root_hash)| {
512 let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
513 let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
514 Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
515 })
516 .collect::<Result<Vec<_>>>()?;
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900517
518 info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
519
Andrew Scull34916a72022-01-30 21:34:24 +0000520 // Use the salt from a verified instance, or generate a salt for a new instance.
521 let salt = if let Some(saved_data) = saved_data {
522 saved_data.salt.clone()
523 } else {
524 let mut salt = vec![0u8; 64];
525 salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
526 salt
527 };
528
Jiyong Parkf7dea252021-09-08 01:42:54 +0900529 // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
530 // fully verifying the APK or by comparing it with the saved root_hash.
Jooyung Han7a343f92021-09-08 22:53:11 +0900531 Ok(MicrodroidData {
Andrew Scull34916a72022-01-30 21:34:24 +0000532 salt,
Inseob Kim197748b2021-12-01 19:49:00 +0900533 apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
534 extra_apks_data,
Jooyung Han7a343f92021-09-08 22:53:11 +0900535 apex_data: apex_data_from_payload,
536 })
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900537}
538
Inseob Kim197748b2021-12-01 19:49:00 +0900539fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
540 // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
541 for i in 0..config.extra_apks.len() {
542 let mount_dir = format!("/mnt/extra-apk/{}", i);
543 create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
544
545 // don't wait, just detach
Andrew Scullcc339a12022-07-04 12:44:19 +0000546 let noexec = true;
Inseob Kim197748b2021-12-01 19:49:00 +0900547 run_zipfuse(
Andrew Scullcc339a12022-07-04 12:44:19 +0000548 noexec,
Inseob Kim197748b2021-12-01 19:49:00 +0900549 "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
550 Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
551 Path::new(&mount_dir),
552 )
553 .context("Failed to zipfuse extra apks")?;
554 }
555
556 Ok(())
557}
558
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900559// Waits until linker config is generated
560fn wait_for_apex_config_done() -> Result<()> {
561 let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
562 loop {
563 prop.wait()?;
Andrew Scull4b5369f2022-01-28 13:47:20 +0000564 if system_properties::read_bool(APEX_CONFIG_DONE_PROP, false)? {
Jiyong Parkbb4a9872021-09-06 15:59:21 +0900565 break;
566 }
567 }
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900568 Ok(())
569}
570
Inseob Kim197748b2021-12-01 19:49:00 +0900571fn get_apk_root_hash_from_idsig(path: &str) -> Result<Box<RootHash>> {
572 let mut idsig = File::open(path)?;
Jiyong Park21ce2c52021-08-28 02:32:17 +0900573 let idsig = V4Signature::from(&mut idsig)?;
574 Ok(idsig.hashing_info.raw_root_hash)
575}
576
Inseob Kim197748b2021-12-01 19:49:00 +0900577fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
578 if !root_hash_trustful {
579 verify(apk).context(MicrodroidError::PayloadVerificationFailed(format!(
580 "failed to verify {}",
581 apk
582 )))
583 } else {
584 get_public_key_der(apk)
585 }
586}
587
Jooyung Han634e2d72021-06-10 16:27:38 +0900588fn load_config(path: &Path) -> Result<VmPayloadConfig> {
589 info!("loading config from {:?}...", path);
590 let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
591 Ok(serde_json::from_reader(file)?)
592}
593
Jiyong Park8611a6c2021-07-09 18:17:44 +0900594/// Executes the given task. Stdout of the task is piped into the vsock stream to the
595/// virtualizationservice in the host side.
Jooyung Han5c6d4172021-12-06 14:17:52 +0900596fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
Jiyong Park8611a6c2021-07-09 18:17:44 +0900597 info!("executing main task {:?}...", task);
Inseob Kim86ca0162021-10-20 02:21:02 +0000598 let mut command = build_command(task)?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900599
600 info!("notifying payload started");
Inseob Kimc7d28c72021-10-25 14:28:10 +0000601 service.notifyPayloadStarted()?;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900602
Inseob Kim86ca0162021-10-20 02:21:02 +0000603 let exit_status = command.spawn()?.wait()?;
Jooyung Han5c6d4172021-12-06 14:17:52 +0900604 exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
Jooyung Han347d9f22021-05-28 00:05:14 +0900605}
Jooyung Han634e2d72021-06-10 16:27:38 +0900606
607fn build_command(task: &Task) -> Result<Command> {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900608 const VMADDR_CID_HOST: u32 = 2;
Inseob Kim7f61fe72021-08-20 20:50:47 +0900609
610 let mut command = match task.type_ {
Jooyung Han634e2d72021-06-10 16:27:38 +0900611 TaskType::Executable => {
612 let mut command = Command::new(&task.command);
613 command.args(&task.args);
614 command
615 }
616 TaskType::MicrodroidLauncher => {
617 let mut command = Command::new("/system/bin/microdroid_launcher");
618 command.arg(find_library_path(&task.command)?).args(&task.args);
619 command
620 }
Inseob Kim7f61fe72021-08-20 20:50:47 +0900621 };
622
Inseob Kimd0587562021-09-01 21:27:32 +0900623 match VsockStream::connect_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32) {
Inseob Kim7f61fe72021-08-20 20:50:47 +0900624 Ok(stream) => {
625 // SAFETY: the ownership of the underlying file descriptor is transferred from stream
626 // to the file object, and then into the Command object. When the command is finished,
627 // the file descriptor is closed.
628 let file = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
629 command
630 .stdin(Stdio::from(file.try_clone()?))
631 .stdout(Stdio::from(file.try_clone()?))
632 .stderr(Stdio::from(file));
633 }
634 Err(e) => {
635 error!("failed to connect to virtualization service: {}", e);
636 // Don't fail hard here. Even if we failed to connect to the virtualizationservice,
637 // we keep executing the task. This can happen if the owner of the VM doesn't register
638 // callback to accept the stream. Use /dev/null as the stream so that the task can
639 // make progress without waiting for someone to consume the output.
640 command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
641 }
642 }
643
644 Ok(command)
Jooyung Han634e2d72021-06-10 16:27:38 +0900645}
646
647fn find_library_path(name: &str) -> Result<String> {
648 let mut watcher = PropertyWatcher::new("ro.product.cpu.abilist")?;
649 let value = watcher.read(|_name, value| Ok(value.trim().to_string()))?;
650 let abi = value.split(',').next().ok_or_else(|| anyhow!("no abilist"))?;
651 let path = format!("/mnt/apk/lib/{}/{}", abi, name);
652
653 let metadata = fs::metadata(&path)?;
654 if !metadata.is_file() {
655 bail!("{} is not a file", &path);
656 }
657
658 Ok(path)
659}
Jiyong Park21ce2c52021-08-28 02:32:17 +0900660
661fn to_hex_string(buf: &[u8]) -> String {
662 buf.iter().map(|b| format!("{:02X}", b)).collect()
663}