xref: /aosp_15_r20/bootable/libbootloader/gbl/libgbl/src/fuchsia_boot/vboot.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1 // Copyright 2024, 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::{
16     fuchsia_boot::{zbi_split_unused_buffer, zircon_part_name, SlotIndex},
17     gbl_avb::ops::GblAvbOps,
18     gbl_print, GblOps, Result as GblResult,
19 };
20 use avb::{slot_verify, Descriptor, HashtreeErrorMode, Ops as _, SlotVerifyError, SlotVerifyFlags};
21 use core::ffi::CStr;
22 use zbi::ZbiContainer;
23 use zerocopy::ByteSliceMut;
24 
25 /// Helper for getting the A/B/R suffix.
slot_suffix(slot: Option<SlotIndex>) -> Option<&'static CStr>26 fn slot_suffix(slot: Option<SlotIndex>) -> Option<&'static CStr> {
27     Some(match slot? {
28         SlotIndex::A => c"_a",
29         SlotIndex::B => c"_b",
30         SlotIndex::R => c"_r",
31     })
32 }
33 
34 /// Verifies a loaded ZBI kernel.
35 ///
36 /// # Arguments
37 ///
38 /// * glb_ops - GblOps implementation
39 /// * slot - slot to verify
40 /// * slot_booted_successfully - if true, roll back indexes will be increased
41 /// * zbi_kernel - preloaded kernel to verify
42 /// * zbi_items - vbmeta items will be appended to this ZbiContainer
zircon_verify_kernel<'a, 'b, 'c, B: ByteSliceMut + PartialEq>( gbl_ops: &mut impl GblOps<'b, 'c>, slot: Option<SlotIndex>, slot_booted_successfully: bool, zbi_kernel: &'a mut [u8], zbi_items: &mut ZbiContainer<B>, ) -> GblResult<()>43 pub(crate) fn zircon_verify_kernel<'a, 'b, 'c, B: ByteSliceMut + PartialEq>(
44     gbl_ops: &mut impl GblOps<'b, 'c>,
45     slot: Option<SlotIndex>,
46     slot_booted_successfully: bool,
47     zbi_kernel: &'a mut [u8],
48     zbi_items: &mut ZbiContainer<B>,
49 ) -> GblResult<()> {
50     let (kernel, _) = zbi_split_unused_buffer(&mut zbi_kernel[..])?;
51 
52     // Verifies the kernel.
53     let part = zircon_part_name(slot);
54     let preloaded = [(part, &kernel[..])];
55     let mut avb_ops = GblAvbOps::new(gbl_ops, &preloaded[..], true);
56 
57     // Determines verify flags and error mode.
58     let unlocked = avb_ops.read_is_device_unlocked()?;
59     let mode = HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO; // Don't care for fuchsia
60     let flag = match unlocked {
61         true => SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR,
62         _ => SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
63     };
64 
65     // TODO(b/334962583): Supports optional additional partitions to verify.
66     let verify_res = slot_verify(&mut avb_ops, &[c"zircon"], slot_suffix(slot), flag, mode);
67     let verified_success = verify_res.is_ok();
68     let verify_data = match verify_res {
69         Ok(v) => {
70             gbl_print!(avb_ops.gbl_ops, "{} successfully verified.\r\n", part);
71             v
72         }
73         Err(SlotVerifyError::Verification(Some(v))) if unlocked => {
74             gbl_print!(avb_ops.gbl_ops, "Verification failed. Device is unlocked. Ignore.\r\n");
75             v
76         }
77         Err(_) if unlocked => {
78             gbl_print!(
79                 avb_ops.gbl_ops,
80                 "Verification failed. No valid verify metadata. \
81                     Device is unlocked. Ignore.\r\n"
82             );
83             return Ok(());
84         }
85         Err(e) => {
86             gbl_print!(avb_ops.gbl_ops, "Verification failed {:?}.\r\n", e);
87             return Err(e.without_verify_data().into());
88         }
89     };
90 
91     // Collects ZBI items from vbmetadata and appends to the `zbi_items`.
92     for vbmeta_data in verify_data.vbmeta_data() {
93         for prop in vbmeta_data.descriptors()?.iter().filter_map(|d| match d {
94             Descriptor::Property(p) if p.key.starts_with("zbi") => Some(p),
95             _ => None,
96         }) {
97             zbi_items.extend_unaligned(prop.value)?;
98         }
99     }
100 
101     // Increases rollback indices if the slot has successfully booted.
102     if verified_success && slot_booted_successfully {
103         for (loc, val) in verify_data.rollback_indexes().iter().enumerate() {
104             if *val > 0 && avb_ops.read_rollback_index(loc)? != *val {
105                 avb_ops.write_rollback_index(loc, *val)?;
106             }
107         }
108 
109         // Increases rollback index values for Fuchsia key version locations.
110         for key_version in avb_ops.key_versions {
111             match key_version {
112                 Some((loc, rollback)) if avb_ops.read_rollback_index(loc)? != rollback => {
113                     avb_ops.write_rollback_index(loc, rollback)?;
114                 }
115                 _ => {}
116             }
117         }
118     }
119 
120     Ok(())
121 }
122 
123 /// Copy ZBI items following kernel to separate container.
copy_items_after_kernel<'a, B: ByteSliceMut + PartialEq>( zbi_kernel: &'a mut [u8], zbi_items: &mut ZbiContainer<B>, ) -> GblResult<()>124 pub fn copy_items_after_kernel<'a, B: ByteSliceMut + PartialEq>(
125     zbi_kernel: &'a mut [u8],
126     zbi_items: &mut ZbiContainer<B>,
127 ) -> GblResult<()> {
128     let zbi_container = ZbiContainer::parse(&mut zbi_kernel[..])?;
129     let mut items_iter = zbi_container.iter();
130     items_iter.next(); // Skip first kernel item
131     zbi_items.extend_items(items_iter)?;
132     Ok(())
133 }
134 
135 #[cfg(test)]
136 mod test {
137     use super::*;
138     use crate::fuchsia_boot::{
139         test::{
140             append_cmd_line, corrupt_data, create_gbl_ops, create_storage, normalize_zbi,
141             read_test_data, AlignedBuffer, ZIRCON_A_ZBI_FILE,
142         },
143         ZIRCON_KERNEL_ALIGN,
144     };
145     use avb_bindgen::{AVB_CERT_PIK_VERSION_LOCATION, AVB_CERT_PSK_VERSION_LOCATION};
146     use zbi::ZBI_ALIGNMENT_USIZE;
147 
148     // The cert test keys were both generated with rollback version 42.
149     const TEST_CERT_PIK_VERSION: u64 = 42;
150     const TEST_CERT_PSK_VERSION: u64 = 42;
151 
152     #[test]
test_verify_success()153     fn test_verify_success() {
154         let storage = create_storage();
155         let mut ops = create_gbl_ops(&storage);
156 
157         let expect_rollback = ops.avb_ops.rollbacks.clone();
158         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
159         let mut load_buffer = AlignedBuffer::new(zbi.len(), ZIRCON_KERNEL_ALIGN);
160         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
161         let mut zbi_items = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
162         load_buffer[..zbi.len()].clone_from_slice(zbi);
163         zircon_verify_kernel(&mut ops, Some(SlotIndex::A), false, &mut load_buffer, &mut zbi_items)
164             .unwrap();
165 
166         // Verifies that vbmeta ZBI items are appended. Non-zbi items are ignored.
167         let mut expected_zbi_items = AlignedBuffer::new(zbi.len() + 1024, 8);
168         let _ = ZbiContainer::new(&mut expected_zbi_items[..]).unwrap();
169         append_cmd_line(&mut expected_zbi_items, b"vb_prop_0=val\0");
170         append_cmd_line(&mut expected_zbi_items, b"vb_prop_1=val\0");
171         assert_eq!(normalize_zbi(&zbi_items_buffer), normalize_zbi(&expected_zbi_items));
172 
173         // Slot is not successful, rollback index should not be updated.
174         assert_eq!(expect_rollback, ops.avb_ops.rollbacks);
175     }
176 
177     #[test]
test_verify_update_rollback_index_for_successful_slot()178     fn test_verify_update_rollback_index_for_successful_slot() {
179         let storage = create_storage();
180         let mut ops = create_gbl_ops(&storage);
181 
182         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
183         let mut load_buffer = AlignedBuffer::new(zbi.len(), ZIRCON_KERNEL_ALIGN);
184         load_buffer[..zbi.len()].clone_from_slice(zbi);
185         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
186         let mut zbi_items = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
187         zircon_verify_kernel(&mut ops, Some(SlotIndex::A), true, &mut load_buffer, &mut zbi_items)
188             .unwrap();
189 
190         // Slot is successful, rollback index should be updated.
191         // vbmeta_a has rollback index value 2 at location 1.
192         assert_eq!(
193             ops.avb_ops.rollbacks,
194             [
195                 (1, Ok(2)),
196                 (
197                     usize::try_from(AVB_CERT_PSK_VERSION_LOCATION).unwrap(),
198                     Ok(TEST_CERT_PSK_VERSION)
199                 ),
200                 (
201                     usize::try_from(AVB_CERT_PIK_VERSION_LOCATION).unwrap(),
202                     Ok(TEST_CERT_PIK_VERSION)
203                 )
204             ]
205             .into()
206         );
207     }
208 
209     #[test]
test_verify_failed_on_corrupted_image()210     fn test_verify_failed_on_corrupted_image() {
211         let storage = create_storage();
212         let mut ops = create_gbl_ops(&storage);
213 
214         let expect_rollback = ops.avb_ops.rollbacks.clone();
215         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
216         let mut load_buffer = AlignedBuffer::new(zbi.len(), ZIRCON_KERNEL_ALIGN);
217         load_buffer[..zbi.len()].clone_from_slice(zbi);
218         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
219         let mut zbi_items = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
220         // Corrupts a random kernel bytes. Skips pass two ZBI headers.
221         load_buffer[64] = !load_buffer[64];
222         let expect_load = load_buffer.to_vec();
223         assert!(zircon_verify_kernel(
224             &mut ops,
225             Some(SlotIndex::A),
226             true,
227             &mut load_buffer,
228             &mut zbi_items
229         )
230         .is_err());
231         // Failed while device is locked. ZBI items should not be appended.
232         assert_eq!(expect_load, &load_buffer[..]);
233         // Rollback index should not be updated on verification failure.
234         assert_eq!(expect_rollback, ops.avb_ops.rollbacks);
235     }
236 
237     #[test]
test_verify_failed_on_corrupted_vbmetadata()238     fn test_verify_failed_on_corrupted_vbmetadata() {
239         let storage = create_storage();
240         let mut ops = create_gbl_ops(&storage);
241 
242         let expect_rollback = ops.avb_ops.rollbacks.clone();
243         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
244         let mut load = AlignedBuffer::new(zbi.len(), ZIRCON_KERNEL_ALIGN);
245         load[..zbi.len()].clone_from_slice(zbi);
246         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
247         let mut zbi_items = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
248         let expect_load = load.to_vec();
249         // Corrupts vbmetadata
250         corrupt_data(&mut ops, "vbmeta_a");
251         assert!(zircon_verify_kernel(
252             &mut ops,
253             Some(SlotIndex::A),
254             true,
255             &mut load,
256             &mut zbi_items
257         )
258         .is_err());
259         // Failed while device is locked. ZBI items should not be appended.
260         assert_eq!(expect_load, &load[..]);
261         // Rollback index should not be updated on verification failure.
262         assert_eq!(expect_rollback, ops.avb_ops.rollbacks);
263     }
264 
265     #[test]
test_verify_failed_on_rollback_protection()266     fn test_verify_failed_on_rollback_protection() {
267         let storage = create_storage();
268         let mut ops = create_gbl_ops(&storage);
269 
270         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
271         let mut load_buffer = AlignedBuffer::new(zbi.len(), ZIRCON_KERNEL_ALIGN);
272         load_buffer[..zbi.len()].clone_from_slice(zbi);
273         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
274         let mut zbi_items = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
275         let expect_load = load_buffer.to_vec();
276         // vbmeta_a has rollback index value 2 at location 1. Setting min rollback value of 3 should
277         // cause rollback protection failure.
278         ops.avb_ops.rollbacks.insert(1, Ok(3));
279         let expect_rollback = ops.avb_ops.rollbacks.clone();
280         assert!(zircon_verify_kernel(
281             &mut ops,
282             Some(SlotIndex::A),
283             true,
284             &mut load_buffer,
285             &mut zbi_items
286         )
287         .is_err());
288         // Failed while device is locked. ZBI items should not be appended.
289         assert_eq!(expect_load, &load_buffer[..]);
290         // Rollback index should not be updated on verification failure.
291         assert_eq!(expect_rollback, ops.avb_ops.rollbacks);
292     }
293 
294     #[test]
test_verify_failure_when_unlocked()295     fn test_verify_failure_when_unlocked() {
296         let storage = create_storage();
297         let mut ops = create_gbl_ops(&storage);
298 
299         ops.avb_ops.unlock_state = Ok(true);
300         let expect_rollback = ops.avb_ops.rollbacks.clone();
301 
302         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
303         let mut load_buffer = AlignedBuffer::new(zbi.len(), ZIRCON_KERNEL_ALIGN);
304         load_buffer[..zbi.len()].clone_from_slice(zbi);
305         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
306         let mut zbi_items = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
307         // Corrupts a random kernel bytes. Skips pass two ZBI headers.
308         load_buffer[64] = !load_buffer[64];
309         // Verification should proceeds OK.
310         zircon_verify_kernel(&mut ops, Some(SlotIndex::A), true, &mut load_buffer, &mut zbi_items)
311             .unwrap();
312         // Verifies that vbmeta ZBI items are appended as long as unlocked.
313         let mut expected_zbi_items = AlignedBuffer::new(load_buffer.len(), ZBI_ALIGNMENT_USIZE);
314         let _ = ZbiContainer::new(&mut expected_zbi_items[..]).unwrap();
315         append_cmd_line(&mut expected_zbi_items, b"vb_prop_0=val\0");
316         append_cmd_line(&mut expected_zbi_items, b"vb_prop_1=val\0");
317         assert_eq!(normalize_zbi(&zbi_items_buffer), normalize_zbi(&expected_zbi_items));
318         // Rollback index should not be updated in any failure cases, even when unlocked.
319         assert_eq!(expect_rollback, ops.avb_ops.rollbacks);
320     }
321 
322     #[test]
test_copy_items_after_kernel()323     fn test_copy_items_after_kernel() {
324         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
325         let mut load_buffer = AlignedBuffer::new(zbi.len() + 1024, ZIRCON_KERNEL_ALIGN);
326         load_buffer[..zbi.len()].clone_from_slice(zbi);
327         // Add items that will be copied
328         append_cmd_line(&mut load_buffer, b"vb_prop_0=val\0");
329         append_cmd_line(&mut load_buffer, b"vb_prop_1=val\0");
330 
331         // Create ZBI items container that contain 1 element
332         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
333         let _ = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
334         append_cmd_line(&mut zbi_items_buffer, b"vb_prop_2=val\0");
335         let mut zbi_items = ZbiContainer::parse(&mut zbi_items_buffer[..]).unwrap();
336 
337         // Verifies that ZBI items are appended
338         let mut expected_zbi_items = AlignedBuffer::new(load_buffer.len(), ZBI_ALIGNMENT_USIZE);
339         let _ = ZbiContainer::new(&mut expected_zbi_items[..]).unwrap();
340         append_cmd_line(&mut expected_zbi_items, b"vb_prop_2=val\0");
341         append_cmd_line(&mut expected_zbi_items, b"vb_prop_0=val\0");
342         append_cmd_line(&mut expected_zbi_items, b"vb_prop_1=val\0");
343 
344         copy_items_after_kernel(&mut load_buffer, &mut zbi_items).unwrap();
345         assert_eq!(normalize_zbi(&zbi_items_buffer), normalize_zbi(&expected_zbi_items));
346     }
347 
348     #[test]
test_verify_failure_by_corrupted_vbmetadata_unlocked()349     fn test_verify_failure_by_corrupted_vbmetadata_unlocked() {
350         let storage = create_storage();
351         let mut ops = create_gbl_ops(&storage);
352 
353         ops.avb_ops.unlock_state = Ok(true);
354         let expect_rollback = ops.avb_ops.rollbacks.clone();
355         let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
356         let mut load_buffer = AlignedBuffer::new(zbi.len(), ZIRCON_KERNEL_ALIGN);
357         load_buffer[..zbi.len()].clone_from_slice(zbi);
358         let mut zbi_items_buffer = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
359         let mut zbi_items = ZbiContainer::new(&mut zbi_items_buffer[..]).unwrap();
360         let expect_load = load_buffer.to_vec();
361         // Corrupts vbmetadata
362         corrupt_data(&mut ops, "vbmeta_a");
363         zircon_verify_kernel(&mut ops, Some(SlotIndex::A), true, &mut load_buffer, &mut zbi_items)
364             .unwrap();
365         // Unlocked but vbmetadata is invalid so no ZBI items should be appended.
366         assert_eq!(expect_load, &load_buffer[..]);
367         // Rollback index should not be updated on verification failure.
368         assert_eq!(expect_rollback, ops.avb_ops.rollbacks);
369     }
370 }
371