1 // Copyright 2022, 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 //! This module handles the pvmfw payload verification.
16
17 use crate::ops::{Ops, Payload};
18 use crate::partition::PartitionName;
19 use crate::PvmfwVerifyError;
20 use alloc::{string::String, vec::Vec};
21 use avb::{
22 Descriptor, DescriptorError, DescriptorResult, HashDescriptor, PartitionData, SlotVerifyError,
23 SlotVerifyNoDataResult, VbmetaData,
24 };
25 use core::str;
26
27 // We use this for the rollback_index field if SlotVerifyData has empty rollback_indexes
28 const DEFAULT_ROLLBACK_INDEX: u64 = 0;
29
30 /// SHA256 digest type for kernel and initrd.
31 pub type Digest = [u8; 32];
32
33 /// Verified data returned when the payload verification succeeds.
34 #[derive(Debug, PartialEq, Eq)]
35 pub struct VerifiedBootData<'a> {
36 /// DebugLevel of the VM.
37 pub debug_level: DebugLevel,
38 /// Kernel digest.
39 pub kernel_digest: Digest,
40 /// Initrd digest if initrd exists.
41 pub initrd_digest: Option<Digest>,
42 /// Trusted public key.
43 pub public_key: &'a [u8],
44 /// VM capabilities.
45 pub capabilities: Vec<Capability>,
46 /// Rollback index of kernel.
47 pub rollback_index: u64,
48 /// Page size of kernel, if present.
49 pub page_size: Option<usize>,
50 /// Name of the guest payload, if present.
51 pub name: Option<String>,
52 }
53
54 impl VerifiedBootData<'_> {
55 /// Returns whether the kernel have the given capability
has_capability(&self, cap: Capability) -> bool56 pub fn has_capability(&self, cap: Capability) -> bool {
57 self.capabilities.contains(&cap)
58 }
59 }
60
61 /// This enum corresponds to the `DebugLevel` in `VirtualMachineConfig`.
62 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
63 pub enum DebugLevel {
64 /// Not debuggable at all.
65 None,
66 /// Fully debuggable.
67 Full,
68 }
69
70 /// VM Capability.
71 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
72 pub enum Capability {
73 /// Remote attestation.
74 RemoteAttest,
75 /// Secretkeeper protected secrets.
76 SecretkeeperProtection,
77 /// Trusty security VM.
78 TrustySecurityVm,
79 /// UEFI support for booting guest kernel.
80 SupportsUefiBoot,
81 /// (internal)
82 #[allow(non_camel_case_types)] // TODO: Use mem::variant_count once stable.
83 _VARIANT_COUNT,
84 }
85
86 impl Capability {
87 const KEY: &'static str = "com.android.virt.cap";
88 const REMOTE_ATTEST: &'static [u8] = b"remote_attest";
89 const TRUSTY_SECURITY_VM: &'static [u8] = b"trusty_security_vm";
90 const SECRETKEEPER_PROTECTION: &'static [u8] = b"secretkeeper_protection";
91 const SEPARATOR: u8 = b'|';
92 const SUPPORTS_UEFI_BOOT: &'static [u8] = b"supports_uefi_boot";
93 /// Number of supported capabilites.
94 pub const COUNT: usize = Self::_VARIANT_COUNT as usize;
95
96 /// Returns the capabilities indicated in `descriptor`, or error if the descriptor has
97 /// unexpected contents.
get_capabilities(vbmeta_data: &VbmetaData) -> Result<Vec<Self>, PvmfwVerifyError>98 fn get_capabilities(vbmeta_data: &VbmetaData) -> Result<Vec<Self>, PvmfwVerifyError> {
99 let Some(value) = vbmeta_data.get_property_value(Self::KEY) else {
100 return Ok(Vec::new());
101 };
102
103 let mut res = Vec::new();
104
105 for v in value.split(|b| *b == Self::SEPARATOR) {
106 let cap = match v {
107 Self::REMOTE_ATTEST => Self::RemoteAttest,
108 Self::TRUSTY_SECURITY_VM => Self::TrustySecurityVm,
109 Self::SECRETKEEPER_PROTECTION => Self::SecretkeeperProtection,
110 Self::SUPPORTS_UEFI_BOOT => Self::SupportsUefiBoot,
111 _ => return Err(PvmfwVerifyError::UnknownVbmetaProperty),
112 };
113 if res.contains(&cap) {
114 return Err(SlotVerifyError::InvalidMetadata.into());
115 }
116 res.push(cap);
117 }
118 Ok(res)
119 }
120 }
121
verify_only_one_vbmeta_exists(vbmeta_data: &[VbmetaData]) -> SlotVerifyNoDataResult<()>122 fn verify_only_one_vbmeta_exists(vbmeta_data: &[VbmetaData]) -> SlotVerifyNoDataResult<()> {
123 if vbmeta_data.len() == 1 {
124 Ok(())
125 } else {
126 Err(SlotVerifyError::InvalidMetadata)
127 }
128 }
129
verify_vbmeta_is_from_kernel_partition(vbmeta_image: &VbmetaData) -> SlotVerifyNoDataResult<()>130 fn verify_vbmeta_is_from_kernel_partition(vbmeta_image: &VbmetaData) -> SlotVerifyNoDataResult<()> {
131 match vbmeta_image.partition_name().try_into() {
132 Ok(PartitionName::Kernel) => Ok(()),
133 _ => Err(SlotVerifyError::InvalidMetadata),
134 }
135 }
136
verify_loaded_partition_has_expected_length( loaded_partitions: &[PartitionData], partition_name: PartitionName, expected_len: usize, ) -> SlotVerifyNoDataResult<()>137 fn verify_loaded_partition_has_expected_length(
138 loaded_partitions: &[PartitionData],
139 partition_name: PartitionName,
140 expected_len: usize,
141 ) -> SlotVerifyNoDataResult<()> {
142 if loaded_partitions.len() != 1 {
143 // Only one partition should be loaded in each verify result.
144 return Err(SlotVerifyError::Io);
145 }
146 let loaded_partition = &loaded_partitions[0];
147 if !PartitionName::try_from(loaded_partition.partition_name())
148 .map_or(false, |p| p == partition_name)
149 {
150 // Only the requested partition should be loaded.
151 return Err(SlotVerifyError::Io);
152 }
153 if loaded_partition.data().len() == expected_len {
154 Ok(())
155 } else {
156 Err(SlotVerifyError::Verification(None))
157 }
158 }
159
160 /// Hash descriptors extracted from a vbmeta image.
161 ///
162 /// We always have a kernel hash descriptor and may have initrd normal or debug descriptors.
163 struct HashDescriptors<'a> {
164 kernel: &'a HashDescriptor<'a>,
165 initrd_normal: Option<&'a HashDescriptor<'a>>,
166 initrd_debug: Option<&'a HashDescriptor<'a>>,
167 }
168
169 impl<'a> HashDescriptors<'a> {
170 /// Extracts the hash descriptors from all vbmeta descriptors. Any unexpected hash descriptor
171 /// is an error.
get(descriptors: &'a [Descriptor<'a>]) -> DescriptorResult<Self>172 fn get(descriptors: &'a [Descriptor<'a>]) -> DescriptorResult<Self> {
173 let mut kernel = None;
174 let mut initrd_normal = None;
175 let mut initrd_debug = None;
176
177 for descriptor in descriptors.iter().filter_map(|d| match d {
178 Descriptor::Hash(h) => Some(h),
179 _ => None,
180 }) {
181 let target = match descriptor
182 .partition_name
183 .as_bytes()
184 .try_into()
185 .map_err(|_| DescriptorError::InvalidContents)?
186 {
187 PartitionName::Kernel => &mut kernel,
188 PartitionName::InitrdNormal => &mut initrd_normal,
189 PartitionName::InitrdDebug => &mut initrd_debug,
190 };
191
192 if target.is_some() {
193 // Duplicates of the same partition name is an error.
194 return Err(DescriptorError::InvalidContents);
195 }
196 target.replace(descriptor);
197 }
198
199 // Kernel is required, the others are optional.
200 Ok(Self {
201 kernel: kernel.ok_or(DescriptorError::InvalidContents)?,
202 initrd_normal,
203 initrd_debug,
204 })
205 }
206
207 /// Returns an error if either initrd descriptor exists.
verify_no_initrd(&self) -> Result<(), PvmfwVerifyError>208 fn verify_no_initrd(&self) -> Result<(), PvmfwVerifyError> {
209 match self.initrd_normal.or(self.initrd_debug) {
210 Some(_) => Err(SlotVerifyError::InvalidMetadata.into()),
211 None => Ok(()),
212 }
213 }
214 }
215
216 /// Returns a copy of the SHA256 digest in `descriptor`, or error if the sizes don't match.
copy_digest(descriptor: &HashDescriptor) -> SlotVerifyNoDataResult<Digest>217 fn copy_digest(descriptor: &HashDescriptor) -> SlotVerifyNoDataResult<Digest> {
218 let mut digest = Digest::default();
219 if descriptor.digest.len() != digest.len() {
220 return Err(SlotVerifyError::InvalidMetadata);
221 }
222 digest.clone_from_slice(descriptor.digest);
223 Ok(digest)
224 }
225
226 /// Returns the indicated payload page size, if present.
read_page_size(vbmeta_data: &VbmetaData) -> Result<Option<usize>, PvmfwVerifyError>227 fn read_page_size(vbmeta_data: &VbmetaData) -> Result<Option<usize>, PvmfwVerifyError> {
228 let Some(property) = vbmeta_data.get_property_value("com.android.virt.page_size") else {
229 return Ok(None);
230 };
231 let size = str::from_utf8(property)
232 .or(Err(PvmfwVerifyError::InvalidPageSize))?
233 .parse::<usize>()
234 .or(Err(PvmfwVerifyError::InvalidPageSize))?
235 .checked_mul(1024)
236 // TODO(stable(unsigned_is_multiple_of)): use .is_multiple_of()
237 .filter(|sz| sz % (4 << 10) == 0 && *sz != 0)
238 .ok_or(PvmfwVerifyError::InvalidPageSize)?;
239
240 Ok(Some(size))
241 }
242
243 /// Returns the indicated payload name, if present.
read_name(vbmeta_data: &VbmetaData) -> Result<Option<String>, PvmfwVerifyError>244 fn read_name(vbmeta_data: &VbmetaData) -> Result<Option<String>, PvmfwVerifyError> {
245 let Some(property) = vbmeta_data.get_property_value("com.android.virt.name") else {
246 return Ok(None);
247 };
248 let name = str::from_utf8(property).map_err(|_| PvmfwVerifyError::InvalidVmName)?;
249 if name.is_empty() {
250 return Err(PvmfwVerifyError::InvalidVmName);
251 }
252 Ok(Some(name.into()))
253 }
254
255 /// Verifies the given initrd partition, and checks that the resulting contents looks like expected.
verify_initrd( ops: &mut Ops, partition_name: PartitionName, expected_initrd: &[u8], ) -> SlotVerifyNoDataResult<()>256 fn verify_initrd(
257 ops: &mut Ops,
258 partition_name: PartitionName,
259 expected_initrd: &[u8],
260 ) -> SlotVerifyNoDataResult<()> {
261 let result =
262 ops.verify_partition(partition_name.as_cstr()).map_err(|e| e.without_verify_data())?;
263 verify_loaded_partition_has_expected_length(
264 result.partition_data(),
265 partition_name,
266 expected_initrd.len(),
267 )
268 }
269
270 /// Verifies the payload (signed kernel + initrd) against the trusted public key.
verify_payload<'a>( kernel: &[u8], initrd: Option<&[u8]>, trusted_public_key: &'a [u8], ) -> Result<VerifiedBootData<'a>, PvmfwVerifyError>271 pub fn verify_payload<'a>(
272 kernel: &[u8],
273 initrd: Option<&[u8]>,
274 trusted_public_key: &'a [u8],
275 ) -> Result<VerifiedBootData<'a>, PvmfwVerifyError> {
276 let payload = Payload::new(kernel, initrd, trusted_public_key);
277 let mut ops = Ops::new(&payload);
278 let kernel_verify_result = ops.verify_partition(PartitionName::Kernel.as_cstr())?;
279
280 let vbmeta_images = kernel_verify_result.vbmeta_data();
281 // TODO(b/302093437): Use explicit rollback_index_location instead of default
282 // location (first element).
283 let rollback_index =
284 *kernel_verify_result.rollback_indexes().first().unwrap_or(&DEFAULT_ROLLBACK_INDEX);
285 verify_only_one_vbmeta_exists(vbmeta_images)?;
286 let vbmeta_image = &vbmeta_images[0];
287 verify_vbmeta_is_from_kernel_partition(vbmeta_image)?;
288 let descriptors = vbmeta_image.descriptors()?;
289 let hash_descriptors = HashDescriptors::get(&descriptors)?;
290 let capabilities = Capability::get_capabilities(vbmeta_image)?;
291 let page_size = read_page_size(vbmeta_image)?;
292 let name = read_name(vbmeta_image)?;
293
294 if initrd.is_none() {
295 hash_descriptors.verify_no_initrd()?;
296 return Ok(VerifiedBootData {
297 debug_level: DebugLevel::None,
298 kernel_digest: copy_digest(hash_descriptors.kernel)?,
299 initrd_digest: None,
300 public_key: trusted_public_key,
301 capabilities,
302 rollback_index,
303 page_size,
304 name,
305 });
306 }
307
308 let initrd = initrd.unwrap();
309 let (debug_level, initrd_descriptor) =
310 if verify_initrd(&mut ops, PartitionName::InitrdNormal, initrd).is_ok() {
311 (DebugLevel::None, hash_descriptors.initrd_normal)
312 } else if verify_initrd(&mut ops, PartitionName::InitrdDebug, initrd).is_ok() {
313 (DebugLevel::Full, hash_descriptors.initrd_debug)
314 } else {
315 return Err(SlotVerifyError::Verification(None).into());
316 };
317 let initrd_descriptor = initrd_descriptor.ok_or(DescriptorError::InvalidContents)?;
318 Ok(VerifiedBootData {
319 debug_level,
320 kernel_digest: copy_digest(hash_descriptors.kernel)?,
321 initrd_digest: Some(copy_digest(initrd_descriptor)?),
322 public_key: trusted_public_key,
323 capabilities,
324 rollback_index,
325 page_size,
326 name,
327 })
328 }
329