Add API for loading, verifying, booting zircon
Bug: 334962583
Change-Id: Ia78695240efd729ea1bbb896a1f422e6c68933ac
diff --git a/gbl/efi/src/fuchsia_boot.rs b/gbl/efi/src/fuchsia_boot.rs
index 5ba17d4..336a790 100644
--- a/gbl/efi/src/fuchsia_boot.rs
+++ b/gbl/efi/src/fuchsia_boot.rs
@@ -195,7 +195,7 @@
let mut load_buffer = vec![0u8; 128 * 1024 * 1024]; // 128MB
let zbi_kernel = load_fuchsia_simple(&efi_entry, &mut load_buffer[..])?;
#[allow(unused_variables)]
- let (original, relocated, kernel_entry) = relocate_to_tail(&mut zbi_kernel[..])?;
+ let (original, relocated, _) = relocate_to_tail(&mut zbi_kernel[..])?;
#[cfg(target_arch = "aarch64")]
{
@@ -206,7 +206,7 @@
let (_, remains) = zbi_get_unused_buffer(relocated)?;
let _ = efi::exit_boot_services(efi_entry, remains).unwrap();
// SAFETY: For demo, we assume images are provided valid.
- unsafe { boot::aarch64::jump_zircon_el2_or_lower(relocated, kernel_entry, original) };
+ unsafe { boot::aarch64::jump_zircon_el2_or_lower(relocated, original) };
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
diff --git a/gbl/efi/src/ops.rs b/gbl/efi/src/ops.rs
index 09c0315..0aa1050 100644
--- a/gbl/efi/src/ops.rs
+++ b/gbl/efi/src/ops.rs
@@ -18,7 +18,11 @@
use core::fmt::Write;
use efi::{efi_print, efi_println, EfiEntry};
-use libgbl::{GblOps, GblOpsError};
+use libgbl::{
+ slots::{BootToken, Cursor},
+ BootImages, GblOps, GblOpsError, Result as GblResult,
+};
+use zbi::ZbiContainer;
pub struct Ops<'a> {
pub efi_entry: &'a EfiEntry,
@@ -33,6 +37,10 @@
}
impl GblOps for Ops<'_> {
+ fn console_out(&mut self) -> Option<&mut dyn Write> {
+ unimplemented!();
+ }
+
fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError> {
// TODO(b/349829690): also query GblSlotProtocol.get_boot_reason() for board-specific
// fastboot triggers.
@@ -43,4 +51,49 @@
}
Ok(found?)
}
+
+ fn preboot(&mut self, _: BootImages) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ async fn read_from_partition(
+ &mut self,
+ _: &str,
+ _: u64,
+ _: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ async fn write_to_partition(
+ &mut self,
+ _: &str,
+ _: u64,
+ _: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ fn partition_size(&mut self, _: &str) -> Result<Option<u64>, GblOpsError> {
+ unimplemented!();
+ }
+
+ fn zircon_add_device_zbi_items(
+ &mut self,
+ _: &mut ZbiContainer<&mut [u8]>,
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ fn do_fastboot<B: gbl_storage::AsBlockDevice>(&self, _: &mut Cursor<B>) -> GblResult<()> {
+ unimplemented!();
+ }
+
+ fn load_slot_interface<'a, B: gbl_storage::AsBlockDevice>(
+ &'a mut self,
+ _: &'a mut B,
+ _: BootToken,
+ ) -> GblResult<Cursor<'a, B>> {
+ unimplemented!();
+ }
}
diff --git a/gbl/libboot/BUILD b/gbl/libboot/BUILD
index 83c20e8..2d207fa 100644
--- a/gbl/libboot/BUILD
+++ b/gbl/libboot/BUILD
@@ -75,6 +75,7 @@
rustc_flags = ANDROID_RUST_LINTS,
deps = [
":x86_bootparam_defs",
+ "@gbl//third_party/libzbi",
"@zerocopy",
] + select({
"@gbl//toolchain:gbl_rust_uefi_aarch64": [
diff --git a/gbl/libboot/src/aarch64.rs b/gbl/libboot/src/aarch64.rs
index 32384c1..6655c91 100644
--- a/gbl/libboot/src/aarch64.rs
+++ b/gbl/libboot/src/aarch64.rs
@@ -17,6 +17,7 @@
//! https://www.kernel.org/doc/html/v5.11/arm64/booting.html
use core::arch::asm;
+use zbi::ZbiContainer;
/// ARM exception levels.
#[allow(missing_docs)]
@@ -119,13 +120,14 @@
///
/// # Safety
///
-/// Caller must ensure that `zbi_kernel` contains a valid zircon kernel ZBI item and `entry_off` is
-/// the correct kernel entry offset.
-pub unsafe fn jump_zircon_el2_or_lower(zbi_kernel: &[u8], entry_off: usize, zbi_item: &[u8]) -> ! {
+/// Caller must ensure that `zbi_kernel` contains a valid zircon kernel ZBI item.
+pub unsafe fn jump_zircon_el2_or_lower(kernel: &[u8], zbi_item: &[u8]) -> ! {
assert_ne!(current_el(), ExceptionLevel::EL3);
- flush_dcache_buffer(zbi_kernel);
+ let (entry, _) =
+ ZbiContainer::parse(zbi_item).unwrap().get_kernel_entry_and_reserved_memory_size().unwrap();
+ flush_dcache_buffer(kernel);
flush_dcache_buffer(zbi_item);
- let addr = (zbi_kernel.as_ptr() as usize).checked_add(entry_off).unwrap();
+ let addr = (kernel.as_ptr() as usize).checked_add(usize::try_from(entry).unwrap()).unwrap();
// SAFETY:
// * `zbi_kernel` and `zbi_item` have been flushed.
// * By requirement of this function, the computed `addr` is a valid kernel entry point.
diff --git a/gbl/libefi/BUILD b/gbl/libefi/BUILD
index 2f4f7fb..be18e31 100644
--- a/gbl/libefi/BUILD
+++ b/gbl/libefi/BUILD
@@ -111,5 +111,6 @@
deps = [
"@gbl//libavb:sysdeps",
"@gbl//libstorage:libstorage_testlib",
+ "@gbl//third_party/libzbi",
],
)
diff --git a/gbl/libefi/src/ab_slots.rs b/gbl/libefi/src/ab_slots.rs
index ecc9fc4..0fd0282 100644
--- a/gbl/libefi/src/ab_slots.rs
+++ b/gbl/libefi/src/ab_slots.rs
@@ -180,12 +180,16 @@
use core::ptr::null_mut;
use gbl::{
slots::{Bootability, Cursor, RecoveryTarget, UnbootableReason},
- Gbl, GblOps, GblOpsError, Result as GblResult,
+ BootImages, Gbl, GblOps, GblOpsError, Result as GblResult,
};
use gbl_storage_testlib::TestBlockDevice;
// TODO(b/350526796): use ptr.is_aligned() when Rust 1.79 is in Android
- use std::mem::align_of;
- use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
+ use std::{
+ fmt::Write,
+ mem::align_of,
+ sync::atomic::{AtomicBool, AtomicU32, Ordering},
+ };
+ use zbi::ZbiContainer;
// The thread-local atomics are an ugly, ugly hack to pass state between
// the protocol method functions and the rest of the test body.
@@ -243,8 +247,49 @@
}
impl<'b> GblOps for TestGblOps<'b> {
+ fn console_out(&mut self) -> Option<&mut dyn Write> {
+ unimplemented!();
+ }
+
fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError> {
- Ok(false)
+ unimplemented!();
+ }
+
+ fn preboot(&mut self, _: BootImages) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ async fn read_from_partition(
+ &mut self,
+ _: &str,
+ _: u64,
+ _: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ async fn write_to_partition(
+ &mut self,
+ _: &str,
+ _: u64,
+ _: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ fn partition_size(&mut self, _: &str) -> Result<Option<u64>, GblOpsError> {
+ unimplemented!();
+ }
+
+ fn zircon_add_device_zbi_items(
+ &mut self,
+ _: &mut ZbiContainer<&mut [u8]>,
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ fn do_fastboot<B: gbl_storage::AsBlockDevice>(&self, _: &mut Cursor<B>) -> GblResult<()> {
+ unimplemented!();
}
fn load_slot_interface<'a, B: gbl_storage::AsBlockDevice>(
diff --git a/gbl/libgbl/BUILD b/gbl/libgbl/BUILD
index 036422f..0b64c95 100644
--- a/gbl/libgbl/BUILD
+++ b/gbl/libgbl/BUILD
@@ -30,7 +30,9 @@
"@bitflags",
"@crc32fast",
"@cstr",
+ "@gbl//libabr",
"@gbl//libasync",
+ "@gbl//libboot",
"@gbl//libfastboot",
"@gbl//libsafemath",
"@gbl//libstorage",
@@ -59,9 +61,12 @@
"@gbl//libgbl/testdata:sparse_test_raw.bin",
"@gbl//libgbl/testdata:testkey_rsa4096_pub.bin",
"@gbl//libgbl/testdata:writeback_test_disk.bin",
- "@gbl//libgbl/testdata:zircon_a.bin",
"@gbl//libgbl/testdata:zircon_a.vbmeta",
"@gbl//libgbl/testdata:zircon_a.vbmeta.cert",
+ "@gbl//libgbl/testdata:zircon_a.zbi",
+ "@gbl//libgbl/testdata:zircon_b.zbi",
+ "@gbl//libgbl/testdata:zircon_r.zbi",
+ "@gbl//libgbl/testdata:zircon_slotless.zbi",
],
rustc_flags = ANDROID_RUST_LINTS,
deps = [
@@ -76,22 +81,3 @@
"@uuid",
],
)
-
-rust_test(
- name = "integration_test",
- srcs = ["tests/integration_tests.rs"],
- compile_data = [
- "@gbl//libgbl/testdata:zircon_a.bin",
- "@gbl//libgbl/testdata:zircon_b.bin",
- "@gbl//libgbl/testdata:zircon_r.bin",
- "@gbl//libgbl/testdata:zircon_gpt.bin",
- ],
- rustc_flags = ANDROID_RUST_LINTS,
- deps = [
- ":libgbl",
- "@avb//:avb_crypto_ops_sha_impl_staticlib",
- "@gbl//libavb:sysdeps",
- "@gbl//libstorage",
- "@gbl//libstorage:libstorage_testlib",
- ],
-)
diff --git a/gbl/libgbl/src/error.rs b/gbl/libgbl/src/error.rs
index fbc8ac2..396adb4 100644
--- a/gbl/libgbl/src/error.rs
+++ b/gbl/libgbl/src/error.rs
@@ -45,6 +45,12 @@
AvbOpsBusy,
/// Buffers overlap and can cause undefined behavior and data corruption.
BufferOverlap,
+ /// Input parameter is invalid.
+ InvalidInput,
+ /// Buffer is too small.
+ BufferTooSmall,
+ /// Buffer does not meet alignment requirement.
+ InvalidAlignment,
}
impl Display for Error {
@@ -59,6 +65,9 @@
Error::Internal => write!(f, "Internal error"),
Error::AvbOpsBusy => write!(f, "AvbOps were already borrowed"),
Error::BufferOverlap => write!(f, "Buffers overlap"),
+ Error::InvalidInput => write!(f, "Invalid Input"),
+ Error::BufferTooSmall => write!(f, "Buffer is too small"),
+ Error::InvalidAlignment => write!(f, "Buffer does not meet alignment requirement"),
}
}
}
@@ -150,8 +159,10 @@
GblSlotsError(crate::slots::Error),
FromBytesUntilNulError(FromBytesUntilNulError),
FromBytesWithNulError(FromBytesWithNulError),
- StorageError(StorageError),
SafeMathError(safemath::Error),
+ StorageError(StorageError),
+ TryFromIntError(core::num::TryFromIntError),
+ ZbiError(zbi::ZbiError),
}
}
diff --git a/gbl/libgbl/src/fuchsia_boot/mod.rs b/gbl/libgbl/src/fuchsia_boot/mod.rs
new file mode 100644
index 0000000..4983502
--- /dev/null
+++ b/gbl/libgbl/src/fuchsia_boot/mod.rs
@@ -0,0 +1,396 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This file provides APIs for loading, verifying and booting Fuchsia/Zircon.
+
+use crate::{Error as GblError, GblOps, Result as GblResult};
+pub use abr::SlotIndex;
+use safemath::SafeNum;
+use zbi::{ZbiContainer, ZbiFlags, ZbiHeader, ZbiType};
+use zerocopy::AsBytes;
+
+/// Kernel load address alignment. Value taken from
+/// https://fuchsia.googlesource.com/fuchsia/+/4f204d8a0243e84a86af4c527a8edcc1ace1615f/zircon/kernel/target/arm64/boot-shim/BUILD.gn#38
+pub const ZIRCON_KERNEL_ALIGN: usize = 64 * 1024;
+
+/// A helper for getting a subslice with an aligned address.
+fn aligned_subslice(buffer: &mut [u8], alignment: usize) -> GblResult<&mut [u8]> {
+ let addr = SafeNum::from(buffer.as_ptr() as usize);
+ let aligned_offset = addr.round_up(alignment) - addr;
+ Ok(buffer.get_mut(aligned_offset.try_into()?..).ok_or(GblError::BufferTooSmall)?)
+}
+
+/// A helper for splitting the trailing unused portion of a ZBI container buffer.
+///
+/// Returns a tuple of used subslice and unused subslice
+fn zbi_split_unused_buffer(zbi: &mut [u8]) -> GblResult<(&mut [u8], &mut [u8])> {
+ Ok(zbi.split_at_mut(ZbiContainer::parse(&zbi[..])?.container_size()))
+}
+
+/// Relocates a ZBI kernel to a different buffer.
+///
+/// * `dest` must be aligned to `ZIRCON_KERNEL_ALIGN`.
+/// * `dest` will be a ZBI container containing only the kernel item.
+pub fn relocate_kernel(kernel: &[u8], dest: &mut [u8]) -> GblResult<()> {
+ if (dest.as_ptr() as usize % ZIRCON_KERNEL_ALIGN) != 0 {
+ return Err(GblError::InvalidAlignment.into());
+ }
+
+ let kernel = ZbiContainer::parse(&kernel[..])?;
+ let kernel_item = kernel.is_bootable()?;
+ let hdr = kernel_item.header;
+ // Creates a new ZBI kernel item at the destination.
+ let mut relocated = ZbiContainer::new(&mut dest[..])?;
+ let zbi_type = ZbiType::try_from(hdr.type_)?;
+ relocated.create_entry_with_payload(
+ zbi_type,
+ hdr.extra,
+ hdr.get_flags() & !ZbiFlags::CRC32,
+ kernel_item.payload.as_bytes(),
+ )?;
+ let (_, reserved_memory_size) = relocated.get_kernel_entry_and_reserved_memory_size()?;
+ match reserved_memory_size > u64::try_from(zbi_split_unused_buffer(dest)?.1.len())? {
+ true => Err(GblError::BufferTooSmall.into()),
+ _ => Ok(()),
+ }
+}
+
+/// Relocate a ZBI kernel to the trailing unused buffer.
+///
+/// Returns the original kernel subslice and relocated kernel subslice.
+pub fn relocate_to_tail(kernel: &mut [u8]) -> GblResult<(&mut [u8], &mut [u8])> {
+ let reloc_size = ZbiContainer::parse(&kernel[..])?.get_buffer_size_for_kernel_relocation()?;
+ let (original, relocated) = zbi_split_unused_buffer(kernel)?;
+ let relocated = aligned_subslice(relocated, ZIRCON_KERNEL_ALIGN)?;
+ let off = (SafeNum::from(relocated.len()) - reloc_size).round_down(ZIRCON_KERNEL_ALIGN);
+ let relocated = &mut relocated[off.try_into()?..];
+ relocate_kernel(original, relocated)?;
+ let reloc_addr = relocated.as_ptr() as usize;
+ Ok(kernel.split_at_mut(reloc_addr.checked_sub(kernel.as_ptr() as usize).unwrap()))
+}
+
+/// Helper for getting the slotted/slotless standard zircon partition name.
+fn zircon_part_name(slot: Option<SlotIndex>) -> &'static str {
+ match slot {
+ Some(slot) => match slot {
+ SlotIndex::A => "zircon_a",
+ SlotIndex::B => "zircon_b",
+ SlotIndex::R => "zircon_r",
+ },
+ _ => "zircon",
+ }
+}
+
+/// Gets the ZBI command line string for the current slot.
+fn slot_cmd_line(slot: SlotIndex) -> &'static str {
+ match slot {
+ SlotIndex::A => "zvb.current_slot=a",
+ SlotIndex::B => "zvb.current_slot=b",
+ SlotIndex::R => "zvb.current_slot=r",
+ }
+}
+
+/// Loads and verifies a kernel of the given slot or slotless.
+///
+/// # Args
+///
+/// * `ops`: A reference to an object that implements `GblOps`.
+/// * `slot`: None if slotless. Otherwise the target slot to boot.
+/// * `load`: Buffer for loading the kernel.
+///
+/// On success returns a pair containing: 1. the slice of the ZBI container with device ZBI items
+/// and 2. the slice of the relocated kernel at the tail.
+pub fn zircon_load_verify<'a>(
+ ops: &mut impl GblOps,
+ slot: Option<SlotIndex>,
+ load: &'a mut [u8],
+) -> GblResult<(&'a mut [u8], &'a mut [u8])> {
+ let load = aligned_subslice(load, ZIRCON_KERNEL_ALIGN)?;
+ let zircon_part = zircon_part_name(slot);
+
+ // Reads ZBI header to computes the total size of kernel.
+ let mut zbi_header: ZbiHeader = Default::default();
+ ops.read_from_partition_sync(zircon_part, 0, zbi_header.as_bytes_mut())?;
+ let image_length = SafeNum::from(zbi_header.as_bytes_mut().len()) + zbi_header.length;
+
+ // Reads the entire kernel
+ let kernel = load.get_mut(..image_length.try_into()?).ok_or(GblError::BufferTooSmall)?;
+ ops.read_from_partition_sync(zircon_part, 0, kernel)?;
+
+ // TODO(b/334962583): Perform AVB verification.
+
+ // Append additional ZBI items.
+ let mut zbi_kernel = ZbiContainer::parse(&mut load[..])?;
+
+ match slot {
+ Some(slot) => {
+ // Appends current slot item.
+ zbi_kernel.create_entry_with_payload(
+ ZbiType::CmdLine,
+ 0,
+ ZbiFlags::default(),
+ slot_cmd_line(slot).as_bytes(),
+ )?;
+ }
+ _ => {}
+ }
+
+ // Relocates the kernel to the tail to reserved extra memory that the kernel may require.
+ let (zbi_items, relocated) = relocate_to_tail(&mut load[..])?;
+
+ // Appends device specific ZBI items.
+ ops.zircon_add_device_zbi_items(&mut ZbiContainer::parse(&mut zbi_items[..])?)?;
+
+ Ok((zbi_items, relocated))
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{slots, BootImages, GblOpsError};
+ use std::{
+ collections::{BTreeSet, HashMap},
+ fmt::Write,
+ fs,
+ path::Path,
+ };
+
+ // The `reserve_memory_size` value in the test ZBI kernel.
+ // See `gen_zircon_test_images()` in libgbl/testdata/gen_test_data.py.
+ const TEST_KERNEL_RESERVED_MEMORY_SIZE: usize = 1024;
+
+ pub(crate) const ZIRCON_A_ZBI_FILE: &str = "zircon_a.zbi";
+ pub(crate) const ZIRCON_B_ZBI_FILE: &str = "zircon_b.zbi";
+ pub(crate) const ZIRCON_R_ZBI_FILE: &str = "zircon_r.zbi";
+ pub(crate) const ZIRCON_SLOTLESS_ZBI_FILE: &str = "zircon_slotless.zbi";
+
+ /// Reads a data file under libgbl/testdata/
+ pub(crate) fn read_test_data(file: &str) -> Vec<u8> {
+ fs::read(Path::new(format!("external/gbl/libgbl/testdata/{}", file).as_str())).unwrap()
+ }
+
+ /// `TestZirconBootGblOps` implements `GblOps` for test.
+ pub(crate) struct TestZirconBootGblOps {
+ pub(crate) partitions: HashMap<&'static str, Vec<u8>>,
+ }
+
+ impl Default for TestZirconBootGblOps {
+ fn default() -> Self {
+ let partitions = HashMap::from([
+ ("zircon_a", read_test_data(ZIRCON_A_ZBI_FILE)),
+ ("zircon_b", read_test_data(ZIRCON_B_ZBI_FILE)),
+ ("zircon_r", read_test_data(ZIRCON_R_ZBI_FILE)),
+ ("zircon", read_test_data(ZIRCON_SLOTLESS_ZBI_FILE)),
+ ]);
+ Self { partitions }
+ }
+ }
+
+ impl GblOps for TestZirconBootGblOps {
+ fn console_out(&mut self) -> Option<&mut dyn Write> {
+ unimplemented!();
+ }
+
+ fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError> {
+ unimplemented!();
+ }
+
+ fn preboot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ async fn read_from_partition(
+ &mut self,
+ part: &str,
+ off: u64,
+ out: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ match self.partitions.get_mut(part) {
+ Some(v) => Ok(out.clone_from_slice(&v[off.try_into().unwrap()..][..out.len()])),
+ _ => Err(GblOpsError(Some("Test: No such partition"))),
+ }
+ }
+
+ async fn write_to_partition(
+ &mut self,
+ part: &str,
+ off: u64,
+ data: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ match self.partitions.get_mut(part) {
+ Some(v) => Ok(v[off.try_into().unwrap()..][..data.len()].clone_from_slice(data)),
+ _ => Err(GblOpsError(Some("Test: No such partition"))),
+ }
+ }
+
+ fn partition_size(&mut self, part: &str) -> Result<Option<u64>, GblOpsError> {
+ Ok(self.partitions.get_mut(part).map(|v| v.len().try_into().unwrap()))
+ }
+
+ fn zircon_add_device_zbi_items(
+ &mut self,
+ container: &mut ZbiContainer<&mut [u8]>,
+ ) -> Result<(), GblOpsError> {
+ container
+ .create_entry_with_payload(
+ ZbiType::CmdLine,
+ 0,
+ ZbiFlags::default(),
+ b"test_zbi_item",
+ )
+ .unwrap();
+ Ok(())
+ }
+
+ fn do_fastboot<B: gbl_storage::AsBlockDevice>(
+ &self,
+ cursor: &mut slots::Cursor<B>,
+ ) -> GblResult<()> {
+ unimplemented!();
+ }
+
+ fn load_slot_interface<'a, B: gbl_storage::AsBlockDevice>(
+ &'a mut self,
+ block_device: &'a mut B,
+ boot_token: slots::BootToken,
+ ) -> GblResult<slots::Cursor<'a, B>> {
+ unimplemented!();
+ }
+ }
+
+ // Helper object for allocating aligned buffer.
+ pub(crate) struct AlignedBuffer {
+ buffer: Vec<u8>,
+ size: usize,
+ alignment: usize,
+ }
+
+ impl AlignedBuffer {
+ /// Allocates a buffer.
+ pub(crate) fn new(size: usize, alignment: usize) -> Self {
+ Self { buffer: vec![0u8; alignment + size - 1], size, alignment }
+ }
+
+ /// Allocates a buffer and initializes with data.
+ pub(crate) fn new_with_data(data: &[u8], alignment: usize) -> Self {
+ let mut res = Self::new(data.len(), alignment);
+ res.get().clone_from_slice(data);
+ res
+ }
+
+ /// Gets the buffer
+ pub(crate) fn get(&mut self) -> &mut [u8] {
+ &mut aligned_subslice(&mut self.buffer[..], self.alignment).unwrap()[..self.size]
+ }
+ }
+
+ /// Normalizes a ZBI container by converting each ZBI item into raw bytes and storing them in
+ /// an ordered set. The function is mainly used for comparing two ZBI containers have identical
+ /// set of items, disregarding order.
+ pub(crate) fn normalize_zbi(zbi: &[u8]) -> BTreeSet<Vec<u8>> {
+ let zbi = ZbiContainer::parse(zbi).unwrap();
+ BTreeSet::from_iter(zbi.iter().map(|v| {
+ let mut hdr = *v.header;
+ hdr.crc32 = 0; // ignores crc32 field.
+ hdr.flags &= !ZbiFlags::CRC32.bits();
+ [hdr.as_bytes(), v.payload.as_bytes()].concat()
+ }))
+ }
+
+ /// Helper to append a command line ZBI item to a ZBI container
+ pub(crate) fn append_cmd_line(zbi: &mut [u8], cmd: &[u8]) {
+ let mut container = ZbiContainer::parse(zbi).unwrap();
+ container.create_entry_with_payload(ZbiType::CmdLine, 0, ZbiFlags::default(), cmd).unwrap();
+ }
+
+ /// Helper for testing `zircon_load_verify`.
+ fn test_load_verify(
+ ops: &mut impl GblOps,
+ slot: Option<SlotIndex>,
+ expected_zbi_items: &[u8],
+ expected_kernel: &[u8],
+ ) {
+ // Test load buffer layout:
+ // | zircon_x.zbi + items| ~~ |~64k~| relocated kernel + reserved |
+ // | ---------- 64K -----------|~~~~~| ----------------------------|
+ let sz = 2 * ZIRCON_KERNEL_ALIGN + expected_kernel.len() + TEST_KERNEL_RESERVED_MEMORY_SIZE;
+ let mut load = AlignedBuffer::new(sz, ZIRCON_KERNEL_ALIGN);
+ let (zbi_items, relocated) = zircon_load_verify(ops, slot, load.get()).unwrap();
+ // Verifies loaded ZBI kernel/items
+ assert_eq!(normalize_zbi(expected_zbi_items), normalize_zbi(zbi_items));
+ // Verifies relocated kernel
+ assert_eq!(normalize_zbi(expected_kernel), normalize_zbi(relocated));
+ // Relocated kernel is at the latest aligned address
+ let off = (relocated.as_ptr() as usize) - (load.get().as_ptr() as usize);
+ assert_eq!(off, 2 * ZIRCON_KERNEL_ALIGN);
+ }
+
+ #[test]
+ fn test_zircon_load_verify_slotless() {
+ let mut ops = TestZirconBootGblOps::default();
+ let zbi = &read_test_data(ZIRCON_SLOTLESS_ZBI_FILE);
+ let mut expected_kernel = AlignedBuffer::new_with_data(zbi, 8);
+ // Adds extra bytes for device ZBI items.
+ let mut expected_zbi_items = AlignedBuffer::new(zbi.len() + 1024, 8);
+ expected_zbi_items.get()[..zbi.len()].clone_from_slice(zbi);
+ append_cmd_line(expected_zbi_items.get(), b"test_zbi_item");
+ test_load_verify(&mut ops, None, expected_zbi_items.get(), expected_kernel.get());
+ }
+
+ /// Helper for testing `zircon_load_verify` using A/B/R.
+ fn test_load_verify_slotted_helper(
+ ops: &mut impl GblOps,
+ slot: SlotIndex,
+ zbi: &[u8],
+ slot_item: &str,
+ ) {
+ let mut expected_kernel = AlignedBuffer::new_with_data(zbi, 8);
+ // Adds extra bytes for device ZBI items.
+ let mut expected_zbi_items = AlignedBuffer::new(zbi.len() + 1024, 8);
+ expected_zbi_items.get()[..zbi.len()].clone_from_slice(zbi);
+ append_cmd_line(expected_zbi_items.get(), b"test_zbi_item");
+ append_cmd_line(expected_zbi_items.get(), slot_item.as_bytes());
+ test_load_verify(ops, Some(slot), expected_zbi_items.get(), expected_kernel.get());
+ }
+
+ #[test]
+ fn test_load_verify_slot_a() {
+ let mut ops = TestZirconBootGblOps::default();
+ let zircon_a_zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
+ test_load_verify_slotted_helper(&mut ops, SlotIndex::A, zircon_a_zbi, "zvb.current_slot=a");
+ }
+
+ #[test]
+ fn test_load_verify_slot_b() {
+ let mut ops = TestZirconBootGblOps::default();
+ let zircon_b_zbi = &read_test_data(ZIRCON_B_ZBI_FILE);
+ test_load_verify_slotted_helper(&mut ops, SlotIndex::B, zircon_b_zbi, "zvb.current_slot=b");
+ }
+
+ #[test]
+ fn test_load_verify_slot_r() {
+ let mut ops = TestZirconBootGblOps::default();
+ let zircon_r_zbi = &read_test_data(ZIRCON_R_ZBI_FILE);
+ test_load_verify_slotted_helper(&mut ops, SlotIndex::R, zircon_r_zbi, "zvb.current_slot=r");
+ }
+
+ #[test]
+ fn test_not_enough_buffer_for_reserved_memory() {
+ let mut ops = TestZirconBootGblOps::default();
+ let zbi = &read_test_data(ZIRCON_A_ZBI_FILE);
+ let sz = ZIRCON_KERNEL_ALIGN + zbi.len() + TEST_KERNEL_RESERVED_MEMORY_SIZE - 1;
+ let mut load = AlignedBuffer::new(sz, ZIRCON_KERNEL_ALIGN);
+ assert!(zircon_load_verify(&mut ops, Some(SlotIndex::A), load.get()).is_err());
+ }
+}
diff --git a/gbl/libgbl/src/lib.rs b/gbl/libgbl/src/lib.rs
index a26fe45..e49a4b3 100644
--- a/gbl/libgbl/src/lib.rs
+++ b/gbl/libgbl/src/lib.rs
@@ -42,6 +42,7 @@
pub mod boot_reason;
pub mod error;
pub mod fastboot;
+pub mod fuchsia_boot;
pub mod ops;
mod overlap;
@@ -59,7 +60,6 @@
AndroidBootImages, BootImages, DefaultGblOps, FuchsiaBootImages, GblOps, GblOpsError,
};
-use gbl_async::block_on;
use overlap::is_overlap;
// TODO: b/312607649 - Replace placeholders with actual structures: https://r.android.com/2721974, etc
@@ -515,25 +515,6 @@
Ok((kernel_image, token))
}
-
- /// Loads and boots a Zircon kernel according to ABR + AVB.
- pub fn zircon_load_and_boot(&mut self, load_buffer: &mut [u8]) -> Result<()> {
- // TODO(b/334962583): Implement zircon ABR + AVB.
- // The following are place holder for test of invocation in the integration test only.
- let ptn_size = self
- .ops
- .partition_size("zircon_a")?
- .ok_or(Error::MissingImage)?
- .try_into()
- .or(Err(Error::ArithmeticOverflow))?;
- let (kernel, remains) = load_buffer.split_at_mut(ptn_size);
- block_on(self.ops.read_from_partition("zircon_a", 0, kernel))?;
- self.ops.boot(BootImages::Fuchsia(FuchsiaBootImages {
- zbi_kernel: kernel,
- zbi_items: &mut [],
- }))?;
- Err(Error::BootFailed.into())
- }
}
#[cfg(test)]
@@ -548,7 +529,7 @@
const TEST_ZIRCON_PARTITION_NAME: &str = "zircon_a";
const TEST_ZIRCON_PARTITION_NAME_CSTR: &CStr = c"zircon_a";
- const TEST_ZIRCON_IMAGE_PATH: &str = "zircon_a.bin";
+ const TEST_ZIRCON_IMAGE_PATH: &str = "zircon_a.zbi";
const TEST_ZIRCON_VBMETA_PATH: &str = "zircon_a.vbmeta";
const TEST_ZIRCON_VBMETA_CERT_PATH: &str = "zircon_a.vbmeta.cert";
const TEST_PUBLIC_KEY_PATH: &str = "testkey_rsa4096_pub.bin";
diff --git a/gbl/libgbl/src/ops.rs b/gbl/libgbl/src/ops.rs
index 2bf4063..b1541f4 100644
--- a/gbl/libgbl/src/ops.rs
+++ b/gbl/libgbl/src/ops.rs
@@ -17,13 +17,15 @@
#[cfg(feature = "alloc")]
extern crate alloc;
-use crate::error::{Error, Result as GblResult};
+use crate::error::Result as GblResult;
#[cfg(feature = "alloc")]
use alloc::ffi::CString;
use core::{
fmt::{Debug, Write},
result::Result,
};
+use gbl_async::block_on;
+use zbi::ZbiContainer;
use super::slots;
@@ -54,9 +56,15 @@
}
/// `GblOpsError` is the error type returned by required methods in `GblOps`.
-#[derive(Default, Debug, PartialEq, Eq)]
+#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
pub struct GblOpsError(pub Option<&'static str>);
+impl From<&'static str> for GblOpsError {
+ fn from(val: &'static str) -> Self {
+ Self(Some(val))
+ }
+}
+
// https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
// should we use traits for this? or optional/box FnMut?
//
@@ -67,21 +75,15 @@
*/
/// Trait that defines callbacks that can be provided to Gbl.
pub trait GblOps {
- /// Prints a ASCII character to the platform console.
- fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError> {
- Err(GblOpsError(Some("not defined yet")))
- }
+ /// Gets a console for logging messages.
+ fn console_out(&mut self) -> Option<&mut dyn Write>;
/// This method can be used to implement platform specific mechanism for deciding whether boot
/// should abort and enter Fastboot mode.
fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError>;
- /// Platform specific kernel boot implementation.
- ///
- /// Implementation is not expected to return on success.
- fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError> {
- Err(GblOpsError(Some("not defined yet")))
- }
+ /// Platform specific processing of boot images before booting.
+ fn preboot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError>;
/// Reads data from a partition.
async fn read_from_partition(
@@ -89,8 +91,16 @@
part: &str,
off: u64,
out: &mut [u8],
+ ) -> Result<(), GblOpsError>;
+
+ /// Reads data from a partition synchronously.
+ fn read_from_partition_sync(
+ &mut self,
+ part: &str,
+ off: u64,
+ out: &mut [u8],
) -> Result<(), GblOpsError> {
- Err(GblOpsError(Some("not defined yet")))
+ block_on(self.read_from_partition(part, off, out))
}
/// Writes data to a partition.
@@ -99,14 +109,26 @@
part: &str,
off: u64,
data: &mut [u8],
+ ) -> Result<(), GblOpsError>;
+
+ /// Writes data to a partition synchronously.
+ fn write_to_partition_sync(
+ &mut self,
+ part: &str,
+ off: u64,
+ data: &mut [u8],
) -> Result<(), GblOpsError> {
- Err(GblOpsError(Some("not defined yet")))
+ block_on(self.write_to_partition(part, off, data))
}
/// Returns the size of a partiiton. Returns Ok(None) if partition doesn't exist.
- fn partition_size(&mut self, part: &str) -> Result<Option<usize>, GblOpsError> {
- Err(GblOpsError(Some("not defined yet")))
- }
+ fn partition_size(&mut self, part: &str) -> Result<Option<u64>, GblOpsError>;
+
+ /// Adds device specific ZBI items to the given `container`
+ fn zircon_add_device_zbi_items(
+ &mut self,
+ container: &mut ZbiContainer<&mut [u8]>,
+ ) -> Result<(), GblOpsError>;
// TODO(b/334962570): figure out how to plumb ops-provided hash implementations into
// libavb. The tricky part is that libavb hashing APIs are global with no way to directly
@@ -121,15 +143,12 @@
fn do_fastboot<B: gbl_storage::AsBlockDevice>(
&self,
cursor: &mut slots::Cursor<B>,
- ) -> GblResult<()> {
- Err(Error::NotImplemented.into())
- }
+ ) -> GblResult<()>;
/// TODO: b/312607649 - placeholder interface for Gbl specific callbacks that uses alloc.
#[cfg(feature = "alloc")]
fn gbl_alloc_extra_action(&mut self, s: &str) -> GblResult<()> {
- let _c_string = CString::new(s);
- Err(Error::NotImplemented.into())
+ unimplemented!();
}
/// Load and initialize a slot manager and return a cursor over the manager on success.
@@ -137,38 +156,7 @@
&'a mut self,
block_device: &'a mut B,
boot_token: slots::BootToken,
- ) -> GblResult<slots::Cursor<'a, B>> {
- Err(Error::OperationProhibited.into())
- }
-}
-
-/// `GblUtils` takes a reference to `GblOps` and implements various traits.
-pub(crate) struct GblUtils<'a, T: GblOps> {
- ops: &'a mut T,
-}
-
-impl<'a, T: GblOps> GblUtils<'a, T> {
- /// Create a new instance.
- ///
- /// # Args
- ///
- /// * `ops`: A reference to a `GblOps`,
- ///
- /// # Returns
- ///
- /// Returns a new instance and the trailing unused part of the input scratch buffer.
- pub fn new(ops: &'a mut T) -> GblResult<Self> {
- Ok(Self { ops })
- }
-}
-
-impl<T: GblOps> Write for GblUtils<'_, T> {
- fn write_str(&mut self, s: &str) -> core::fmt::Result {
- for ch in s.as_bytes() {
- self.ops.console_put_char(*ch).map_err(|_| core::fmt::Error {})?;
- }
- Ok(())
- }
+ ) -> GblResult<slots::Cursor<'a, B>>;
}
/// Default [GblOps] implementation that returns errors and does nothing.
@@ -176,15 +164,59 @@
pub struct DefaultGblOps {}
impl GblOps for DefaultGblOps {
- fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError> {
- Err(GblOpsError(Some("unimplemented")))
+ fn console_out(&mut self) -> Option<&mut dyn Write> {
+ unimplemented!();
}
fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError> {
- Err(GblOpsError(Some("unimplemented")))
+ unimplemented!();
}
- fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError> {
- Err(GblOpsError(Some("unimplemented")))
+ fn preboot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ async fn read_from_partition(
+ &mut self,
+ part: &str,
+ off: u64,
+ out: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ async fn write_to_partition(
+ &mut self,
+ part: &str,
+ off: u64,
+ data: &mut [u8],
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ fn partition_size(&mut self, part: &str) -> Result<Option<u64>, GblOpsError> {
+ unimplemented!();
+ }
+
+ fn zircon_add_device_zbi_items(
+ &mut self,
+ container: &mut ZbiContainer<&mut [u8]>,
+ ) -> Result<(), GblOpsError> {
+ unimplemented!();
+ }
+
+ fn do_fastboot<B: gbl_storage::AsBlockDevice>(
+ &self,
+ cursor: &mut slots::Cursor<B>,
+ ) -> GblResult<()> {
+ unimplemented!();
+ }
+
+ fn load_slot_interface<'a, B: gbl_storage::AsBlockDevice>(
+ &'a mut self,
+ block_device: &'a mut B,
+ boot_token: slots::BootToken,
+ ) -> GblResult<slots::Cursor<'a, B>> {
+ unimplemented!();
}
}
diff --git a/gbl/libgbl/testdata/gen_test_data.py b/gbl/libgbl/testdata/gen_test_data.py
index 5a74fc3..bf94d88 100755
--- a/gbl/libgbl/testdata/gen_test_data.py
+++ b/gbl/libgbl/testdata/gen_test_data.py
@@ -15,6 +15,7 @@
# limitations under the License.
"""Generate test data files for libgbl tests"""
+import argparse
import os
import pathlib
import random
@@ -34,7 +35,7 @@
# reproducibility as much as possible; this will prevent adding a bunch of
# unnecessary test binaries to the git history.
RNG_SEED_SPARSE_TEST_RAW = 1
-RNG_SEED_ZIRCON = {"a": 2, "b": 3, "r": 4}
+RNG_SEED_ZIRCON = {"a": 2, "b": 3, "r": 4, "slotless": 5}
# A helper for writing bytes to a file at a given offset.
@@ -65,7 +66,9 @@
# For now this requires that img2simg exists on $PATH.
# It can be built from an Android checkout via `m img2simg`; the resulting
# binary will be at out/host/linux-x86/bin/img2simg.
- subprocess.run(["img2simg", "-s", out_file_raw, SCRIPT_DIR / "sparse_test.bin"])
+ subprocess.run(
+ ["img2simg", "-s", out_file_raw, SCRIPT_DIR / "sparse_test.bin"]
+ )
subprocess.run(
[
"img2simg",
@@ -77,19 +80,37 @@
)
-# Generates GPT disk, kernel data for Zircon tests
-def gen_zircon_gpt():
- gen_gpt_args = []
- for suffix in ["a", "b", "r"]:
- random.seed(RNG_SEED_ZIRCON[suffix])
- zircon = random.randbytes(16 * SZ_KB)
- out_file = SCRIPT_DIR / f"zircon_{suffix}.bin"
- out_file.write_bytes(zircon)
- gen_gpt_args.append(f"--partition=zircon_{suffix},16K,{str(out_file)}")
+def gen_zircon_test_images(zbi_tool):
+ if not zbi_tool:
+ print(
+ "Warning: ZBI tool not provided. Skip regenerating zircon test images"
+ )
+ return
- subprocess.run(
- [GPT_TOOL, SCRIPT_DIR / "zircon_gpt.bin", "128K"] + gen_gpt_args, check=True
- )
+ with tempfile.TemporaryDirectory() as temp_dir:
+ for suffix in ["a", "b", "r", "slotless"]:
+ temp_dir = pathlib.Path(temp_dir)
+ random.seed(RNG_SEED_ZIRCON[suffix])
+ out_kernel_bin_file = temp_dir / f"zircon_{suffix}.bin"
+ # The first 16 bytes are two u64 integers representing `entry` and
+ # `reserve_memory_size`.
+ # Set `entry` value to 2048 and `reserve_memory_size` to 1024.
+ kernel_bytes = int(2048).to_bytes(8, "little") + int(1024).to_bytes(
+ 8, "little"
+ )
+ kernel_bytes += random.randbytes(8 * SZ_KB - 16)
+ out_kernel_bin_file.write_bytes(kernel_bytes)
+ out_zbi_file = SCRIPT_DIR / f"zircon_{suffix}.zbi"
+ # Put image in a zbi container.
+ subprocess.run(
+ [
+ zbi_tool,
+ "--output",
+ out_zbi_file,
+ "--type=KERNEL_X64",
+ out_kernel_bin_file,
+ ]
+ )
# Generates test data for A/B slot Manager writeback test
@@ -139,7 +160,9 @@
# Also create a corrupted version of the permanent attributes to test failure.
# This is a little bit of a pain but we don't have an easy way to do a SHA256 in Rust
# at the moment so we can't generate it on the fly.
- bad_attrs = bytearray((SCRIPT_DIR / "cert_permanent_attributes.bin").read_bytes())
+ bad_attrs = bytearray(
+ (SCRIPT_DIR / "cert_permanent_attributes.bin").read_bytes()
+ )
bad_attrs[4] ^= 0x01 # Bytes 0-3 = version, byte 4 starts the public key.
(SCRIPT_DIR / "cert_permanent_attributes.bad.bin").write_bytes(bad_attrs)
hash_bytes = sha256_hash(SCRIPT_DIR / "cert_permanent_attributes.bad.bin")
@@ -173,7 +196,7 @@
"--partition_name",
"zircon_a",
"--image",
- SCRIPT_DIR / "zircon_a.bin",
+ SCRIPT_DIR / "zircon_a.zbi",
"--output_vbmeta_image",
hash_descriptor_path,
"--salt",
@@ -218,8 +241,22 @@
)
+def _parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+
+ parser.add_argument(
+ "--zbi_tool", default="", help="Path to the Fuchsia ZBI tool"
+ )
+
+ return parser.parse_args()
+
+
if __name__ == "__main__":
+ args = _parse_args()
gen_writeback_test_bin()
gen_sparse_test_file()
- gen_zircon_gpt()
+ gen_zircon_test_images(args.zbi_tool)
gen_vbmeta()
diff --git a/gbl/libgbl/testdata/zircon_a.bin b/gbl/libgbl/testdata/zircon_a.bin
deleted file mode 100644
index 10e7ce4..0000000
--- a/gbl/libgbl/testdata/zircon_a.bin
+++ /dev/null
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_a.vbmeta b/gbl/libgbl/testdata/zircon_a.vbmeta
index 13e15d1..1078e98 100644
--- a/gbl/libgbl/testdata/zircon_a.vbmeta
+++ b/gbl/libgbl/testdata/zircon_a.vbmeta
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_a.vbmeta.cert b/gbl/libgbl/testdata/zircon_a.vbmeta.cert
index cb69e5c..f7449db 100644
--- a/gbl/libgbl/testdata/zircon_a.vbmeta.cert
+++ b/gbl/libgbl/testdata/zircon_a.vbmeta.cert
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_a.zbi b/gbl/libgbl/testdata/zircon_a.zbi
new file mode 100644
index 0000000..e2bbf5e
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_a.zbi
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_b.zbi b/gbl/libgbl/testdata/zircon_b.zbi
new file mode 100644
index 0000000..cad59d7
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_b.zbi
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_gpt.bin b/gbl/libgbl/testdata/zircon_gpt.bin
deleted file mode 100644
index f873c07..0000000
--- a/gbl/libgbl/testdata/zircon_gpt.bin
+++ /dev/null
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_r.zbi b/gbl/libgbl/testdata/zircon_r.zbi
new file mode 100644
index 0000000..e04847e
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_r.zbi
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_slotless.zbi b/gbl/libgbl/testdata/zircon_slotless.zbi
new file mode 100644
index 0000000..d10c149
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_slotless.zbi
Binary files differ
diff --git a/gbl/libgbl/tests/integration_tests.rs b/gbl/libgbl/tests/integration_tests.rs
deleted file mode 100644
index b7dcf29..0000000
--- a/gbl/libgbl/tests/integration_tests.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2024, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use libgbl::{BootImages, FuchsiaBootImages, Gbl, GblOps, GblOpsError};
-use std::{
- collections::{BTreeMap, VecDeque},
- vec::Vec,
-};
-
-extern crate avb_sysdeps;
-
-/// `TestGblOps` provides mock implementation of GblOps for integration test.
-#[derive(Default)]
-struct TestGblOps<'a> {
- console_out: VecDeque<u8>,
- boot_cb: Option<MustUse<&'a mut dyn FnMut(BootImages)>>,
- storage: BTreeMap<&'static str, Vec<u8>>,
-}
-
-impl GblOps for TestGblOps<'_> {
- fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError> {
- Ok(self.console_out.push_back(ch))
- }
-
- fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError> {
- Ok(false)
- }
-
- fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError> {
- Ok((self.boot_cb.as_mut().unwrap().get())(boot_images))
- }
-
- async fn read_from_partition(
- &mut self,
- part: &str,
- off: u64,
- out: &mut [u8],
- ) -> Result<(), GblOpsError> {
- match self.storage.get_mut(part) {
- Some(v) => Ok(out.clone_from_slice(&v[off.try_into().unwrap()..][..out.len()])),
- _ => Err(GblOpsError(Some("Test: No such partition"))),
- }
- }
-
- async fn write_to_partition(
- &mut self,
- part: &str,
- off: u64,
- data: &mut [u8],
- ) -> Result<(), GblOpsError> {
- match self.storage.get_mut(part) {
- Some(v) => Ok(v[off.try_into().unwrap()..][..data.len()].clone_from_slice(data)),
- _ => Err(GblOpsError(Some("Test: No such partition"))),
- }
- }
-
- fn partition_size(&mut self, part: &str) -> Result<Option<usize>, GblOpsError> {
- Ok(self.storage.get_mut(part).map(|v| v.len()))
- }
-}
-
-/// `MustUse` wraps an object and checks that it is accessed at least once before it's dropped.
-/// In this integration test, it is mainly used to check that test provided ops callbacks are run.
-struct MustUse<T: ?Sized> {
- used: bool,
- val: T,
-}
-
-impl<T: ?Sized> MustUse<T> {
- /// Create a new instance.
- fn new(val: T) -> Self
- where
- T: Sized,
- {
- Self { used: false, val: val }
- }
-
- /// Returns a mutable reference to the object.
- fn get(&mut self) -> &mut T {
- self.used = true;
- &mut self.val
- }
-}
-
-impl<T: ?Sized> Drop for MustUse<T> {
- fn drop(&mut self) {
- assert!(self.used)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_zircon_load_and_boot() {
- // TODO(b/334962583): Invocation test only. Update this test once
- // `Gbl::zircon_load_and_boot()` is implemented.
- let mut boot_cb = |boot_images: BootImages| {
- let BootImages::Fuchsia(FuchsiaBootImages { zbi_kernel, zbi_items }) = boot_images
- else {
- panic!("Wrong image type");
- };
- assert_eq!(zbi_kernel, include_bytes!("../testdata/zircon_a.bin"));
- assert_eq!(zbi_items, []);
- };
- let mut ops: TestGblOps = Default::default();
- ops.storage =
- BTreeMap::from([("zircon_a", include_bytes!("../testdata/zircon_a.bin").to_vec())]);
- ops.boot_cb = Some(MustUse::new(&mut boot_cb));
- let mut gbl = Gbl::new(&mut ops);
- let mut load_buffer = vec![0u8; 64 * 1024];
- let _ = gbl.zircon_load_and_boot(&mut load_buffer[..]);
- }
-}
diff --git a/gbl/tests/BUILD b/gbl/tests/BUILD
index 705bff3..4a55b18 100644
--- a/gbl/tests/BUILD
+++ b/gbl/tests/BUILD
@@ -24,7 +24,6 @@
"@gbl//libefi:libefi_test",
"@gbl//libfastboot:libfastboot_test",
"@gbl//libfdt:libfdt_test",
- "@gbl//libgbl:integration_test",
"@gbl//libgbl:libgbl_test",
"@gbl//libsafemath:libsafemath_test",
"@gbl//libstorage:libstorage_doc_test",
diff --git a/gbl/third_party/libzbi/src/lib.rs b/gbl/third_party/libzbi/src/lib.rs
index 529a87d..7774df2 100644
--- a/gbl/third_party/libzbi/src/lib.rs
+++ b/gbl/third_party/libzbi/src/lib.rs
@@ -303,6 +303,11 @@
self.payload_length
}
+ /// Returns the total size including the ZBI container header, payload length after padding.
+ pub fn container_size(&self) -> usize {
+ self.get_payload_length_usize() + size_of::<ZbiHeader>()
+ }
+
/// Immutable iterator over ZBI elements. First element is first ZBI element after
/// container header. Container header is not available via iterator.
pub fn iter(&self) -> ZbiContainerIterator<impl ByteSlice + Default + Debug + PartialEq + '_> {
@@ -316,25 +321,56 @@
///
/// # Returns
///
- /// * `Ok(())` - if bootable
+ /// * `Ok(item)` - if bootable, where `item` is the ZBI kernel item.
/// * Err([`ZbiError::IncompleteKernel`]) - if first element in container has type not bootable
/// on target platform.
/// * Err([`ZbiError::Truncated`]) - if container is empty
- pub fn is_bootable(&self) -> ZbiResult<()> {
+ pub fn is_bootable(
+ &self,
+ ) -> ZbiResult<ZbiItem<impl ByteSlice + Default + Debug + PartialEq + '_>> {
let hdr = &self.header;
if hdr.length == 0 {
return Err(ZbiError::Truncated);
}
match self.iter().next() {
- Some(ZbiItem { header, payload: _ }) if header.type_ == ZBI_ARCH_KERNEL_TYPE as u32 => {
- Ok(())
- }
+ Some(v) if v.header.type_ == ZBI_ARCH_KERNEL_TYPE as u32 => Ok(v),
Some(_) => Err(ZbiError::IncompleteKernel),
None => Err(ZbiError::Truncated),
}
}
+ /// Returns the ZBI kernel `entry` and `reserved_memory_size` field value if the container is a
+ /// bootable ZBI kernel.
+ ///
+ /// # Returns
+ ///
+ /// * Returns `Ok((entry, reserved_memory_size))` on success.
+ /// * Returns `Err` if container is not a bootable ZBI kernel or is truncated.
+ pub fn get_kernel_entry_and_reserved_memory_size(&self) -> ZbiResult<(u64, u64)> {
+ let kernel = self.is_bootable()?;
+ let vals = Ref::<_, [u64]>::new_slice_from_prefix(kernel.payload, 2)
+ .ok_or(ZbiError::IncompleteKernel)?
+ .0
+ .into_slice();
+ Ok((vals[0], vals[1]))
+ }
+
+ /// Computes the required buffer size needed for relocating this ZBI kernel.
+ ///
+ /// # Returns
+ ///
+ /// * Returns `Ok(size)` on success.
+ /// * Returns `Err` if container is not a valid bootable ZBI kernel.
+ pub fn get_buffer_size_for_kernel_relocation(&self) -> ZbiResult<usize> {
+ let kernel = self.is_bootable()?;
+ let (_, reserve_memory_size) = self.get_kernel_entry_and_reserved_memory_size()?;
+ let kernel_size = 2 * size_of::<ZbiHeader>() + kernel.payload.as_bytes().len();
+ let reserve_memory_size =
+ usize::try_from(reserve_memory_size).map_err(|_| ZbiError::LengthOverflow)?;
+ kernel_size.checked_add(reserve_memory_size).ok_or(ZbiError::LengthOverflow)
+ }
+
/// Creates `ZbiContainer` from provided buffer.
///
/// Buffer must be aligned to [`ZBI_ALIGNMENT_USIZE`] ([`align_buffer`] could be
@@ -1128,7 +1164,7 @@
/// ```
pub type ZbiKernel = zbi_kernel_t;
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Eq)]
/// Error values that can be returned by function in this library
pub enum ZbiError {
/// Generic error
@@ -2056,6 +2092,10 @@
})
.sum::<usize>()
);
+ assert_eq!(
+ container.container_size(),
+ container.get_payload_length_usize() + size_of::<ZbiHeader>()
+ );
// Check if container elements match provided items
let mut it = expected_items.iter();
@@ -2447,6 +2487,41 @@
}
#[test]
+ fn zbi_test_get_kernel_entry_and_reserved_memory_size() {
+ let mut buffer = ZbiAligned::default();
+ let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
+ let bytes = [1u64.to_le_bytes(), 2u64.to_le_bytes()].concat();
+ container
+ .create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &bytes)
+ .unwrap();
+ assert_eq!(container.get_kernel_entry_and_reserved_memory_size().unwrap(), (1, 2));
+ }
+
+ #[test]
+ fn zbi_test_get_kernel_entry_and_reserved_memory_size_truncated() {
+ let mut buffer = ZbiAligned::default();
+ let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
+ container
+ .create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &[])
+ .unwrap();
+ assert!(container.get_kernel_entry_and_reserved_memory_size().is_err());
+ }
+
+ #[test]
+ fn zbi_get_buffer_size_for_kernel_relocation() {
+ let mut buffer = ZbiAligned::default();
+ let mut container = ZbiContainer::new(&mut buffer.0[..]).unwrap();
+ let bytes = [0u64.to_le_bytes(), 1024u64.to_le_bytes()].concat();
+ container
+ .create_entry_with_payload(ZBI_ARCH_KERNEL_TYPE, 0, ZbiFlags::default(), &bytes)
+ .unwrap();
+ assert_eq!(
+ container.get_buffer_size_for_kernel_relocation().unwrap(),
+ container.container_size() + 1024
+ );
+ }
+
+ #[test]
fn zbi_test_header_alignment() {
assert_eq!(core::mem::size_of::<ZbiHeader>() & ZBI_ALIGNMENT_USIZE, 0);
}