blob: bc83eb1e8a91ef971dd27fe1e13b93dc3bbec763 [file] [log] [blame]
// Copyright 2023, 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.
//! The public interface for bootimg structs
use zerocopy::{ByteSlice, LayoutVerified};
use bootimg_private::{
boot_img_hdr_v0, boot_img_hdr_v1, boot_img_hdr_v2, boot_img_hdr_v3, boot_img_hdr_v4,
vendor_boot_img_hdr_v3, vendor_boot_img_hdr_v4, BOOT_MAGIC, BOOT_MAGIC_SIZE, VENDOR_BOOT_MAGIC,
VENDOR_BOOT_MAGIC_SIZE,
};
/// Generalized boot image from a backing store of bytes.
#[derive(PartialEq, Debug)]
pub enum BootImage<B: ByteSlice + PartialEq> {
/// Version 0 header
V0(LayoutVerified<B, boot_img_hdr_v0>),
/// Version 1 header
V1(LayoutVerified<B, boot_img_hdr_v1>),
/// Version 2 header
V2(LayoutVerified<B, boot_img_hdr_v2>),
/// Version 3 header
V3(LayoutVerified<B, boot_img_hdr_v3>),
/// Version 4 header
V4(LayoutVerified<B, boot_img_hdr_v4>),
}
/// Generalized vendor boot header from a backing store of bytes.
#[derive(PartialEq, Debug)]
pub enum VendorImageHeader<B: ByteSlice + PartialEq> {
/// Version 3 header
V3(LayoutVerified<B, vendor_boot_img_hdr_v3>),
/// Version 4 header
V4(LayoutVerified<B, vendor_boot_img_hdr_v4>),
}
/// Boot related errors.
#[derive(PartialEq, Debug)]
pub enum ImageError {
/// The provided buffer was too small to hold a header.
BufferTooSmall,
/// The magic string was incorrect.
BadMagic,
/// The header version present is not supported.
UnexpectedVersion,
/// Catch-all for remaining errors.
Unknown,
}
impl core::fmt::Display for ImageError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let str = match self {
Self::BufferTooSmall => "The provided buffer is too small",
Self::BadMagic => "The magic string is incorrect",
Self::UnexpectedVersion => "Header version is not supported",
Self::Unknown => "Unknown error",
};
write!(f, "{str}")
}
}
/// Common result type for use with boot headers
pub type BootResult<T> = Result<T, ImageError>;
fn parse_header<B: ByteSlice + PartialEq, T>(buffer: B) -> BootResult<LayoutVerified<B, T>> {
Ok(LayoutVerified::<B, T>::new_from_prefix(buffer).ok_or(ImageError::BufferTooSmall)?.0)
}
impl<B: ByteSlice + PartialEq> BootImage<B> {
/// Given a byte buffer, attempt to parse the contents and return a zero-copy reference
/// to the associated boot image header.
///
/// # Arguments
/// * `buffer` - buffer to parse
///
/// # Returns
///
/// * `Ok(BootImage)` - if parsing was successful.
/// * `Err(ImageError)` - if `buffer` does not contain a valid boot image header.
///
/// # Example
///
/// ```
/// use bootimg::BootImage;
///
/// let mut buffer = [0; 4096];
/// // Not shown: read first 4096 bytes of boot image into buffer
/// let header = BootImage::parse(&buffer[..]).unwrap();
/// ```
pub fn parse(buffer: B) -> BootResult<Self> {
let magic_size = BOOT_MAGIC_SIZE as usize;
// Note: even though the v3 header is not a prefix for the v0, v1, or v2 header,
// the version and the magic string exist at the same offset and have the same types.
// Make a v3 temporary because it is the smallest.
let (hdr, _) =
LayoutVerified::<&[u8], boot_img_hdr_v3>::new_from_prefix(buffer.get(..).unwrap())
.ok_or(ImageError::BufferTooSmall)?;
if hdr.magic.ne(&BOOT_MAGIC[..magic_size]) {
return Err(ImageError::BadMagic);
}
match hdr.header_version {
0 => Ok(Self::V0(parse_header::<B, boot_img_hdr_v0>(buffer)?)),
1 => Ok(Self::V1(parse_header::<B, boot_img_hdr_v1>(buffer)?)),
2 => Ok(Self::V2(parse_header::<B, boot_img_hdr_v2>(buffer)?)),
3 => Ok(Self::V3(parse_header::<B, boot_img_hdr_v3>(buffer)?)),
4 => Ok(Self::V4(parse_header::<B, boot_img_hdr_v4>(buffer)?)),
_ => Err(ImageError::UnexpectedVersion),
}
}
}
impl<B: ByteSlice + PartialEq> VendorImageHeader<B> {
/// Given a byte buffer, attempt to parse the contents and return a zero-copy reference
/// to the associated vendor boot image header.
///
/// # Arguments
/// * `buffer` - buffer to parse
///
/// # Returns
///
/// * `Ok(VendorImageHeader)` - if parsing was successful.
/// * `Err(ImageError)` - If `buffer` does not contain a valid boot image header.
///
/// # Example
///
/// ```
/// use bootimg::VendorImageHeader;
///
/// let mut buffer = [0; 4096];
/// // Not shown: read first 4096 bytes of vendor image into buffer
/// let header = VendorImageHeader::parse(&buffer[..]).unwrap();
/// ```
pub fn parse(buffer: B) -> BootResult<Self> {
let magic_size = VENDOR_BOOT_MAGIC_SIZE as usize;
let (hdr, _) = LayoutVerified::<&[u8], vendor_boot_img_hdr_v3>::new_from_prefix(
buffer.get(..).unwrap(),
)
.ok_or(ImageError::BufferTooSmall)?;
if hdr.magic.ne(&VENDOR_BOOT_MAGIC[..magic_size]) {
return Err(ImageError::BadMagic);
}
match hdr.header_version {
3 => Ok(Self::V3(parse_header::<B, vendor_boot_img_hdr_v3>(buffer)?)),
4 => Ok(Self::V4(parse_header::<B, vendor_boot_img_hdr_v4>(buffer)?)),
_ => Err(ImageError::UnexpectedVersion),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use zerocopy::AsBytes;
const MAGIC_SIZE: usize = BOOT_MAGIC_SIZE as usize;
const VENDOR_MAGIC_SIZE: usize = VENDOR_BOOT_MAGIC_SIZE as usize;
pub fn add<T: AsBytes>(buffer: &mut [u8], t: T) {
t.write_to_prefix(buffer).unwrap();
}
#[test]
fn buffer_too_small_for_version() {
let buffer = [0; 40];
assert_eq!(BootImage::parse(&buffer[..]), Err(ImageError::BufferTooSmall));
}
#[test]
fn buffer_too_small_valid_version() {
// Note: because the v1 header fully encapsulates the v0 header,
// we can trigger a buffer-too-small error by providing
// a perfectly valid v0 header and changing the version to 1.
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v0>()];
add::<boot_img_hdr_v0>(
&mut buffer,
boot_img_hdr_v0 {
magic: BOOT_MAGIC[0..MAGIC_SIZE].try_into().unwrap(),
header_version: 1,
..Default::default()
},
);
assert_eq!(BootImage::parse(&buffer[..]), Err(ImageError::BufferTooSmall));
}
#[test]
fn bad_magic() {
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v0>()];
add::<boot_img_hdr_v0>(
&mut buffer,
boot_img_hdr_v0 { magic: *b"ANDROGEN", ..Default::default() },
);
assert_eq!(BootImage::parse(&buffer[..]), Err(ImageError::BadMagic));
}
#[test]
fn bad_version() {
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v0>()];
add::<boot_img_hdr_v0>(
&mut buffer,
boot_img_hdr_v0 {
magic: BOOT_MAGIC[0..MAGIC_SIZE].try_into().unwrap(),
header_version: 2112,
..Default::default()
},
);
assert_eq!(BootImage::parse(&buffer[..]), Err(ImageError::UnexpectedVersion));
}
#[test]
fn parse_v0() {
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v0>()];
add::<boot_img_hdr_v0>(
&mut buffer,
boot_img_hdr_v0 {
magic: BOOT_MAGIC[0..MAGIC_SIZE].try_into().unwrap(),
header_version: 0,
..Default::default()
},
);
let expected =
Ok(BootImage::V0(LayoutVerified::<&[u8], boot_img_hdr_v0>::new(&buffer).unwrap()));
assert_eq!(BootImage::parse(&buffer[..]), expected);
}
#[test]
fn parse_v1() {
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v1>()];
add::<boot_img_hdr_v1>(
&mut buffer,
boot_img_hdr_v1 {
_base: boot_img_hdr_v0 {
magic: BOOT_MAGIC[0..MAGIC_SIZE].try_into().unwrap(),
header_version: 1,
..Default::default()
},
..Default::default()
},
);
let expected =
Ok(BootImage::V1(LayoutVerified::<&[u8], boot_img_hdr_v1>::new(&buffer).unwrap()));
assert_eq!(BootImage::parse(&buffer[..]), expected);
}
#[test]
fn parse_v2() {
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v2>()];
add::<boot_img_hdr_v2>(
&mut buffer,
boot_img_hdr_v2 {
_base: boot_img_hdr_v1 {
_base: boot_img_hdr_v0 {
magic: BOOT_MAGIC[0..MAGIC_SIZE].try_into().unwrap(),
header_version: 2,
..Default::default()
},
..Default::default()
},
..Default::default()
},
);
let expected =
Ok(BootImage::V2(LayoutVerified::<&[u8], boot_img_hdr_v2>::new(&buffer).unwrap()));
assert_eq!(BootImage::parse(&buffer[..]), expected);
}
#[test]
fn parse_v3() {
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v3>()];
add::<boot_img_hdr_v3>(
&mut buffer,
boot_img_hdr_v3 {
magic: BOOT_MAGIC[0..MAGIC_SIZE].try_into().unwrap(),
header_version: 3,
..Default::default()
},
);
let expected =
Ok(BootImage::V3(LayoutVerified::<&[u8], boot_img_hdr_v3>::new(&buffer).unwrap()));
assert_eq!(BootImage::parse(&buffer[..]), expected);
}
#[test]
fn parse_v4() {
let mut buffer = [0; core::mem::size_of::<boot_img_hdr_v4>()];
add::<boot_img_hdr_v4>(
&mut buffer,
boot_img_hdr_v4 {
_base: boot_img_hdr_v3 {
magic: BOOT_MAGIC[0..MAGIC_SIZE].try_into().unwrap(),
header_version: 4,
..Default::default()
},
..Default::default()
},
);
let expected =
Ok(BootImage::V4(LayoutVerified::<&[u8], boot_img_hdr_v4>::new(&buffer).unwrap()));
assert_eq!(BootImage::parse(&buffer[..]), expected);
}
#[test]
fn vendor_buffer_too_small_for_version() {
let buffer = [0; VENDOR_MAGIC_SIZE + 3];
assert_eq!(VendorImageHeader::parse(&buffer[..]), Err(ImageError::BufferTooSmall));
}
#[test]
fn vendor_bad_magic() {
let mut buffer = [0; core::mem::size_of::<vendor_boot_img_hdr_v3>()];
add::<vendor_boot_img_hdr_v3>(
&mut buffer,
vendor_boot_img_hdr_v3 { magic: *b"VNDRBOOK", header_version: 3, ..Default::default() },
);
assert_eq!(VendorImageHeader::parse(&buffer[..]), Err(ImageError::BadMagic));
}
#[test]
fn vendor_bad_version() {
let mut buffer = [0; core::mem::size_of::<vendor_boot_img_hdr_v3>()];
add::<vendor_boot_img_hdr_v3>(
&mut buffer,
vendor_boot_img_hdr_v3 {
magic: VENDOR_BOOT_MAGIC[0..VENDOR_MAGIC_SIZE].try_into().unwrap(),
header_version: 2112,
..Default::default()
},
);
assert_eq!(VendorImageHeader::parse(&buffer[..]), Err(ImageError::UnexpectedVersion));
}
#[test]
fn vendor_buffer_too_small_valid_version() {
let mut buffer = [0; core::mem::size_of::<vendor_boot_img_hdr_v3>()];
add::<vendor_boot_img_hdr_v3>(
&mut buffer,
vendor_boot_img_hdr_v3 {
magic: VENDOR_BOOT_MAGIC[0..VENDOR_MAGIC_SIZE].try_into().unwrap(),
// Note: because the v4 header fully encapsulates the v3 header,
// we can trigger a buffer-too-small error by providing
// a perfectly valid v3 header and changing the version to 4.
header_version: 4,
..Default::default()
},
);
assert_eq!(VendorImageHeader::parse(&buffer[..]), Err(ImageError::BufferTooSmall));
}
#[test]
fn vendor_parse_v3() {
let mut buffer = [0; core::mem::size_of::<vendor_boot_img_hdr_v3>()];
add::<vendor_boot_img_hdr_v3>(
&mut buffer,
vendor_boot_img_hdr_v3 {
magic: VENDOR_BOOT_MAGIC[0..VENDOR_MAGIC_SIZE].try_into().unwrap(),
header_version: 3,
..Default::default()
},
);
let expected = Ok(VendorImageHeader::V3(
LayoutVerified::<&[u8], vendor_boot_img_hdr_v3>::new(&buffer).unwrap(),
));
assert_eq!(VendorImageHeader::parse(&buffer[..]), expected);
}
#[test]
fn vendor_parse_v4() {
let mut buffer = [0; core::mem::size_of::<vendor_boot_img_hdr_v4>()];
add::<vendor_boot_img_hdr_v4>(
&mut buffer,
vendor_boot_img_hdr_v4 {
_base: vendor_boot_img_hdr_v3 {
magic: VENDOR_BOOT_MAGIC[0..VENDOR_MAGIC_SIZE].try_into().unwrap(),
header_version: 4,
..Default::default()
},
..Default::default()
},
);
let expected = Ok(VendorImageHeader::V4(
LayoutVerified::<&[u8], vendor_boot_img_hdr_v4>::new(&buffer).unwrap(),
));
assert_eq!(VendorImageHeader::parse(&buffer[..]), expected);
}
}