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