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