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