gbl: create libutils We have a bunch of separate implementations of `aligned_subslice()` scattered throughout the code, which have some slightly different behaviors and are not tested. This CL creates a libutils for common utility functions like this that are needed from various GBL projects. Some utilities may be able to go directly in other existing libraries, but for this function there didn't seem to be anything at quite the right place in the dependency hierarchy (depends on libsafemath and liberror, but not on libgbl or libstorage). This will also simplify the future refactoring of moving Android boot logic into GBL. Bug: b/363074091 Change-Id: Ia9770d10658e4aa5a2110630359069ef1c7aea9f
diff --git a/gbl/efi/BUILD b/gbl/efi/BUILD index fb5032e..4cee1fa 100644 --- a/gbl/efi/BUILD +++ b/gbl/efi/BUILD
@@ -43,6 +43,7 @@ "@gbl//libmisc", "@gbl//libsafemath", "@gbl//libstorage", + "@gbl//libutils", "@gbl//third_party/libzbi", "@smoltcp", "@spin",
diff --git a/gbl/efi/src/android_boot.rs b/gbl/efi/src/android_boot.rs index c8f023f..6a9fa1c 100644 --- a/gbl/efi/src/android_boot.rs +++ b/gbl/efi/src/android_boot.rs
@@ -13,10 +13,7 @@ // limitations under the License. use crate::{ - avb::GblEfiAvbOps, - efi_blocks::find_block_devices, - ops::Ops, - utils::{aligned_subslice, cstr_bytes_to_str}, + avb::GblEfiAvbOps, efi_blocks::find_block_devices, ops::Ops, utils::cstr_bytes_to_str, }; use avb::{slot_verify, HashtreeErrorMode, Ops as _, SlotVerifyFlags}; use bootconfig::BootConfigBuilder; @@ -27,6 +24,7 @@ use fdt::Fdt; use liberror::Error; use libgbl::{gbl_print, gbl_println, GblOps, IntegrationError, Result}; +use libutils::aligned_subslice; use misc::{AndroidBootMode, BootloaderMessage}; use safemath::SafeNum; use zerocopy::{AsBytes, ByteSlice};
diff --git a/gbl/efi/src/utils.rs b/gbl/efi/src/utils.rs index 8f0f594..2b1a503 100644 --- a/gbl/efi/src/utils.rs +++ b/gbl/efi/src/utils.rs
@@ -27,21 +27,10 @@ use fdt::FdtHeader; use liberror::Error; use libgbl::Result; -use safemath::SafeNum; pub const EFI_DTB_TABLE_GUID: EfiGuid = EfiGuid::new(0xb1b621d5, 0xf19c, 0x41a5, [0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0]); -/// Gets a subslice of the given slice with aligned address according to `alignment` -pub fn aligned_subslice( - bytes: &mut [u8], - alignment: usize, -) -> core::result::Result<&mut [u8], Error> { - let addr = bytes.as_ptr() as usize; - let aligned_start = SafeNum::from(addr).round_up(alignment) - addr; - Ok(&mut bytes[aligned_start.try_into()?..]) -} - /// Helper function to get the `DevicePathText` from a `DeviceHandle`. pub fn get_device_path<'a>( entry: &'a EfiEntry,
diff --git a/gbl/libefi/BUILD b/gbl/libefi/BUILD index 969947d..be1c4e7 100644 --- a/gbl/libefi/BUILD +++ b/gbl/libefi/BUILD
@@ -32,6 +32,7 @@ "@gbl//libgbl", "@gbl//libsafemath", "@gbl//libstorage", + "@gbl//libutils", "@spin", "@zerocopy", ],
diff --git a/gbl/libefi/src/lib.rs b/gbl/libefi/src/lib.rs index e88e5dd..0a77979 100644 --- a/gbl/libefi/src/lib.rs +++ b/gbl/libefi/src/lib.rs
@@ -76,6 +76,7 @@ EFI_LOCATE_HANDLE_SEARCH_TYPE_BY_PROTOCOL, EFI_OPEN_PROTOCOL_ATTRIBUTE_BY_HANDLE_PROTOCOL, }; use liberror::{Error, Result}; +use libutils::aligned_subslice; use protocol::{ simple_text_output::SimpleTextOutputProtocol, {Protocol, ProtocolInfo}, @@ -135,17 +136,6 @@ Ok(efi_entry) } -/// A helper for getting a subslice with an aligned address. -pub fn aligned_subslice(buffer: &mut [u8], alignment: usize) -> Option<&mut [u8]> { - let addr = buffer.as_ptr() as usize; - let aligned_offset = addr - .checked_add(alignment - 1)? - .checked_div(alignment)? - .checked_mul(alignment)? - .checked_sub(addr)?; - buffer.get_mut(aligned_offset..) -} - /// Exits boot service and returns the memory map in the given buffer. /// /// The API takes ownership of the given `entry` and causes it to go out of scope. @@ -155,8 +145,7 @@ /// Existing heap allocated memories will maintain their states. All system memory including them /// will be under onwership of the subsequent OS or OS loader code. pub fn exit_boot_services(entry: EfiEntry, mmap_buffer: &mut [u8]) -> Result<EfiMemoryMap> { - let aligned = aligned_subslice(mmap_buffer, core::mem::align_of::<EfiMemoryDescriptor>()) - .ok_or(Error::BufferTooSmall(None))?; + let aligned = aligned_subslice(mmap_buffer, core::mem::align_of::<EfiMemoryDescriptor>())?; let res = entry.system_table().boot_services().get_memory_map(aligned)?; entry.system_table().boot_services().exit_boot_services(&res)?;
diff --git a/gbl/libgbl/BUILD b/gbl/libgbl/BUILD index 2132066..10fb699 100644 --- a/gbl/libgbl/BUILD +++ b/gbl/libgbl/BUILD
@@ -38,6 +38,7 @@ "@gbl//libfastboot", "@gbl//libsafemath", "@gbl//libstorage", + "@gbl//libutils", "@gbl//third_party/libzbi", "@itertools_noalloc", "@spin",
diff --git a/gbl/libgbl/src/fuchsia_boot/mod.rs b/gbl/libgbl/src/fuchsia_boot/mod.rs index bae47ec..d3c0b89 100644 --- a/gbl/libgbl/src/fuchsia_boot/mod.rs +++ b/gbl/libgbl/src/fuchsia_boot/mod.rs
@@ -18,6 +18,7 @@ pub use abr::{get_boot_slot, Ops as AbrOps, SlotIndex}; use core::fmt::Write; use liberror::{Error, Result}; +use libutils::aligned_subslice; use safemath::SafeNum; use zbi::{ZbiContainer, ZbiFlags, ZbiHeader, ZbiType}; use zerocopy::AsBytes; @@ -54,13 +55,6 @@ (addr.round_up(alignment) - addr).try_into().map_err(From::from) } -/// A helper for getting a subslice with an aligned address. -fn aligned_subslice(buffer: &mut [u8], alignment: usize) -> Result<&mut [u8]> { - let aligned_offset = aligned_offset(buffer, alignment)?; - let len = buffer.len(); - Ok(buffer.get_mut(aligned_offset..).ok_or(Error::BufferTooSmall(Some(aligned_offset)))?) -} - /// A helper for splitting the trailing unused portion of a ZBI container buffer. /// /// Returns a tuple of used subslice and unused subslice
diff --git a/gbl/libstorage/BUILD b/gbl/libstorage/BUILD index bffb4a4..2707116 100644 --- a/gbl/libstorage/BUILD +++ b/gbl/libstorage/BUILD
@@ -34,6 +34,7 @@ "@gbl//libasync", "@gbl//liberror", "@gbl//libsafemath", + "@gbl//libutils", "@zerocopy", ], )
diff --git a/gbl/libstorage/src/algorithm.rs b/gbl/libstorage/src/algorithm.rs index 79834ee..b02f838 100644 --- a/gbl/libstorage/src/algorithm.rs +++ b/gbl/libstorage/src/algorithm.rs
@@ -12,13 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - aligned_subslice, check_range, is_aligned, is_buffer_aligned, BlockInfo, BlockIoAsync, - BlockIoSync, -}; +use crate::{check_range, is_aligned, is_buffer_aligned, BlockInfo, BlockIoAsync, BlockIoSync}; use core::cmp::min; use gbl_async::block_on; use liberror::Result; +use libutils::aligned_subslice; use safemath::SafeNum; /// Reads from a range at block boundary to an aligned buffer.
diff --git a/gbl/libstorage/src/gpt.rs b/gbl/libstorage/src/gpt.rs index d9aa376..c83cd05 100644 --- a/gbl/libstorage/src/gpt.rs +++ b/gbl/libstorage/src/gpt.rs
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{aligned_subslice, read_async, write_async, BlockIoAsync, Result}; +use crate::{read_async, write_async, BlockIoAsync, Result}; use core::{ convert::TryFrom, default::Default, @@ -21,11 +21,11 @@ str::from_utf8, }; use crc32fast::Hasher; +use liberror::Error; +use libutils::aligned_subslice; use safemath::SafeNum; use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref}; -use liberror::Error; - const GPT_GUID_LEN: usize = 16; /// The maximum number of UTF-16 characters in a GPT partition name, including termination. pub const GPT_NAME_LEN_U16: usize = 36;
diff --git a/gbl/libstorage/src/lib.rs b/gbl/libstorage/src/lib.rs index 13a7922..aac3a52 100644 --- a/gbl/libstorage/src/lib.rs +++ b/gbl/libstorage/src/lib.rs
@@ -541,12 +541,6 @@ ((SafeNum::from(info.alignment) - 1) * 2 + block_alignment).try_into().map_err(Into::into) } -/// Gets a subslice of the given slice with aligned address according to `alignment` -fn aligned_subslice(buffer: &mut [u8], alignment: u64) -> Result<&mut [u8]> { - let addr = SafeNum::from(buffer.as_ptr() as usize); - Ok(&mut buffer[(addr.round_up(alignment) - addr).try_into()?..]) -} - /// `AsyncBlockDevice` provides APIs for asynchronous read/write of raw block or GPT partitions. pub struct AsyncBlockDevice<'a, T: BlockIoAsync> { io: T,
diff --git a/gbl/libutils/BUILD b/gbl/libutils/BUILD new file mode 100644 index 0000000..fe7e4f0 --- /dev/null +++ b/gbl/libutils/BUILD
@@ -0,0 +1,36 @@ +# Copyright (C) 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. + +load("@gbl//toolchain:gbl_workspace_util.bzl", "ANDROID_RUST_LINTS") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "libutils", + srcs = [ + "src/lib.rs", + ], + edition = "2021", + rustc_flags = ANDROID_RUST_LINTS, + visibility = ["//visibility:public"], + deps = [ + "@gbl//liberror", + "@gbl//libsafemath", + ], +) + +rust_test( + name = "test", + crate = ":libutils", + rustc_flags = ANDROID_RUST_LINTS, +)
diff --git a/gbl/libutils/src/lib.rs b/gbl/libutils/src/lib.rs new file mode 100644 index 0000000..f9577b4 --- /dev/null +++ b/gbl/libutils/src/lib.rs
@@ -0,0 +1,117 @@ +// Copyright (C) 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. + +//! Low-level utilities shared across multiple GBL libraries. + +#![cfg_attr(not(test), no_std)] + +use liberror::{Error, Result}; +use safemath::SafeNum; + +/// Returns the largest aligned subslice. +/// +/// This function drops as many bytes as needed from the front of the given slice to ensure the +/// result is properly-aligned. It does not truncate bytes from the end, so the resulting size may +/// not be a multiple of `alignment`. +/// +/// If the next `alignment` boundary would be directly following the last byte, this returns the +/// 0-length slice at that alignment rather than an error, to match standard slicing behavior. +/// +/// # Arguments +/// * `bytes`: the byte slice to align +/// * `alignment`: the desired starting alignment +/// +/// # Returns +/// * The subslice on success +/// * [Error::ArithmeticOverflow] if `bytes` overflows when finding the next `alignment` +/// * [Error::BufferTooSmall] if `bytes` is not large enough to reach the next `alignment`. The +/// error will contain the size that would have been needed to reach `alignment`. +pub fn aligned_subslice<T>(bytes: &mut [u8], alignment: T) -> Result<&mut [u8]> +where + T: Copy + Into<SafeNum>, +{ + let addr = bytes.as_ptr() as usize; + let aligned_offset = (SafeNum::from(addr).round_up(alignment) - addr).try_into()?; + Ok(bytes.get_mut(aligned_offset..).ok_or(Error::BufferTooSmall(Some(aligned_offset)))?) +} + +#[cfg(test)] +mod test { + use super::*; + + // A byte array that's always at least 8-byte aligned for testing. + #[repr(align(8))] + struct AlignedBytes<const N: usize>([u8; N]); + + #[test] + fn aligned_subslice_already_aligned() { + let mut bytes = AlignedBytes([0u8; 16]); + let bytes = &mut bytes.0; + + // AlignedBytes is `align(8)`, so must be 1/2/4/8-aligned. + assert_eq!(aligned_subslice(bytes, 1).unwrap().as_ptr_range(), bytes.as_ptr_range()); + assert_eq!(aligned_subslice(bytes, 2).unwrap().as_ptr_range(), bytes.as_ptr_range()); + assert_eq!(aligned_subslice(bytes, 4).unwrap().as_ptr_range(), bytes.as_ptr_range()); + assert_eq!(aligned_subslice(bytes, 8).unwrap().as_ptr_range(), bytes.as_ptr_range()); + } + + #[test] + fn aligned_subslice_unaligned() { + let mut bytes = AlignedBytes([0u8; 16]); + let bytes = &mut bytes.0; + + // AlignedBytes is 8-aligned, so offsetting by <8 should snap to the next 8-alignment. + assert_eq!( + aligned_subslice(&mut bytes[1..], 8).unwrap().as_ptr_range(), + bytes[8..].as_ptr_range() + ); + assert_eq!( + aligned_subslice(&mut bytes[4..], 8).unwrap().as_ptr_range(), + bytes[8..].as_ptr_range() + ); + assert_eq!( + aligned_subslice(&mut bytes[7..], 8).unwrap().as_ptr_range(), + bytes[8..].as_ptr_range() + ); + } + + #[test] + fn aligned_subslice_empty_slice() { + let mut bytes = AlignedBytes([0u8; 16]); + let bytes = &mut bytes.0; + + // If the next alignment is just past the input, return the empty slice. + assert_eq!( + aligned_subslice(&mut bytes[9..], 8).unwrap().as_ptr_range(), + bytes[16..].as_ptr_range() + ); + } + + #[test] + fn aligned_subslice_buffer_overflow() { + let mut bytes = AlignedBytes([0u8; 7]); // 7 bytes; can't reach the next 8-alignment. + let bytes = &mut bytes.0; + + assert_eq!(aligned_subslice(&mut bytes[1..], 8), Err(Error::BufferTooSmall(Some(7)))); + assert_eq!(aligned_subslice(&mut bytes[6..], 8), Err(Error::BufferTooSmall(Some(2)))); + } + + #[test] + fn aligned_subslice_alignment_overflow() { + let mut bytes = AlignedBytes([0u8; 16]); + let bytes = &mut bytes.0; + + assert!(matches!(aligned_subslice(bytes, SafeNum::MAX), Err(Error::ArithmeticOverflow(_)))); + } +}
diff --git a/gbl/tests/BUILD b/gbl/tests/BUILD index a20eccd..7f85fd8 100644 --- a/gbl/tests/BUILD +++ b/gbl/tests/BUILD
@@ -36,6 +36,7 @@ "@gbl//libstorage:libstorage_doc_test", "@gbl//libstorage:libstorage_test", "@gbl//libstorage:libstorage_testlib_test", + "@gbl//libutils:test", "@gbl//third_party/libzbi:libzbi_test", ], )