• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 //! This file provides APIs for loading, verifying and booting Fuchsia/Zircon.
16 
17 use crate::{gbl_print, gbl_println, image_buffer::ImageBuffer, GblOps, Result as GblResult};
18 pub use abr::{get_and_clear_one_shot_bootloader, get_boot_slot, Ops as AbrOps, SlotIndex};
19 use core::{fmt::Write, mem::MaybeUninit, num::NonZeroUsize};
20 use liberror::{Error, Result};
21 use libutils::aligned_subslice;
22 use safemath::SafeNum;
23 use zbi::{ZbiContainer, ZbiFlags, ZbiHeader, ZbiType};
24 use zerocopy::IntoBytes;
25 
26 mod vboot;
27 use vboot::zircon_verify_kernel;
28 
29 /// Kernel load address alignment. Value taken from
30 /// https://fuchsia.googlesource.com/fuchsia/+/4f204d8a0243e84a86af4c527a8edcc1ace1615f/zircon/kernel/target/arm64/boot-shim/BUILD.gn#38
31 pub const ZIRCON_KERNEL_ALIGN: usize = 64 * 1024;
32 
33 const DURABLE_BOOT_PARTITION: &str = "durable_boot";
34 const MISC_PARTITION: &str = "misc";
35 const ABR_PARTITION_ALIASES: &[&str] = &[DURABLE_BOOT_PARTITION, MISC_PARTITION];
36 
37 /// Helper function to find partition given a list of possible aliases.
find_part_aliases<'a, 'b, 'c>( ops: &mut (impl GblOps<'a, 'c> + ?Sized), aliases: &'b [&str], ) -> Result<&'b str>38 fn find_part_aliases<'a, 'b, 'c>(
39     ops: &mut (impl GblOps<'a, 'c> + ?Sized),
40     aliases: &'b [&str],
41 ) -> Result<&'b str> {
42     Ok(*aliases
43         .iter()
44         .find(|v| matches!(ops.partition_size(v), Ok(Some(_))))
45         .ok_or(Error::NotFound)?)
46 }
47 
48 /// `GblAbrOps` wraps an object implementing `GblOps` and implements the `abr::Ops` trait.
49 pub(crate) struct GblAbrOps<'a, T: ?Sized>(pub &'a mut T);
50 
51 impl<'b, 'c, T: GblOps<'b, 'c> + ?Sized> AbrOps for GblAbrOps<'_, T> {
read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()>52     fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()> {
53         let part = find_part_aliases(self.0, &ABR_PARTITION_ALIASES)?;
54         self.0.read_from_partition_sync(part, 0, out)
55     }
56 
write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()>57     fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()> {
58         let part = find_part_aliases(self.0, &ABR_PARTITION_ALIASES)?;
59         self.0.write_to_partition_sync(part, 0, data)
60     }
61 
console(&mut self) -> Option<&mut dyn Write>62     fn console(&mut self) -> Option<&mut dyn Write> {
63         self.0.console_out()
64     }
65 }
66 
67 /// A helper for splitting the trailing unused portion of a ZBI container buffer.
68 ///
69 /// Returns a tuple of used subslice and unused subslice
zbi_split_unused_buffer(zbi: &mut [u8]) -> GblResult<(&mut [u8], &mut [u8])>70 fn zbi_split_unused_buffer(zbi: &mut [u8]) -> GblResult<(&mut [u8], &mut [u8])> {
71     Ok(zbi.split_at_mut(ZbiContainer::parse(&zbi[..])?.container_size()?))
72 }
73 
74 /// Relocates a ZBI kernel to a different buffer.
75 ///
76 /// * `dest` must be aligned to `ZIRCON_KERNEL_ALIGN`.
77 /// * `dest` will be a ZBI container containing only the kernel item.
relocate_kernel(kernel: &[u8], dest: &mut [u8]) -> GblResult<()>78 pub fn relocate_kernel(kernel: &[u8], dest: &mut [u8]) -> GblResult<()> {
79     if (dest.as_ptr() as usize % ZIRCON_KERNEL_ALIGN) != 0 {
80         return Err(Error::InvalidAlignment.into());
81     }
82 
83     let kernel = ZbiContainer::parse(&kernel[..])?;
84     let kernel_item = kernel.get_bootable_kernel_item()?;
85     let hdr = kernel_item.header;
86     // Creates a new ZBI kernel item at the destination.
87     let mut relocated = ZbiContainer::new(&mut dest[..])?;
88     let zbi_type = ZbiType::try_from(hdr.type_)?;
89     relocated.create_entry_with_payload(
90         zbi_type,
91         hdr.extra,
92         hdr.get_flags() & !ZbiFlags::CRC32,
93         kernel_item.payload.as_bytes(),
94     )?;
95     let (_, reserved_memory_size) = relocated.get_kernel_entry_and_reserved_memory_size()?;
96     let buf_len = u64::try_from(zbi_split_unused_buffer(dest)?.1.len()).map_err(Error::from)?;
97     match reserved_memory_size > buf_len {
98         true => Err(Error::BufferTooSmall(None).into()),
99         _ => Ok(()),
100     }
101 }
102 
103 /// Relocate a ZBI kernel to the trailing unused buffer.
104 ///
105 /// Returns the original kernel subslice and relocated kernel subslice.
relocate_to_tail(kernel: &mut [u8]) -> GblResult<(&mut [u8], &mut [u8])>106 pub fn relocate_to_tail(kernel: &mut [u8]) -> GblResult<(&mut [u8], &mut [u8])> {
107     let reloc_size = ZbiContainer::parse(&kernel[..])?.get_buffer_size_for_kernel_relocation()?;
108     let (original, relocated) = zbi_split_unused_buffer(kernel)?;
109     let relocated = aligned_subslice(relocated, ZIRCON_KERNEL_ALIGN)?;
110     let off = (SafeNum::from(relocated.len()) - reloc_size)
111         .round_down(ZIRCON_KERNEL_ALIGN)
112         .try_into()
113         .map_err(Error::from)?;
114     let relocated = &mut relocated[off..];
115     relocate_kernel(original, relocated)?;
116     let reloc_addr = relocated.as_ptr() as usize;
117     Ok(kernel.split_at_mut(reloc_addr.checked_sub(kernel.as_ptr() as usize).unwrap()))
118 }
119 
120 /// Gets the list of aliases for slotted/slotless zircon partition name.
zircon_part_name_aliases(slot: Option<SlotIndex>) -> &'static [&'static str]121 fn zircon_part_name_aliases(slot: Option<SlotIndex>) -> &'static [&'static str] {
122     match slot {
123         Some(SlotIndex::A) => &["zircon_a", "zircon-a"][..],
124         Some(SlotIndex::B) => &["zircon_b", "zircon-b"][..],
125         Some(SlotIndex::R) => &["zircon_r", "zircon-r"][..],
126         _ => &["zircon"][..],
127     }
128 }
129 
130 /// Gets the slotted/slotless standard zircon partition name.
zircon_part_name(slot: Option<SlotIndex>) -> &'static str131 pub fn zircon_part_name(slot: Option<SlotIndex>) -> &'static str {
132     zircon_part_name_aliases(slot)[0]
133 }
134 
135 /// Gets the ZBI command line string for the current slot.
slot_cmd_line(slot: SlotIndex) -> &'static str136 fn slot_cmd_line(slot: SlotIndex) -> &'static str {
137     match slot {
138         SlotIndex::A => "zvb.current_slot=a",
139         SlotIndex::B => "zvb.current_slot=b",
140         SlotIndex::R => "zvb.current_slot=r",
141     }
142 }
143 
144 /// Loads and verifies a kernel of the given slot or slotless.
145 ///
146 /// # Args
147 ///
148 /// * `ops`: A reference to an object that implements `GblOps`.
149 /// * `slot`: None if slotless. Otherwise the target slot to boot.
150 /// * `slot_booted_successfully`: whether the slot is known-successful boot, and if so then this
151 /// function will update the anti-rollbacks.
152 ///
153 /// On success returns a pair containing: 1. the slice of the ZBI container with device ZBI items
154 /// and 2. the slice of container containing the kernel.
zircon_load_verify<'a, 'd>( ops: &mut impl GblOps<'a, 'd>, slot: Option<SlotIndex>, slot_booted_successfully: bool, ) -> GblResult<(ImageBuffer<'d>, ImageBuffer<'d>)>155 pub fn zircon_load_verify<'a, 'd>(
156     ops: &mut impl GblOps<'a, 'd>,
157     slot: Option<SlotIndex>,
158     slot_booted_successfully: bool,
159 ) -> GblResult<(ImageBuffer<'d>, ImageBuffer<'d>)> {
160     // TODO(b/379778252): use single `zbi_zircon` buffer for container to store both kernel and
161     // arguments/items
162     let mut zbi_items_img =
163         ops.get_image_buffer("zbi_items", NonZeroUsize::new(64 * 1024 * 1024).unwrap()).unwrap();
164 
165     let init_len = zbi_items_img.tail().len();
166     // TODO(b/379787423): it is possible to optimize this initialisation by treating
167     // `zbi_items_img` same as kernel image (&[MaybeUninit]).
168     MaybeUninit::fill(zbi_items_img.tail(), 0);
169 
170     // SAFETY: buffer was fully filled with 0 which is valid init value for u8
171     unsafe {
172         zbi_items_img.advance_used(init_len).unwrap();
173     }
174     let mut zbi_items = ZbiContainer::new(zbi_items_img.used_mut())?;
175 
176     let zircon_part = find_part_aliases(ops, zircon_part_name_aliases(slot))?;
177 
178     // Reads ZBI header to computes the total size of kernel.
179     let mut zbi_header: ZbiHeader = Default::default();
180     ops.read_from_partition_sync(zircon_part, 0, zbi_header.as_bytes_mut())?;
181     let image_length = (SafeNum::from(zbi_header.as_bytes_mut().len()) + zbi_header.length)
182         .try_into()
183         .map_err(Error::from)?;
184 
185     // Reads the entire kernel
186     // TODO(b/379778252): as part of an attempt to use single container for kernel and arguments,
187     // it would be necessary to read kernel header first to figure out how much space needed
188     // (kernel size + scratch space)
189     let mut kernel_img =
190         ops.get_image_buffer("zbi_zircon", NonZeroUsize::new(128 * 1024 * 1024).unwrap()).unwrap();
191     let kernel_uninit = kernel_img
192         .as_mut()
193         .get_mut(..image_length)
194         .ok_or(Error::BufferTooSmall(Some(image_length)))?;
195     ops.read_from_partition_sync(zircon_part, 0, kernel_uninit)?;
196     // SAFETY: buffer was successfully filled from partition
197     unsafe {
198         kernel_img.advance_used(image_length).unwrap();
199     }
200     let load = kernel_img.used_mut();
201 
202     // Performs AVB verification.
203     // TODO(b/379789161) verify that kernel buffer is big enough for the image and scratch buffer.
204     zircon_verify_kernel(ops, slot, slot_booted_successfully, load, &mut zbi_items)?;
205 
206     // Append additional ZBI items.
207     match slot {
208         Some(slot) => {
209             // Appends current slot item.
210             zbi_items.create_entry_with_payload(
211                 ZbiType::CmdLine,
212                 0,
213                 ZbiFlags::default(),
214                 slot_cmd_line(slot).as_bytes(),
215             )?;
216         }
217         _ => {}
218     }
219 
220     // Appends device specific ZBI items.
221     ops.zircon_add_device_zbi_items(&mut zbi_items)?;
222 
223     // Appends staged bootloader file if present.
224     match ops.get_zbi_bootloader_files_buffer_aligned().map(|v| ZbiContainer::parse(v)) {
225         Some(Ok(v)) => zbi_items.extend(&v)?,
226         _ => {}
227     }
228 
229     Ok((zbi_items_img, kernel_img))
230 }
231 
232 /// Loads and verifies the active slot kernel according to A/B/R.
233 ///
234 /// On disk A/B/R metadata will be updated.
235 ///
236 /// # Args
237 ///
238 /// * `ops`: A reference to an object that implements `GblOps`.
239 ///
240 /// Returns a tuple containing: 1. the slice of the ZBI container with device ZBI items, 2. the
241 /// slice of the relocated kernel, and 3. the selected slot index.
zircon_load_verify_abr<'a, 'd>( ops: &mut impl GblOps<'a, 'd>, ) -> GblResult<(ImageBuffer<'d>, ImageBuffer<'d>, SlotIndex)>242 pub fn zircon_load_verify_abr<'a, 'd>(
243     ops: &mut impl GblOps<'a, 'd>,
244 ) -> GblResult<(ImageBuffer<'d>, ImageBuffer<'d>, SlotIndex)> {
245     let (slot, successful) = get_boot_slot(&mut GblAbrOps(ops), true);
246     gbl_println!(ops, "Loading kernel from {}...", zircon_part_name(Some(slot)));
247     let (zbi_items_img, kernel_img) = zircon_load_verify(ops, Some(slot), successful)?;
248     gbl_println!(ops, "Successfully loaded slot: {}", zircon_part_name(Some(slot)));
249     Ok((zbi_items_img, kernel_img, slot))
250 }
251 
252 /// Checks whether platform or A/B/R metadata instructs GBL to boot into fastboot mode.
253 ///
254 /// # Returns
255 ///
256 /// Returns true if fastboot mode is enabled, false if not.
zircon_check_enter_fastboot<'a, 'b>(ops: &mut impl GblOps<'a, 'b>) -> bool257 pub fn zircon_check_enter_fastboot<'a, 'b>(ops: &mut impl GblOps<'a, 'b>) -> bool {
258     match get_and_clear_one_shot_bootloader(&mut GblAbrOps(ops)) {
259         Ok(true) => {
260             gbl_println!(ops, "A/B/R one-shot-bootloader is set");
261             return true;
262         }
263         Err(e) => {
264             gbl_println!(ops, "Warning: error while checking A/B/R one-shot-bootloader ({:?})", e);
265             gbl_println!(ops, "Ignoring error and considered not set");
266         }
267         _ => {}
268     };
269 
270     match ops.should_stop_in_fastboot() {
271         Ok(true) => {
272             gbl_println!(ops, "Platform instructs GBL to enter fastboot mode");
273             return true;
274         }
275         Err(e) => {
276             gbl_println!(ops, "Warning: error while checking platform fastboot trigger ({:?})", e);
277             gbl_println!(ops, "Ignoring error and considered not triggered");
278         }
279         _ => {}
280     };
281     false
282 }
283 
284 #[cfg(test)]
285 mod test {
286     use super::*;
287     use crate::{
288         ops::{
289             test::{FakeGblOps, FakeGblOpsStorage, TestGblDisk},
290             CertPermanentAttributes,
291         },
292         tests::AlignedBuffer,
293     };
294     use abr::{
295         mark_slot_active, mark_slot_unbootable, set_one_shot_bootloader, ABR_MAX_TRIES_REMAINING,
296     };
297     use avb_bindgen::{AVB_CERT_PIK_VERSION_LOCATION, AVB_CERT_PSK_VERSION_LOCATION};
298     use gbl_storage::as_uninit_mut;
299     use std::{
300         collections::{BTreeSet, HashMap, LinkedList},
301         fs,
302         path::Path,
303     };
304     use zbi::ZBI_ALIGNMENT_USIZE;
305     use zerocopy::FromBytes;
306 
307     // The cert test keys were both generated with rollback version 42.
308     const TEST_CERT_PIK_VERSION: u64 = 42;
309     const TEST_CERT_PSK_VERSION: u64 = 42;
310 
311     // The `reserve_memory_size` value in the test ZBI kernel.
312     // See `gen_zircon_test_images()` in libgbl/testdata/gen_test_data.py.
313     const TEST_KERNEL_RESERVED_MEMORY_SIZE: usize = 1024;
314 
315     // The rollback index value and location in the generated test vbmetadata.
316     // See `gen_zircon_test_images()` in libgbl/testdata/gen_test_data.py.
317     const TEST_ROLLBACK_INDEX_LOCATION: usize = 1;
318     const TEST_ROLLBACK_INDEX_VALUE: u64 = 2;
319 
320     pub(crate) const ZIRCON_A_ZBI_FILE: &str = "zircon_a.zbi";
321     pub(crate) const ZIRCON_B_ZBI_FILE: &str = "zircon_b.zbi";
322     pub(crate) const ZIRCON_R_ZBI_FILE: &str = "zircon_r.zbi";
323     pub(crate) const ZIRCON_SLOTLESS_ZBI_FILE: &str = "zircon_slotless.zbi";
324     pub(crate) const VBMETA_A_FILE: &str = "vbmeta_a.bin";
325     pub(crate) const VBMETA_B_FILE: &str = "vbmeta_b.bin";
326     pub(crate) const VBMETA_R_FILE: &str = "vbmeta_r.bin";
327     pub(crate) const VBMETA_SLOTLESS_FILE: &str = "vbmeta_slotless.bin";
328 
329     /// Reads a data file under libgbl/testdata/
read_test_data(file: &str) -> Vec<u8>330     pub(crate) fn read_test_data(file: &str) -> Vec<u8> {
331         fs::read(Path::new(format!("external/gbl/libgbl/testdata/{}", file).as_str())).unwrap()
332     }
333 
334     /// Returns a default [FakeGblOpsStorage] with valid test images.
335     ///
336     /// Rather than the typical use case of partitions on a single GPT device, this structures data
337     /// as separate raw single-partition devices. This is easier for tests since we don't need to
338     /// generate a GPT, and should be functionally equivalent since our code looks for partitions
339     /// on all devices.
create_storage() -> FakeGblOpsStorage340     pub(crate) fn create_storage() -> FakeGblOpsStorage {
341         let mut storage = FakeGblOpsStorage::default();
342         storage.add_raw_device(c"zircon_a", read_test_data(ZIRCON_A_ZBI_FILE));
343         storage.add_raw_device(c"zircon_b", read_test_data(ZIRCON_B_ZBI_FILE));
344         storage.add_raw_device(c"zircon_r", read_test_data(ZIRCON_R_ZBI_FILE));
345         storage.add_raw_device(c"zircon", read_test_data(ZIRCON_SLOTLESS_ZBI_FILE));
346         storage.add_raw_device(c"vbmeta_a", read_test_data(VBMETA_A_FILE));
347         storage.add_raw_device(c"vbmeta_b", read_test_data(VBMETA_B_FILE));
348         storage.add_raw_device(c"vbmeta_r", read_test_data(VBMETA_R_FILE));
349         storage.add_raw_device(c"vbmeta", read_test_data(VBMETA_SLOTLESS_FILE));
350         storage.add_raw_device(c"durable_boot", vec![0u8; 64 * 1024]);
351         storage
352     }
353 
354     /// Returns a default [FakeGblOpsStorage] with valid test images and using legacy partition
355     /// names.
create_storage_legacy_names() -> FakeGblOpsStorage356     pub(crate) fn create_storage_legacy_names() -> FakeGblOpsStorage {
357         let mut storage = FakeGblOpsStorage::default();
358         storage.add_raw_device(c"zircon-a", read_test_data(ZIRCON_A_ZBI_FILE));
359         storage.add_raw_device(c"zircon-b", read_test_data(ZIRCON_B_ZBI_FILE));
360         storage.add_raw_device(c"zircon-r", read_test_data(ZIRCON_R_ZBI_FILE));
361         storage.add_raw_device(c"zircon", read_test_data(ZIRCON_SLOTLESS_ZBI_FILE));
362         storage.add_raw_device(c"vbmeta_a", read_test_data(VBMETA_A_FILE));
363         storage.add_raw_device(c"vbmeta_b", read_test_data(VBMETA_B_FILE));
364         storage.add_raw_device(c"vbmeta_r", read_test_data(VBMETA_R_FILE));
365         storage.add_raw_device(c"vbmeta", read_test_data(VBMETA_SLOTLESS_FILE));
366         storage.add_raw_device(c"misc", vec![0u8; 64 * 1024]);
367         storage
368     }
369 
create_gbl_ops<'a>(partitions: &'a [TestGblDisk]) -> FakeGblOps<'a, 'static>370     pub(crate) fn create_gbl_ops<'a>(partitions: &'a [TestGblDisk]) -> FakeGblOps<'a, 'static> {
371         let mut ops = FakeGblOps::new(&partitions);
372         ops.avb_ops.unlock_state = Ok(false);
373         ops.avb_ops.rollbacks = HashMap::from([
374             (TEST_ROLLBACK_INDEX_LOCATION, Ok(0)),
375             (AVB_CERT_PSK_VERSION_LOCATION.try_into().unwrap(), Ok(0)),
376             (AVB_CERT_PIK_VERSION_LOCATION.try_into().unwrap(), Ok(0)),
377         ]);
378         ops.avb_ops.use_cert = true;
379         ops.avb_ops.cert_permanent_attributes = Some(
380             CertPermanentAttributes::read_from(
381                 &read_test_data("cert_permanent_attributes.bin")[..],
382             )
383             .unwrap(),
384         );
385         ops.avb_ops.cert_permanent_attributes_hash =
386             Some(read_test_data("cert_permanent_attributes.hash").try_into().unwrap());
387         ops
388     }
389 
390     /// Normalizes a ZBI container by converting each ZBI item into raw bytes and storing them in
391     /// an ordered set. The function is mainly used for comparing two ZBI containers have identical
392     /// set of items, disregarding order.
normalize_zbi(zbi: &[u8]) -> BTreeSet<Vec<u8>>393     pub(crate) fn normalize_zbi(zbi: &[u8]) -> BTreeSet<Vec<u8>> {
394         let zbi = ZbiContainer::parse(zbi).unwrap();
395         BTreeSet::from_iter(zbi.iter().map(|v| {
396             let mut hdr = *v.header;
397             hdr.crc32 = 0; // ignores crc32 field.
398             hdr.flags &= !ZbiFlags::CRC32.bits();
399             [hdr.as_bytes(), v.payload.as_bytes()].concat()
400         }))
401     }
402 
403     /// Helper to append a command line ZBI item to a ZBI container
append_cmd_line(zbi: &mut [u8], cmd: &[u8])404     pub(crate) fn append_cmd_line(zbi: &mut [u8], cmd: &[u8]) {
405         let mut container = ZbiContainer::parse(zbi).unwrap();
406         container.create_entry_with_payload(ZbiType::CmdLine, 0, ZbiFlags::default(), cmd).unwrap();
407     }
408 
409     /// Helper to append a command line ZBI item to a ZBI container
append_zbi_file(zbi: &mut [u8], payload: &[u8])410     pub(crate) fn append_zbi_file(zbi: &mut [u8], payload: &[u8]) {
411         let mut container = ZbiContainer::parse(zbi).unwrap();
412         container
413             .create_entry_with_payload(ZbiType::BootloaderFile, 0, ZbiFlags::default(), payload)
414             .unwrap();
415     }
416 
417     /// Helper for testing `zircon_load_verify`.
test_load_verify( ops: &mut FakeGblOps, slot: Option<SlotIndex>, expected_zbi_items: &[u8], expected_kernel: &[u8], )418     fn test_load_verify(
419         ops: &mut FakeGblOps,
420         slot: Option<SlotIndex>,
421         expected_zbi_items: &[u8],
422         expected_kernel: &[u8],
423     ) {
424         let original_rb = ops.avb_ops.rollbacks.clone();
425         // Loads and verifies with unsuccessful slot flag first.
426         let (mut zbi_items, mut kernel) = zircon_load_verify(ops, slot, false).unwrap();
427         // Verifies loaded ZBI kernel/items
428         assert_eq!(normalize_zbi(expected_zbi_items), normalize_zbi(zbi_items.used_mut()));
429         // Verifies kernel
430         assert_eq!(normalize_zbi(expected_kernel), normalize_zbi(kernel.used_mut()));
431         // Kernel is at aligned address
432         assert_eq!(kernel.used_mut().as_ptr() as usize % ZIRCON_KERNEL_ALIGN, 0);
433 
434         // Verifies that the slot successful flag is passed correctly.
435         // Unsuccessful slot, rollback not updated.
436         assert_eq!(ops.avb_ops.rollbacks, original_rb);
437         // Loads and verifies with successful slot flag.
438         zircon_load_verify(ops, slot, true).unwrap();
439         // Successful slot, rollback updated.
440         assert_eq!(
441             ops.avb_ops.rollbacks,
442             [
443                 (TEST_ROLLBACK_INDEX_LOCATION, Ok(TEST_ROLLBACK_INDEX_VALUE)),
444                 (
445                     usize::try_from(AVB_CERT_PSK_VERSION_LOCATION).unwrap(),
446                     Ok(TEST_CERT_PSK_VERSION)
447                 ),
448                 (
449                     usize::try_from(AVB_CERT_PIK_VERSION_LOCATION).unwrap(),
450                     Ok(TEST_CERT_PIK_VERSION)
451                 )
452             ]
453             .into()
454         );
455     }
456 
457     // Helper to create local buffers and convert them to be used as ImageBuffers
458     // This struct owns the buffers, and returns ImageBuffers maps that reference them.
459     //
460     // Tests should make sure to provide enough buffers for all `get_image_buffer()` calls.
461     //
462     struct ImageBuffersPool(LinkedList<(String, Vec<AlignedBuffer>)>);
463 
464     impl ImageBuffersPool {
builder() -> ImageBuffersBuilder465         pub fn builder() -> ImageBuffersBuilder {
466             ImageBuffersBuilder::new()
467         }
468 
469         // number - number of expected get_image_buffer calls. Each call consumes buffers from the
470         // list. If there are not enough it will start returning errors.
471         //
472         // size - size for the buffers
new(number: usize, size: usize) -> Self473         fn new(number: usize, size: usize) -> Self {
474             let mut zbi_items_buffer_vec = Vec::<AlignedBuffer>::new();
475             let mut zbi_zircon_buffer_vec = Vec::<AlignedBuffer>::new();
476             for _ in 0..number {
477                 zbi_zircon_buffer_vec.push(AlignedBuffer::new(size, ZIRCON_KERNEL_ALIGN));
478                 zbi_items_buffer_vec.push(AlignedBuffer::new(size, ZBI_ALIGNMENT_USIZE));
479             }
480 
481             Self(
482                 [
483                     (String::from("zbi_zircon"), zbi_zircon_buffer_vec),
484                     (String::from("zbi_items"), zbi_items_buffer_vec),
485                 ]
486                 .into(),
487             )
488         }
489 
get(&mut self) -> HashMap<String, LinkedList<ImageBuffer>>490         pub fn get(&mut self) -> HashMap<String, LinkedList<ImageBuffer>> {
491             self.0
492                 .iter_mut()
493                 .map(|(key, val_vec)| {
494                     (
495                         key.clone(),
496                         val_vec
497                             .iter_mut()
498                             .map(|e| ImageBuffer::new(as_uninit_mut(e.as_mut())))
499                             .collect(),
500                     )
501                 })
502                 .collect()
503         }
504     }
505 
506     struct ImageBuffersBuilder {
507         // Number of buffers for each image name
508         number: usize,
509         // Size of the buffers
510         size: usize,
511     }
512 
513     /// Tests should make sure to provide enough buffers for all `get_image_buffer()` calls.
514     /// Default number of calls is 1, if more expected use `builder().number(N).build()`
515     /// Default buffer sizes are 2KiB, if different size required use `builder().size(1MiB).build()`
516     impl ImageBuffersBuilder {
new() -> ImageBuffersBuilder517         pub fn new() -> ImageBuffersBuilder {
518             Self { number: 1, size: 2 * 1024 }
519         }
520 
521         /// If more than 1 `get_image_buffer()` call expected `number(N)` should be used to create
522         /// big enough pool of buffers.
number(mut self, number: usize) -> ImageBuffersBuilder523         pub fn number(mut self, number: usize) -> ImageBuffersBuilder {
524             self.number = number;
525             self
526         }
527 
528         /// To change size of buffers use `builder(). size(S).build()`.
size(mut self, size: usize) -> ImageBuffersBuilder529         pub fn size(mut self, size: usize) -> ImageBuffersBuilder {
530             self.size = size;
531             self
532         }
533 
build(self) -> ImageBuffersPool534         pub fn build(self) -> ImageBuffersPool {
535             ImageBuffersPool::new(self.number, self.size)
536         }
537     }
538 
539     #[test]
test_zircon_load_verify_slotless()540     fn test_zircon_load_verify_slotless() {
541         let storage = create_storage();
542         let mut ops = create_gbl_ops(&storage);
543         let mut image_buffers_pool = ImageBuffersPool::builder().number(2).build();
544         ops.image_buffers = image_buffers_pool.get();
545 
546         let zbi = &read_test_data(ZIRCON_SLOTLESS_ZBI_FILE);
547         let expected_kernel = AlignedBuffer::new_with_data(zbi, ZBI_ALIGNMENT_USIZE);
548         // Adds extra bytes for device ZBI items.
549         let mut expected_zbi_items = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
550         let _ = ZbiContainer::new(&mut expected_zbi_items[..]).unwrap();
551         append_cmd_line(&mut expected_zbi_items, FakeGblOps::ADDED_ZBI_COMMANDLINE_CONTENTS);
552         append_cmd_line(&mut expected_zbi_items, b"vb_prop_0=val\0");
553         append_cmd_line(&mut expected_zbi_items, b"vb_prop_1=val\0");
554         append_zbi_file(&mut expected_zbi_items, FakeGblOps::TEST_BOOTLOADER_FILE_1);
555         append_zbi_file(&mut expected_zbi_items, FakeGblOps::TEST_BOOTLOADER_FILE_2);
556         test_load_verify(&mut ops, None, &expected_zbi_items, &expected_kernel);
557     }
558 
559     /// Helper for testing `zircon_load_verify` using A/B/R.
test_load_verify_slotted_helper( ops: &mut FakeGblOps, slot: SlotIndex, zbi: &[u8], slot_item: &str, )560     fn test_load_verify_slotted_helper(
561         ops: &mut FakeGblOps,
562         slot: SlotIndex,
563         zbi: &[u8],
564         slot_item: &str,
565     ) {
566         let expected_kernel = AlignedBuffer::new_with_data(zbi, ZBI_ALIGNMENT_USIZE);
567         // Adds extra bytes for device ZBI items.
568         let mut expected_zbi_items = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
569         let _ = ZbiContainer::new(&mut expected_zbi_items[..]).unwrap();
570         append_cmd_line(&mut expected_zbi_items, FakeGblOps::ADDED_ZBI_COMMANDLINE_CONTENTS);
571         append_cmd_line(&mut expected_zbi_items, b"vb_prop_0=val\0");
572         append_cmd_line(&mut expected_zbi_items, b"vb_prop_1=val\0");
573         append_cmd_line(&mut expected_zbi_items, slot_item.as_bytes());
574         append_zbi_file(&mut expected_zbi_items, FakeGblOps::TEST_BOOTLOADER_FILE_1);
575         append_zbi_file(&mut expected_zbi_items, FakeGblOps::TEST_BOOTLOADER_FILE_2);
576         test_load_verify(ops, Some(slot), &expected_zbi_items, &expected_kernel);
577     }
578 
579     #[test]
test_load_verify_slot_a()580     fn test_load_verify_slot_a() {
581         let storage = create_storage();
582         let mut ops = create_gbl_ops(&storage);
583         let mut image_buffers_pool = ImageBuffersPool::builder().number(2).build();
584         ops.image_buffers = image_buffers_pool.get();
585 
586         let zircon_a_zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
587         test_load_verify_slotted_helper(&mut ops, SlotIndex::A, zircon_a_zbi, "zvb.current_slot=a");
588     }
589 
590     #[test]
test_load_verify_slot_b()591     fn test_load_verify_slot_b() {
592         let storage = create_storage();
593         let mut ops = create_gbl_ops(&storage);
594         let mut image_buffers_pool = ImageBuffersPool::builder().number(2).build();
595         ops.image_buffers = image_buffers_pool.get();
596 
597         let zircon_b_zbi = &read_test_data(ZIRCON_B_ZBI_FILE);
598         test_load_verify_slotted_helper(&mut ops, SlotIndex::B, zircon_b_zbi, "zvb.current_slot=b");
599     }
600 
601     #[test]
test_load_verify_slot_r()602     fn test_load_verify_slot_r() {
603         let storage = create_storage();
604         let mut ops = create_gbl_ops(&storage);
605         let mut image_buffers_pool = ImageBuffersPool::builder().number(2).build();
606         ops.image_buffers = image_buffers_pool.get();
607 
608         let zircon_r_zbi = &read_test_data(ZIRCON_R_ZBI_FILE);
609         test_load_verify_slotted_helper(&mut ops, SlotIndex::R, zircon_r_zbi, "zvb.current_slot=r");
610     }
611 
612     #[test]
test_not_enough_buffer_for_reserved_memory()613     fn test_not_enough_buffer_for_reserved_memory() {
614         let storage = create_storage();
615         let mut ops = create_gbl_ops(&storage);
616         let mut image_buffers_pool = ImageBuffersPool::builder().size(1024).build();
617         ops.image_buffers = image_buffers_pool.get();
618 
619         assert!(zircon_load_verify(&mut ops, Some(SlotIndex::A), true).is_err());
620     }
621 
622     /// A helper for assembling a set of test needed data. These include:
623     ///
624     /// * The original ZBI kernel image on partition `part` in the given `FakeGblOps`.
625     /// * A buffer for loading and verifying the kernel.
626     /// * The expected ZBI item buffer, if successfully loaded as slot index `slot`.
627     /// * The expected ZBI kernel buffer, if successfully loaded.
load_verify_test_data( ops: &mut FakeGblOps, slot: SlotIndex, part: &str, ) -> (Vec<u8>, AlignedBuffer, AlignedBuffer, AlignedBuffer)628     fn load_verify_test_data(
629         ops: &mut FakeGblOps,
630         slot: SlotIndex,
631         part: &str,
632     ) -> (Vec<u8>, AlignedBuffer, AlignedBuffer, AlignedBuffer) {
633         // Read the (possibly modified) ZBI from disk.
634         let zbi = ops.copy_partition(part);
635         let sz = ZIRCON_KERNEL_ALIGN + zbi.len() + TEST_KERNEL_RESERVED_MEMORY_SIZE;
636         let load_buffer = AlignedBuffer::new(sz, ZIRCON_KERNEL_ALIGN);
637         let expected_kernel = AlignedBuffer::new_with_data(&zbi, ZBI_ALIGNMENT_USIZE);
638         // Adds extra bytes for device ZBI items.
639         let mut expected_zbi_items = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
640         let _ = ZbiContainer::new(&mut expected_zbi_items[..]).unwrap();
641         append_cmd_line(&mut expected_zbi_items, FakeGblOps::ADDED_ZBI_COMMANDLINE_CONTENTS);
642         append_cmd_line(&mut expected_zbi_items, b"vb_prop_0=val\0");
643         append_cmd_line(&mut expected_zbi_items, b"vb_prop_1=val\0");
644         append_cmd_line(
645             &mut expected_zbi_items,
646             format!("zvb.current_slot={}", char::from(slot)).as_bytes(),
647         );
648         append_zbi_file(&mut expected_zbi_items, FakeGblOps::TEST_BOOTLOADER_FILE_1);
649         append_zbi_file(&mut expected_zbi_items, FakeGblOps::TEST_BOOTLOADER_FILE_2);
650         (zbi, load_buffer, expected_zbi_items, expected_kernel)
651     }
652 
653     // Calls `zircon_load_verify_abr` and checks that the specified slot is loaded.
expect_load_verify_abr_ok(ops: &mut FakeGblOps, slot: SlotIndex, part: &str)654     fn expect_load_verify_abr_ok(ops: &mut FakeGblOps, slot: SlotIndex, part: &str) {
655         let (_, _load, expected_items, expected_kernel) = load_verify_test_data(ops, slot, part);
656         let (mut zbi_items, mut kernel, active) = zircon_load_verify_abr(ops).unwrap();
657         assert_eq!(normalize_zbi(&expected_items), normalize_zbi(zbi_items.used_mut()));
658         assert_eq!(normalize_zbi(&expected_kernel), normalize_zbi(kernel.used_mut()));
659         assert_eq!(active, slot);
660     }
661 
662     #[test]
test_load_verify_abr_slot_a()663     fn test_load_verify_abr_slot_a() {
664         let storage = create_storage();
665         let mut ops = create_gbl_ops(&storage);
666         let mut image_buffers_pool = ImageBuffersPool::builder().build();
667         ops.image_buffers = image_buffers_pool.get();
668 
669         expect_load_verify_abr_ok(&mut ops, SlotIndex::A, "zircon_a");
670     }
671 
672     #[test]
test_load_verify_abr_slot_b()673     fn test_load_verify_abr_slot_b() {
674         let storage = create_storage();
675         let mut ops = create_gbl_ops(&storage);
676         let mut image_buffers_pool = ImageBuffersPool::builder().build();
677         ops.image_buffers = image_buffers_pool.get();
678 
679         mark_slot_active(&mut GblAbrOps(&mut ops), SlotIndex::B).unwrap();
680         expect_load_verify_abr_ok(&mut ops, SlotIndex::B, "zircon_b");
681     }
682 
683     #[test]
test_load_verify_abr_slot_r()684     fn test_load_verify_abr_slot_r() {
685         let storage = create_storage();
686         let mut ops = create_gbl_ops(&storage);
687         let mut image_buffers_pool = ImageBuffersPool::builder().build();
688         ops.image_buffers = image_buffers_pool.get();
689 
690         mark_slot_unbootable(&mut GblAbrOps(&mut ops), SlotIndex::A).unwrap();
691         mark_slot_unbootable(&mut GblAbrOps(&mut ops), SlotIndex::B).unwrap();
692         expect_load_verify_abr_ok(&mut ops, SlotIndex::R, "zircon_r");
693     }
694 
695     #[test]
test_load_verify_abr_exhaust_retries()696     fn test_load_verify_abr_exhaust_retries() {
697         let storage = create_storage();
698         let mut ops = create_gbl_ops(&storage);
699         let mut image_buffers_pool =
700             ImageBuffersPool::builder().number((1 + 2 * ABR_MAX_TRIES_REMAINING).into()).build();
701         ops.image_buffers = image_buffers_pool.get();
702 
703         for _ in 0..ABR_MAX_TRIES_REMAINING {
704             expect_load_verify_abr_ok(&mut ops, SlotIndex::A, "zircon_a");
705         }
706         for _ in 0..ABR_MAX_TRIES_REMAINING {
707             expect_load_verify_abr_ok(&mut ops, SlotIndex::B, "zircon_b");
708         }
709         // Tests that load falls back to R eventually.
710         expect_load_verify_abr_ok(&mut ops, SlotIndex::R, "zircon_r");
711     }
712 
713     /// Modifies data in the given partition.
corrupt_data(ops: &mut FakeGblOps, part_name: &str)714     pub(crate) fn corrupt_data(ops: &mut FakeGblOps, part_name: &str) {
715         let mut data = [0u8];
716         assert!(ops.read_from_partition_sync(part_name, 64, &mut data[..]).is_ok());
717         data[0] ^= 0x01;
718         assert!(ops.write_to_partition_sync(part_name, 64, &mut data[..]).is_ok());
719     }
720 
721     #[test]
test_load_verify_abr_verify_failure_a_b()722     fn test_load_verify_abr_verify_failure_a_b() {
723         let storage = create_storage();
724         let mut ops = create_gbl_ops(&storage);
725         let mut image_buffers_pool =
726             ImageBuffersPool::builder().number((1 + 2 * ABR_MAX_TRIES_REMAINING).into()).build();
727         ops.image_buffers = image_buffers_pool.get();
728 
729         corrupt_data(&mut ops, "zircon_a");
730         corrupt_data(&mut ops, "zircon_b");
731 
732         let (_, _load, _, _) = load_verify_test_data(&mut ops, SlotIndex::A, "zircon_a");
733         for _ in 0..ABR_MAX_TRIES_REMAINING {
734             assert!(zircon_load_verify_abr(&mut ops).is_err());
735         }
736         let (_, _load, _, _) = load_verify_test_data(&mut ops, SlotIndex::B, "zircon_b");
737         for _ in 0..ABR_MAX_TRIES_REMAINING {
738             assert!(zircon_load_verify_abr(&mut ops).is_err());
739         }
740         // Tests that load falls back to R eventually.
741         expect_load_verify_abr_ok(&mut ops, SlotIndex::R, "zircon_r");
742     }
743 
744     #[test]
test_load_verify_abr_verify_failure_unlocked()745     fn test_load_verify_abr_verify_failure_unlocked() {
746         let storage = create_storage();
747         let mut ops = create_gbl_ops(&storage);
748         let mut image_buffers_pool =
749             ImageBuffersPool::builder().number((1 + 2 * ABR_MAX_TRIES_REMAINING).into()).build();
750         ops.image_buffers = image_buffers_pool.get();
751 
752         ops.avb_ops.unlock_state = Ok(true);
753         corrupt_data(&mut ops, "zircon_a");
754         corrupt_data(&mut ops, "zircon_b");
755 
756         for _ in 0..ABR_MAX_TRIES_REMAINING {
757             expect_load_verify_abr_ok(&mut ops, SlotIndex::A, "zircon_a");
758         }
759         for _ in 0..ABR_MAX_TRIES_REMAINING {
760             expect_load_verify_abr_ok(&mut ops, SlotIndex::B, "zircon_b");
761         }
762         expect_load_verify_abr_ok(&mut ops, SlotIndex::R, "zircon_r");
763     }
764 
765     #[test]
test_check_enter_fastboot_stop_in_fastboot()766     fn test_check_enter_fastboot_stop_in_fastboot() {
767         let storage = create_storage();
768         let mut ops = create_gbl_ops(&storage);
769 
770         ops.stop_in_fastboot = Ok(false).into();
771         assert!(!zircon_check_enter_fastboot(&mut ops));
772 
773         ops.stop_in_fastboot = Ok(true).into();
774         assert!(zircon_check_enter_fastboot(&mut ops));
775 
776         ops.stop_in_fastboot = Err(Error::NotImplemented).into();
777         assert!(!zircon_check_enter_fastboot(&mut ops));
778     }
779 
780     #[test]
test_check_enter_fastboot_abr()781     fn test_check_enter_fastboot_abr() {
782         let storage = create_storage();
783         let mut ops = create_gbl_ops(&storage);
784         set_one_shot_bootloader(&mut GblAbrOps(&mut ops), true).unwrap();
785         assert!(zircon_check_enter_fastboot(&mut ops));
786         // One-shot only.
787         assert!(!zircon_check_enter_fastboot(&mut ops));
788     }
789 
790     #[test]
test_check_enter_fastboot_prioritize_abr()791     fn test_check_enter_fastboot_prioritize_abr() {
792         let storage = create_storage();
793         let mut ops = create_gbl_ops(&storage);
794         set_one_shot_bootloader(&mut GblAbrOps(&mut ops), true).unwrap();
795         ops.stop_in_fastboot = Ok(true).into();
796         assert!(zircon_check_enter_fastboot(&mut ops));
797         ops.stop_in_fastboot = Ok(false).into();
798         // A/B/R metadata should be prioritized in the previous check and thus one-shot-booloader
799         // flag should be cleared.
800         assert!(!zircon_check_enter_fastboot(&mut ops));
801     }
802     #[test]
test_load_verify_abr_legacy_naming()803     fn test_load_verify_abr_legacy_naming() {
804         let storage = create_storage_legacy_names();
805         let mut ops = create_gbl_ops(&storage);
806         let mut image_buffers_pool =
807             ImageBuffersPool::builder().number((1 + 2 * ABR_MAX_TRIES_REMAINING).into()).build();
808         ops.image_buffers = image_buffers_pool.get();
809 
810         // Tests by exhausting all slots retries so it exercises all legacy name matching code
811         // paths.
812         for _ in 0..ABR_MAX_TRIES_REMAINING {
813             expect_load_verify_abr_ok(&mut ops, SlotIndex::A, "zircon-a");
814         }
815         for _ in 0..ABR_MAX_TRIES_REMAINING {
816             expect_load_verify_abr_ok(&mut ops, SlotIndex::B, "zircon-b");
817         }
818         // Tests that load falls back to R eventually.
819         expect_load_verify_abr_ok(&mut ops, SlotIndex::R, "zircon-r");
820     }
821 
822     #[test]
test_zircon_load_verify_no_bootloader_file()823     fn test_zircon_load_verify_no_bootloader_file() {
824         let storage = create_storage();
825         let mut ops = create_gbl_ops(&storage);
826         let mut image_buffers_pool = ImageBuffersPool::builder().number(2).build();
827         ops.image_buffers = image_buffers_pool.get();
828         ops.get_zbi_bootloader_files_buffer().unwrap().fill(0);
829 
830         let zbi = &read_test_data(ZIRCON_SLOTLESS_ZBI_FILE);
831         let expected_kernel = AlignedBuffer::new_with_data(zbi, ZBI_ALIGNMENT_USIZE);
832         // Adds extra bytes for device ZBI items.
833         let mut expected_zbi_items = AlignedBuffer::new(1024, ZBI_ALIGNMENT_USIZE);
834         let _ = ZbiContainer::new(&mut expected_zbi_items[..]).unwrap();
835         append_cmd_line(&mut expected_zbi_items, FakeGblOps::ADDED_ZBI_COMMANDLINE_CONTENTS);
836         append_cmd_line(&mut expected_zbi_items, b"vb_prop_0=val\0");
837         append_cmd_line(&mut expected_zbi_items, b"vb_prop_1=val\0");
838         test_load_verify(&mut ops, None, &expected_zbi_items, &expected_kernel);
839     }
840 }
841