// 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. use crate::{aligned_subslice, read, write_bytes_mut, BlockIo, Result, StorageError}; use core::default::Default; use core::mem::{align_of, size_of}; use core::num::NonZeroU64; use crc32fast::Hasher; use safemath::SafeNum; use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref}; const GPT_GUID_LEN: usize = 16; pub const GPT_NAME_LEN_U16: usize = 36; #[repr(C, packed)] #[derive(Debug, Default, Copy, Clone, AsBytes, FromBytes, FromZeroes)] pub struct GptHeader { pub magic: u64, pub revision: u32, pub size: u32, pub crc32: u32, pub reserved0: u32, pub current: u64, pub backup: u64, pub first: u64, pub last: u64, pub guid: [u8; GPT_GUID_LEN], pub entries: u64, pub entries_count: u32, pub entries_size: u32, pub entries_crc: u32, } impl GptHeader { /// Cast a bytes slice into a GptHeader structure. fn from_bytes(bytes: &mut [u8]) -> &mut GptHeader { Ref::<_, GptHeader>::new_from_prefix(bytes).unwrap().0.into_mut() } /// Update the header crc32 value. pub fn update_crc(&mut self) { self.crc32 = 0; self.crc32 = crc32(self.as_bytes()); } } /// GptEntry is the partition entry data structure in the GPT. #[repr(C)] #[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)] pub struct GptEntry { pub part_type: [u8; GPT_GUID_LEN], pub guid: [u8; GPT_GUID_LEN], pub first: u64, pub last: u64, pub flags: u64, pub name: [u16; GPT_NAME_LEN_U16], } impl GptEntry { /// Return the partition entry size in blocks. pub fn blocks(&self) -> Result { u64::try_from((SafeNum::from(self.last) - self.first) + 1).map_err(|e| e.into()) } /// Return whether this is a `NULL` entry. The first null entry marks the end of the partition /// entries. fn is_null(&self) -> bool { self.first == 0 && self.last == 0 } /// Decode the partition name into a string. A length N utf16 string can be at most 2N utf8 /// bytes. Therefore, a safe size of `buffer` is 2*GPT_NAME_LEN_U16 = 72. pub fn name_to_str<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a str> { let mut index = 0; for c in char::decode_utf16(self.name) { match c.unwrap_or(char::REPLACEMENT_CHARACTER) { '\0' => break, c if c.len_utf8() <= buffer[index..].len() => { index += c.encode_utf8(&mut buffer[index..]).len() } _ => return Err(StorageError::InvalidInput), // Not enough space in `buffer`. } } // SAFETY: // _unchecked should be OK here since we wrote each utf8 byte ourselves, // but it's just an optimization, checked version would be fine also. unsafe { Ok(core::str::from_utf8_unchecked(&buffer[..index])) } } } impl core::fmt::Display for GptEntry { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // Format: partition name: "abc", [first, last]: [123, 456] let mut name_conversion_buffer = [0u8; GPT_NAME_LEN_U16 * 2]; let name = self.name_to_str(&mut name_conversion_buffer).map_err(|_| core::fmt::Error)?; write!(f, "partition name: \"{}\", [first, last]: [{}, {}]", name, self.first, self.last) } } // core::mem::offset_of!(GptHeader, crc32) is unsatble feature and rejected by the compiler in our // settings. We pre-compute the value here. const GPT_CRC32_OFFSET: u64 = 16; const GPT_ENTRY_ALIGNMENT: u64 = align_of::() as u64; const GPT_ENTRY_SIZE: u64 = size_of::() as u64; const GPT_MAX_NUM_ENTRIES: u64 = 128; const GPT_HEADER_SIZE: u64 = size_of::() as u64; // 92 bytes. const GPT_HEADER_SIZE_PADDED: u64 = (GPT_HEADER_SIZE + GPT_ENTRY_ALIGNMENT - 1) / GPT_ENTRY_ALIGNMENT * GPT_ENTRY_ALIGNMENT; pub const GPT_MAGIC: u64 = 0x5452415020494645; enum HeaderType { Primary, Secondary, } #[repr(C)] #[derive(Debug, Default, Copy, Clone, AsBytes, FromBytes, FromZeroes)] struct GptInfo { // The number of valid entries in the entries array. // May change as partitions are added or removed. num_valid_entries: Option, // The maximum number of elements available in the entries array. // Note: this is GREATER THAN OR EQUAL TO the number of valid entries // and LESS THAN OR EQUAL TO the value of GPT_MAX_NUM_ENTRIES. // Values other than GPT_MAX_NUM_ENTRIES are mostly used in unit tests. max_entries: u64, // Block size of the GPT disk. block_size: u64, } impl GptInfo { fn from_bytes(bytes: &mut [u8]) -> &mut Self { Ref::<_, GptInfo>::new_from_prefix(bytes).unwrap().0.into_mut() } fn num_valid_entries(&self) -> Result { Ok(self.num_valid_entries.ok_or_else(|| StorageError::InvalidInput)?.get()) } } /// An object that contains the GPT header/entries information. pub(crate) struct Gpt<'a> { info: &'a mut GptInfo, /// Raw bytes of primary GPT header. primary_header: &'a mut [u8], /// Raw bytes of primary GPT entries. primary_entries: &'a mut [u8], /// Raw bytes of secondary GPT header. secondary_header: &'a mut [u8], /// Raw bytes of secondary GPT entries. secondary_entries: &'a mut [u8], } impl<'a> Gpt<'a> { /// Create an uninitialized Gpt instance from a provided buffer. /// /// # Args: /// /// * `max_entries`: Maximum number of entries allowed. /// /// * `buffer`: Buffer for creating the object. Must have a size at least /// `Gpt::required_buffer_size(max_entries)`. pub(crate) fn new_from_buffer(max_entries: u64, buffer: &'a mut [u8]) -> Result> { if max_entries > GPT_MAX_NUM_ENTRIES || buffer.len() < Self::required_buffer_size(max_entries)? { return Err(StorageError::InvalidInput); } let buffer = aligned_subslice(buffer, GPT_ENTRY_ALIGNMENT)?; *GptInfo::from_bytes(buffer) = GptInfo { num_valid_entries: None, max_entries, block_size: 0 }; Self::from_existing(buffer) } /// Reconstruct an existing Gpt struct from a buffer previously created with `new_from_buffer`. /// /// The method simply partitions the input buffer and populate the `GptInfo` struct and /// primary/secondary header/entries slices. It assumes that the buffer contains a valid /// GptInfo struct. pub fn from_existing(buffer: &'a mut [u8]) -> Result> { let buffer = aligned_subslice(buffer, GPT_ENTRY_ALIGNMENT)?; let (info, remain) = Ref::<_, GptInfo>::new_from_prefix(buffer).unwrap(); let entries_size = SafeNum::from(info.max_entries) * GPT_ENTRY_SIZE; let header_size: usize = SafeNum::from(GPT_HEADER_SIZE_PADDED).try_into()?; let split_pos = entries_size + header_size; let (primary, secondary) = remain.split_at_mut(split_pos.try_into()?); let (primary_header, primary_entries) = primary.split_at_mut(header_size); let (secondary_header, secondary_entries) = secondary.split_at_mut(header_size); Ok(Self { info: info.into_mut(), primary_header, primary_entries, secondary_header, secondary_entries: &mut secondary_entries[..entries_size.try_into()?], }) } /// The minimum buffer size needed for `new_from_buffer()` pub(crate) fn required_buffer_size(max_entries: u64) -> Result { let entries_size = SafeNum::from(max_entries) * GPT_ENTRY_SIZE; (((entries_size + GPT_HEADER_SIZE_PADDED) * 2) + size_of::() + GPT_ENTRY_ALIGNMENT - 1) .try_into() .map_err(|e: safemath::Error| e.into()) } /// Return the list of GPT entries. /// /// If the object does not contain a valid GPT, the method returns Error. pub(crate) fn entries(&self) -> Result<&[GptEntry]> { self.check_valid()?; Ok(&Ref::<_, [GptEntry]>::new_slice(&self.primary_entries[..]).unwrap().into_slice() [..self.info.num_valid_entries()?.try_into()?]) } /// Search for a partition entry. /// /// If partition doesn't exist, the method returns `Ok(None)`. /// /// If the object does not contain a valid GPT, the method returns Error. pub(crate) fn find_partition(&self, part: &str) -> Result<&GptEntry> { for entry in self.entries()? { let mut name_conversion_buffer = [0u8; GPT_NAME_LEN_U16 * 2]; if entry.name_to_str(&mut name_conversion_buffer)? != part { continue; } return Ok(entry); } Err(StorageError::NotExist) } /// Check whether the Gpt has been initialized. fn check_valid(&self) -> Result<()> { self.info.num_valid_entries()?; Ok(()) } /// Helper function for loading and validating GPT header and entries. fn validate_gpt( &mut self, blk_dev: &mut (impl BlockIo + ?Sized), scratch: &mut [u8], header_type: HeaderType, ) -> Result { let (header_start, header_bytes, entries) = match header_type { HeaderType::Primary => { (blk_dev.block_size().into(), &mut self.primary_header, &mut self.primary_entries) } HeaderType::Secondary => ( (SafeNum::from(blk_dev.num_blocks()) - 1) * blk_dev.block_size(), &mut self.secondary_header, &mut self.secondary_entries, ), }; read(blk_dev, header_start.try_into()?, header_bytes, scratch)?; let header = Ref::<_, GptHeader>::new_from_prefix(header_bytes.as_bytes()).unwrap().0.into_ref(); if header.magic != GPT_MAGIC { return Ok(false); } let entries_size = SafeNum::from(header.entries_count) * GPT_ENTRY_SIZE; let entries_offset = SafeNum::from(header.entries) * blk_dev.block_size(); if self.info.max_entries < header.entries_count.into() || u64::try_from(entries_size + entries_offset)? > ((SafeNum::from(blk_dev.num_blocks()) - 1) * blk_dev.block_size()).try_into()? { return Ok(false); } let crc32_offset = SafeNum::from(GPT_CRC32_OFFSET).try_into()?; let mut hasher = Hasher::new(); hasher.update(&header.as_bytes()[..crc32_offset]); hasher.update(&[0u8; size_of::()]); hasher.update(&header.as_bytes()[crc32_offset + size_of::()..]); if hasher.finalize() != header.crc32 { return Ok(false); } // Load the entries let out = &mut entries[..entries_size.try_into()?]; read(blk_dev, entries_offset.try_into()?, out, scratch)?; // Validate entries crc32. Ok(header.entries_crc == crc32(out)) } /// Load and sync GPT from a block device. fn load_and_sync( &mut self, blk_dev: &mut (impl BlockIo + ?Sized), scratch: &mut [u8], ) -> Result<()> { self.info.num_valid_entries = None; let block_size = blk_dev.block_size(); let total_blocks: SafeNum = blk_dev.num_blocks().into(); let primary_header_blk = 1; let primary_header_pos = block_size; let secondary_header_blk = total_blocks - 1; let secondary_header_pos = secondary_header_blk * block_size; // Entries position for restoring. let primary_entries_blk = 2; let primary_entries_pos = SafeNum::from(primary_entries_blk) * block_size; let primary_valid = self.validate_gpt(blk_dev, scratch, HeaderType::Primary)?; let secondary_valid = self.validate_gpt(blk_dev, scratch, HeaderType::Secondary)?; let primary_header = GptHeader::from_bytes(self.primary_header); let secondary_header = GptHeader::from_bytes(self.secondary_header); if !primary_valid { if !secondary_valid { return Err(StorageError::NoValidGpt); } // Restore to primary primary_header.as_bytes_mut().clone_from_slice(secondary_header.as_bytes()); self.primary_entries.clone_from_slice(&self.secondary_entries); primary_header.current = primary_header_blk; primary_header.backup = secondary_header_blk.try_into()?; primary_header.entries = primary_entries_blk; primary_header.update_crc(); write_bytes_mut(blk_dev, primary_header_pos, primary_header.as_bytes_mut(), scratch)?; write_bytes_mut( blk_dev, primary_entries_pos.try_into()?, self.primary_entries, scratch, )? } else if !secondary_valid { // Restore to secondary let secondary_entries_pos = secondary_header_pos - (SafeNum::from(self.info.max_entries) * core::mem::size_of::()); let secondary_entries_blk = secondary_entries_pos / block_size; secondary_header.as_bytes_mut().clone_from_slice(primary_header.as_bytes()); self.secondary_entries.clone_from_slice(&self.primary_entries); secondary_header.current = secondary_header_blk.try_into()?; secondary_header.backup = primary_header_blk; secondary_header.entries = secondary_entries_blk.try_into()?; secondary_header.update_crc(); write_bytes_mut( blk_dev, secondary_header_pos.try_into()?, secondary_header.as_bytes_mut(), scratch, )?; write_bytes_mut( blk_dev, secondary_entries_pos.try_into()?, self.secondary_entries, scratch, )?; } // Calculate actual number of GPT entries by finding the first invalid entry. let entries = Ref::<_, [GptEntry]>::new_slice(&self.primary_entries[..]).unwrap().into_slice(); self.info.num_valid_entries = NonZeroU64::new(match entries.iter().position(|e| e.is_null()) { Some(idx) => idx as u64, _ => self.info.max_entries, }); self.info.block_size = block_size; Ok(()) } } /// Wrapper of gpt.load_and_sync(). Library internal helper for AsBlockDevice::sync_gpt(). pub(crate) fn gpt_sync( blk_dev: &mut (impl BlockIo + ?Sized), gpt: &mut Gpt, scratch: &mut [u8], ) -> Result<()> { gpt.load_and_sync(blk_dev, scratch) } /// Checks if a read/write range into a GPT partition overflows and returns the range's absolute /// offset in the block device. pub(crate) fn check_gpt_rw_params( gpt_cache_buffer: &mut [u8], part_name: &str, offset: u64, size: usize, ) -> Result { let gpt = Gpt::from_existing(gpt_cache_buffer)?; let entry = gpt.find_partition(part_name)?; let end: u64 = (SafeNum::from(offset) + size).try_into()?; let total_size = SafeNum::from(entry.blocks()?) * gpt.info.block_size; match end <= total_size.try_into()? { true => Ok((SafeNum::from(entry.first) * gpt.info.block_size + offset).try_into()?), false => Err(StorageError::OutOfRange), } } fn crc32(data: &[u8]) -> u32 { let mut hasher = Hasher::new(); hasher.update(data); hasher.finalize() } #[cfg(test)] pub(crate) mod test { use super::*; use gbl_storage_testlib::{ alignment_scratch_size, AsBlockDevice, TestBlockDevice, TestBlockDeviceBuilder, }; /// Helper function to extract the gpt header from a test block device. /// This function lives here and not as a method of TestBlockDevice so that /// the Gpt type doesn't have to be exported. fn gpt(dev: &mut TestBlockDevice) -> Gpt { let (_, gpt) = dev.scratch.split_at_mut(alignment_scratch_size(&mut dev.io).unwrap()); Gpt::from_existing(gpt).unwrap() } #[test] fn test_new_from_buffer() { let mut dev: TestBlockDevice = include_bytes!("../test/gpt_test_1.bin").as_slice().into(); dev.sync_gpt().unwrap(); assert_eq!(dev.partition_iter().count(), 2); dev.find_partition("boot_a").unwrap(); dev.find_partition("boot_b").unwrap(); assert!(dev.find_partition("boot_c").is_err()); } #[test] fn test_gpt_buffer_too_small() { let mut dev: TestBlockDevice = include_bytes!("../test/gpt_test_1.bin").as_slice().into(); dev.scratch = vec![0u8; dev.scratch.len() - 1]; assert!(dev.sync_gpt().is_err()); } #[test] fn test_gpt_too_many_entries() { let mut dev = TestBlockDeviceBuilder::new() .set_data(include_bytes!("../test/gpt_test_1.bin")) .set_max_gpt_entries(129) .build(); assert!(dev.sync_gpt().is_err()); } #[test] fn test_load_gpt_primary() { let disk = include_bytes!("../test/gpt_test_1.bin"); let mut dev: TestBlockDevice = disk.as_slice().into(); // Corrupt secondary. dev.io.storage[disk.len() - 512..].fill(0); dev.sync_gpt().unwrap(); assert_eq!(dev.partition_iter().count(), 2); dev.find_partition("boot_a").unwrap(); dev.find_partition("boot_b").unwrap(); assert!(dev.find_partition("boot_c").is_err()); // Check that secondary is restored assert_eq!(dev.io.storage, disk); } #[test] fn test_load_gpt_secondary() { let disk = include_bytes!("../test/gpt_test_1.bin"); let mut dev: TestBlockDevice = disk.as_slice().into(); // Corrupt primary. dev.io.storage[512..1024].fill(0); dev.sync_gpt().unwrap(); assert_eq!(dev.partition_iter().count(), 2); dev.find_partition("boot_a").unwrap(); dev.find_partition("boot_b").unwrap(); // Check that primary is restored assert_eq!(dev.io.storage, disk); } #[test] fn test_good_gpt_no_repair_write() { let mut dev: TestBlockDevice = include_bytes!("../test/gpt_test_1.bin").as_slice().into(); dev.sync_gpt().unwrap(); assert_eq!(dev.io.num_writes, 0); } #[test] fn test_load_gpt_incorrect_magic() { let disk = include_bytes!("../test/gpt_test_1.bin"); let mut dev = TestBlockDeviceBuilder::new().set_data(disk).build(); dev.sync_gpt().unwrap(); let gpt = gpt(&mut dev); let primary_header = &mut gpt.primary_header[..GPT_HEADER_SIZE.try_into().unwrap()]; let gpt_header = GptHeader::from_bytes(primary_header); gpt_header.magic = 0x123456; gpt_header.update_crc(); let primary_header = Vec::from(primary_header); dev.io.storage[512..512 + primary_header.len()].clone_from_slice(&primary_header); dev.sync_gpt().unwrap(); // Check that incorrect magic header is restored assert_eq!(dev.io.storage, disk); } #[test] fn test_load_gpt_exceeds_max_entries() { let mut dev = TestBlockDeviceBuilder::new() .set_data(include_bytes!("../test/gpt_test_1.bin")) .set_max_gpt_entries(127) .build(); assert!(dev.sync_gpt().is_err()); } #[test] fn test_load_gpt_non_max_entries() { // Create a header with non-max entries_count let disk = include_bytes!("../test/gpt_test_1.bin"); let mut dev = TestBlockDeviceBuilder::new().set_data(disk).build(); let block_size: usize = dev.io.block_size.try_into().unwrap(); dev.sync_gpt().unwrap(); let gpt = gpt(&mut dev); let primary_header = &mut gpt.primary_header[..GPT_HEADER_SIZE.try_into().unwrap()]; let gpt_header = GptHeader::from_bytes(primary_header); gpt_header.entries_count = 2; // Update entries crc32 gpt_header.entries_crc = crc32(&gpt.primary_entries[..(2 * GPT_ENTRY_SIZE).try_into().unwrap()]); gpt_header.update_crc(); // Update to primary. let primary_header = Vec::from(primary_header); dev.io.storage[block_size..block_size + primary_header.len()] .clone_from_slice(&primary_header); // Corrupt secondary. Sync ok dev.io.storage[disk.len() - block_size..].fill(0); dev.sync_gpt().unwrap(); // Corrup primary. Sync ok dev.io.storage[block_size..(block_size * 2)].fill(0); dev.sync_gpt().unwrap(); } #[test] fn test_uninitialized_gpt() { // Load a good GPT first. let mut dev = TestBlockDeviceBuilder::new() .set_data(include_bytes!("../test/gpt_test_1.bin")) .build(); dev.sync_gpt().unwrap(); dev.io.storage[..64 * 1024].fill(0); // Load a bad GPT. Validate that the valid state is reset. assert!(dev.sync_gpt().is_err()); assert!(dev.find_partition("").is_err()); } #[test] fn test_gpt_read() { let mut dev = TestBlockDeviceBuilder::new() .set_data(include_bytes!("../test/gpt_test_1.bin")) .build(); dev.sync_gpt().unwrap(); let expect_boot_a = include_bytes!("../test/boot_a.bin"); let expect_boot_b = include_bytes!("../test/boot_b.bin"); let mut actual_boot_a = vec![0u8; expect_boot_a.len()]; let mut actual_boot_b = vec![0u8; expect_boot_b.len()]; dev.read_gpt_partition("boot_a", 0, &mut actual_boot_a).unwrap(); assert_eq!(expect_boot_a.to_vec(), actual_boot_a); // partial read actual_boot_a = actual_boot_a[1..].to_vec(); dev.read_gpt_partition("boot_a", 1, &mut actual_boot_a).unwrap(); assert_eq!(expect_boot_a[1..].to_vec(), actual_boot_a); dev.read_gpt_partition("boot_b", 0, &mut actual_boot_b).unwrap(); assert_eq!(expect_boot_b.to_vec(), actual_boot_b); // partial read actual_boot_b = actual_boot_b[1..].to_vec(); dev.read_gpt_partition("boot_b", 1, &mut actual_boot_b).unwrap(); assert_eq!(expect_boot_b[1..].to_vec(), actual_boot_b); } #[test] fn test_gpt_write() { let mut dev = TestBlockDeviceBuilder::new() .set_data(include_bytes!("../test/gpt_test_1.bin")) .build(); dev.sync_gpt().unwrap(); let mut expect_boot_a = include_bytes!("../test/boot_a.bin").to_vec(); expect_boot_a.reverse(); let mut expect_boot_b = include_bytes!("../test/boot_b.bin").to_vec(); expect_boot_b.reverse(); let mut actual_boot_a = vec![0u8; expect_boot_a.len()]; let mut actual_boot_b = vec![0u8; expect_boot_b.len()]; // "boot_a" partition // Mutable version dev.write_gpt_partition("boot_a", 0, expect_boot_a.as_mut_slice()).unwrap(); dev.read_gpt_partition("boot_a", 0, &mut actual_boot_a).unwrap(); assert_eq!(expect_boot_a.to_vec(), actual_boot_a); // Mutable version, partial write. dev.write_gpt_partition("boot_a", 1, expect_boot_a[1..].as_mut()).unwrap(); dev.read_gpt_partition("boot_a", 1, &mut actual_boot_a[1..]).unwrap(); assert_eq!(expect_boot_a[1..], actual_boot_a[1..]); // "boot_b" partition // Mutable version dev.write_gpt_partition("boot_b", 0, expect_boot_b.as_mut_slice()).unwrap(); dev.read_gpt_partition("boot_b", 0, &mut actual_boot_b).unwrap(); assert_eq!(expect_boot_b.to_vec(), actual_boot_b); // Mutable version, partial write. dev.write_gpt_partition("boot_b", 1, expect_boot_b[1..].as_mut()).unwrap(); dev.read_gpt_partition("boot_b", 1, &mut actual_boot_b[1..]).unwrap(); assert_eq!(expect_boot_b[1..], actual_boot_b[1..]); } #[test] fn test_gpt_rw_overflow() { let mut dev = TestBlockDeviceBuilder::new() .set_data(include_bytes!("../../libstorage/test/gpt_test_1.bin")) .build(); dev.sync_gpt().unwrap(); let mut boot_a = [0u8; include_bytes!("../test/boot_a.bin").len()]; let mut boot_b = [0u8; include_bytes!("../test/boot_b.bin").len()]; assert!(dev.read_gpt_partition("boot_a", 1, &mut boot_a).is_err()); assert!(dev.write_gpt_partition("boot_a", 1, boot_a.as_mut_slice()).is_err()); assert!(dev.read_gpt_partition("boot_b", 1, &mut boot_b).is_err()); assert!(dev.write_gpt_partition("boot_b", 1, boot_b.as_mut_slice()).is_err()); } }