blob: e5d26fcbacd49c4e7d1a7f5889be180253af51c3 [file] [log] [blame]
Alan Stokes1125e012023-10-13 12:31:10 +01001// Copyright 2023 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
Alan Stokes1508df22023-12-04 11:31:21 +000015use crate::instance::{ApexData, ApkData, MicrodroidData};
Alan Stokes03754962023-11-06 15:36:09 +000016use crate::payload::{get_apex_data_from_payload, to_metadata};
Shikha Panwarad0e5352024-12-11 17:36:43 +000017use crate::MicrodroidError;
Alan Stokes1125e012023-10-13 12:31:10 +010018use anyhow::{anyhow, ensure, Context, Result};
19use apkmanifest::get_manifest_info;
Alan Stokes1508df22023-12-04 11:31:21 +000020use apkverify::{extract_signed_data, verify, V4Signature};
Alan Stokes1125e012023-10-13 12:31:10 +010021use glob::glob;
22use itertools::sorted;
23use log::{info, warn};
Alan Stokes03754962023-11-06 15:36:09 +000024use microdroid_metadata::{write_metadata, Metadata};
Alan Stokes1508df22023-12-04 11:31:21 +000025use openssl::sha::sha512;
Alan Stokes1125e012023-10-13 12:31:10 +010026use rustutils::system_properties;
Alan Stokes03754962023-11-06 15:36:09 +000027use std::fs::OpenOptions;
Alan Stokes1125e012023-10-13 12:31:10 +010028use std::path::Path;
29use std::process::{Child, Command};
30use std::str;
31use std::time::SystemTime;
32
33pub const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
34
35const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
36const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
37const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
38const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
39const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
40
41const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
42
43/// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
44/// when the root_hash values from the idsig file and the instance disk are different. This function
45/// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
46/// saved to the instance disk.
47pub fn verify_payload(
48 metadata: &Metadata,
49 saved_data: Option<&MicrodroidData>,
50) -> Result<MicrodroidData> {
51 let start_time = SystemTime::now();
52
53 // Verify main APK
54 let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
55 let root_hash_trustful =
56 saved_data.map(|d| d.apk_data.root_hash_eq(root_hash_from_idsig.as_ref())).unwrap_or(false);
57
58 // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
59 // instead of the value read from the idsig file.
60 let main_apk_argument = {
61 ApkDmverityArgument {
62 apk: MAIN_APK_PATH,
63 idsig: MAIN_APK_IDSIG_PATH,
64 name: MAIN_APK_DEVICE_NAME,
65 saved_root_hash: if root_hash_trustful {
66 Some(root_hash_from_idsig.as_ref())
67 } else {
68 None
69 },
70 }
71 };
72 let mut apkdmverity_arguments = vec![main_apk_argument];
73
74 // Verify extra APKs
75 // For now, we can't read the payload config, so glob APKs and idsigs.
76 // Later, we'll see if it matches with the payload config.
77
78 // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
79 // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
80 let extra_apks =
81 sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
82 let extra_idsigs =
83 sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
84 ensure!(
85 extra_apks.len() == extra_idsigs.len(),
86 "Extra apks/idsigs mismatch: {} apks but {} idsigs",
87 extra_apks.len(),
88 extra_idsigs.len()
89 );
90
91 let extra_root_hashes_from_idsig: Vec<_> = extra_idsigs
92 .iter()
93 .map(|idsig| {
94 get_apk_root_hash_from_idsig(idsig).expect("Can't find root hash from extra idsig")
95 })
96 .collect();
97
98 let extra_root_hashes_trustful: Vec<_> = if let Some(data) = saved_data {
99 extra_root_hashes_from_idsig
100 .iter()
101 .enumerate()
102 .map(|(i, root_hash)| data.extra_apk_root_hash_eq(i, root_hash))
103 .collect()
104 } else {
105 vec![false; extra_root_hashes_from_idsig.len()]
106 };
107 let extra_apk_names: Vec<_> =
108 (0..extra_apks.len()).map(|i| format!("extra-apk-{}", i)).collect();
109
110 for (i, extra_apk) in extra_apks.iter().enumerate() {
111 apkdmverity_arguments.push({
112 ApkDmverityArgument {
113 apk: extra_apk.to_str().unwrap(),
114 idsig: extra_idsigs[i].to_str().unwrap(),
115 name: &extra_apk_names[i],
116 saved_root_hash: if extra_root_hashes_trustful[i] {
117 Some(&extra_root_hashes_from_idsig[i])
118 } else {
119 None
120 },
121 }
122 });
123 }
124
125 // Start apkdmverity and wait for the dm-verify block
126 let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
127
128 // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
129 // APEX payload.
130 let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
131
Alan Stokes160f3942023-11-10 10:29:00 +0000132 // To prevent a TOCTOU attack, we need to make sure that when apexd verifies & mounts the
133 // APEXes it sees the same ones that we just read - so we write the metadata we just collected
134 // to a file (that the host can't access) that apexd will then verify against. See b/199371341.
135 write_apex_payload_data(saved_data, &apex_data_from_payload)?;
Alan Stokes1125e012023-10-13 12:31:10 +0100136
Alan Stokes9a7f67e2023-11-07 09:37:40 +0000137 if cfg!(not(dice_changes)) {
138 // Start apexd to activate APEXes
139 system_properties::write("ctl.start", "apexd-vm")?;
140 }
Alan Stokes1125e012023-10-13 12:31:10 +0100141
142 // TODO(inseob): add timeout
143 apkdmverity_child.wait()?;
144
145 // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
146 // the APK file and therefore can be very slow if the APK is large. Note that this step is
147 // taken only when the root_hash is un-trustful which can be either when this is the first boot
148 // of the VM or APK was updated in the host.
149 // TODO(jooyung): consider multithreading to make this faster
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100150
151 let main_apk_data =
152 get_data_from_apk(DM_MOUNTED_APK_PATH, root_hash_from_idsig, root_hash_trustful)?;
153
Alan Stokes1125e012023-10-13 12:31:10 +0100154 let extra_apks_data = extra_root_hashes_from_idsig
155 .into_iter()
156 .enumerate()
157 .map(|(i, extra_root_hash)| {
158 let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100159 get_data_from_apk(&mount_path, extra_root_hash, extra_root_hashes_trustful[i])
Alan Stokes1125e012023-10-13 12:31:10 +0100160 })
161 .collect::<Result<Vec<_>>>()?;
162
163 info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
164
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100165 // At this point, we can ensure that the root hashes from the idsig files are trusted, either
166 // because we have fully verified the APK signature (and apkdmverity checks all the data we
167 // verified is consistent with the root hash) or because we have the saved APK data which will
168 // be checked as identical to the data we have verified.
169
Alan Stokes1125e012023-10-13 12:31:10 +0100170 Ok(MicrodroidData {
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100171 apk_data: main_apk_data,
Alan Stokes1125e012023-10-13 12:31:10 +0100172 extra_apks_data,
173 apex_data: apex_data_from_payload,
174 })
175}
176
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100177fn get_data_from_apk(
178 apk_path: &str,
Alan Stokes1508df22023-12-04 11:31:21 +0000179 root_hash: Box<[u8]>,
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100180 root_hash_trustful: bool,
181) -> Result<ApkData> {
Alan Stokes1508df22023-12-04 11:31:21 +0000182 let cert_hash = get_cert_hash_from_apk(apk_path, root_hash_trustful)?.to_vec();
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100183 // Read package name etc from the APK manifest. In the unlikely event that they aren't present
184 // we use the default values. We simply put these values in the DICE node for the payload, and
185 // users of that can decide how to handle blank information - there's no reason for us
186 // to fail starting a VM even with such a weird APK.
187 let manifest_info = get_manifest_info(apk_path)
188 .map_err(|e| warn!("Failed to read manifest info from APK: {e:?}"))
189 .unwrap_or_default();
190
191 Ok(ApkData {
Alan Stokes1508df22023-12-04 11:31:21 +0000192 root_hash: root_hash.into(),
193 cert_hash,
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100194 package_name: manifest_info.package,
195 version_code: manifest_info.version_code,
196 })
197}
198
Alan Stokes03754962023-11-06 15:36:09 +0000199fn write_apex_payload_data(
200 saved_data: Option<&MicrodroidData>,
201 apex_data_from_payload: &[ApexData],
202) -> Result<()> {
203 if let Some(saved_apex_data) = saved_data.map(|d| &d.apex_data) {
204 // We don't support APEX updates. (assuming that update will change root digest)
205 ensure!(
206 saved_apex_data == apex_data_from_payload,
207 MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
208 );
Alan Stokes03754962023-11-06 15:36:09 +0000209 }
Alan Stokes160f3942023-11-10 10:29:00 +0000210 let apex_metadata = to_metadata(apex_data_from_payload);
211 // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
212 // metadata instead of the default one (/dev/block/by-name/payload-metadata)
213 OpenOptions::new()
214 .create_new(true)
215 .write(true)
216 .open("/apex/vm-payload-metadata")
217 .context("Failed to open /apex/vm-payload-metadata")
218 .and_then(|f| write_metadata(&apex_metadata, f))?;
219
Alan Stokes03754962023-11-06 15:36:09 +0000220 Ok(())
221}
222
Alan Stokes1508df22023-12-04 11:31:21 +0000223fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<[u8]>> {
Alan Stokes1125e012023-10-13 12:31:10 +0100224 Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
225}
226
Alan Stokes1508df22023-12-04 11:31:21 +0000227fn get_cert_hash_from_apk(apk: &str, root_hash_trustful: bool) -> Result<[u8; 64]> {
Alan Stokes1125e012023-10-13 12:31:10 +0100228 let current_sdk = get_current_sdk()?;
229
Alan Stokes1508df22023-12-04 11:31:21 +0000230 let signed_data = if !root_hash_trustful {
Alan Stokes1125e012023-10-13 12:31:10 +0100231 verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
232 "failed to verify {}",
233 apk
Alan Stokes9b8b8ec2023-10-13 15:58:11 +0100234 )))
Alan Stokes1125e012023-10-13 12:31:10 +0100235 } else {
Alan Stokes1508df22023-12-04 11:31:21 +0000236 extract_signed_data(apk, current_sdk)
237 }?;
238 Ok(sha512(signed_data.first_certificate_der()?))
Alan Stokes1125e012023-10-13 12:31:10 +0100239}
240
241fn get_current_sdk() -> Result<u32> {
242 let current_sdk = system_properties::read("ro.build.version.sdk")?;
243 let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
244 current_sdk.parse().context("Malformed SDK version")
245}
246
247struct ApkDmverityArgument<'a> {
248 apk: &'a str,
249 idsig: &'a str,
250 name: &'a str,
Alan Stokes1508df22023-12-04 11:31:21 +0000251 saved_root_hash: Option<&'a [u8]>,
Alan Stokes1125e012023-10-13 12:31:10 +0100252}
253
254fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
255 let mut cmd = Command::new(APKDMVERITY_BIN);
256
257 for argument in args {
258 cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
259 if let Some(root_hash) = argument.saved_root_hash {
Chris Wailes63b67d72024-08-19 16:23:21 -0700260 cmd.arg(hex::encode(root_hash));
Alan Stokes1125e012023-10-13 12:31:10 +0100261 } else {
262 cmd.arg("none");
263 }
264 }
265
266 cmd.spawn().context("Spawn apkdmverity")
267}