From 35b4eef6e1df6f710f86e5014062a79909d584ab Mon Sep 17 00:00:00 2001 From: Dishendra Deshmukh Date: Thu, 9 Jul 2026 13:49:57 +0000 Subject: [PATCH] hii: support 64-bit HiiDB addresses Some platforms export the HiiDB buffer above 4GB, so the physical address in the efivarfs payload does not always fit in 32 bits. Parse both the existing 32-bit payload and a 64-bit address payload so the tool can consume either firmware format. Read the exported buffer with page-sized /dev/mem mmap mappings instead of seek/read. This works for high reserved DRAM ranges where read from /dev/mem may fail. Copy the mapped data byte-by-byte to avoid faults from wider unaligned loads on memory with strict access requirements. Add parser coverage for both 32-bit and 64-bit HiiDB payloads. Signed-off-by: Dishendra Deshmukh --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib/hii/extract.rs | 144 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 134 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a6d247..c17a678 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -939,6 +939,7 @@ dependencies = [ "env_logger", "fbthrift", "httparse", + "libc", "libloading", "log", "nix", diff --git a/Cargo.toml b/Cargo.toml index f7ac714..b11a2de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ name = "uefisettings" path = "src/lib/lib.rs" [dependencies] +libc = "0.2" uefisettings_backend_thrift = { path = "thrift/rust/uefisettings_backend_thrift", version = "0.1.0" } uefisettings_spellings_db_thrift = { path = "thrift/rust/uefisettings_spellings_db_thrift", version = "0.1.0" } anyhow = "1.0" diff --git a/src/lib/hii/extract.rs b/src/lib/hii/extract.rs index e963cbd..6783959 100644 --- a/src/lib/hii/extract.rs +++ b/src/lib/hii/extract.rs @@ -12,9 +12,10 @@ use std::fs::File; use std::io::Read; -use std::io::Seek; -use std::io::SeekFrom; +use std::os::fd::AsRawFd; +use std::ptr; +use anyhow::bail; use anyhow::Context; use anyhow::Result; use binrw::io::Cursor; @@ -24,17 +25,60 @@ use binrw::BinReaderExt; pub const OCP_HIIDB_PATH: &str = "/sys/firmware/efi/efivars/HiiDB-1b838190-4625-4ead-abc9-cd5e6af18fe0"; -#[derive(BinRead, Debug, PartialEq)] -#[br(little)] +#[derive(Debug, PartialEq)] struct HiiDBEFIVar { // hiitool calls this varlen but I think these are flags/attributes // first 4 bytes of the (efivarfs) output represent the UEFI variable attributes - from kernel.org flags: u32, + length: u32, + address: u64, +} + +#[derive(BinRead, Debug, PartialEq)] +#[br(little)] +struct HiiDBEFIVar32 { + flags: u32, length: u32, address: u32, } +#[derive(BinRead, Debug, PartialEq)] +#[br(little)] +struct HiiDBEFIVar64 { + flags: u32, + length: u32, + address: u64, +} + +impl HiiDBEFIVar { + fn parse(contents: &[u8]) -> Result { + match contents.len() { + 12 => { + let mut cursor = Cursor::new(contents); + let var: HiiDBEFIVar32 = cursor.read_ne()?; + + Ok(Self { + flags: var.flags, + length: var.length, + address: var.address.into(), + }) + } + 16 => { + let mut cursor = Cursor::new(contents); + let var: HiiDBEFIVar64 = cursor.read_ne()?; + + Ok(Self { + flags: var.flags, + length: var.length, + address: var.address, + }) + } + size => bail!("Unexpected HiiDB efivar size: {size} bytes"), + } + } +} + pub fn extract_db() -> Result> { // I haven't seen any documentation on extracting HiiDB anywhere on the internet // So this is directly based on what hiitool does. @@ -48,18 +92,94 @@ pub fn extract_db() -> Result> { .read_to_end(&mut efivar_contents) .context(format!("Failed to read efivar file, {}", OCP_HIIDB_PATH))?; - let mut efivar_cursor = Cursor::new(&efivar_contents); - let db_info: HiiDBEFIVar = efivar_cursor.read_ne()?; + let db_info = HiiDBEFIVar::parse(&efivar_contents)?; // Now that we have offset and size from the HiiDB efivar, use it to read DB from memory. + read_physical_memory(db_info.address, db_info.length.try_into()?) +} + +fn read_physical_memory(address: u64, length: usize) -> Result> { + let mem_file = File::open("/dev/mem").context("Failed to open /dev/mem")?; + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; + if page_size <= 0 { + bail!("Failed to query system page size"); + } - let mut mem_file = File::open("/dev/mem").context("Failed to open /dev/mem")?; - mem_file.seek(SeekFrom::Start(db_info.address as u64))?; + let page_size = page_size as usize; + let page_mask = page_size as u64 - 1; + let mut buf = Vec::with_capacity(length); + let mut copied = 0usize; - let mut buf = vec![0u8; db_info.length.try_into()?]; - mem_file - .read_exact(&mut buf) - .context("Failed to read bytes of specified length from /dev/mem")?; + while copied < length { + let phys = address + copied as u64; + let map_base = phys & !page_mask; + let map_offset = (phys - map_base) as usize; + let chunk_len = std::cmp::min(page_size - map_offset, length - copied); + + let mapped = unsafe { + libc::mmap( + ptr::null_mut(), + page_size, + libc::PROT_READ, + libc::MAP_SHARED, + mem_file.as_raw_fd(), + map_base as libc::off_t, + ) + }; + if mapped == libc::MAP_FAILED { + return Err(std::io::Error::last_os_error()).context(format!( + "Failed to mmap HII DB page from /dev/mem at 0x{map_base:x}" + )); + } + + // Some platforms expose the HII DB through memory that faults on + // wider unaligned loads. Copy byte-by-byte so every access is a + // naturally aligned u8 load from the mapped physical page. + unsafe { + let src = (mapped as *const u8).add(map_offset); + for i in 0..chunk_len { + buf.push(std::ptr::read_volatile(src.add(i))); + } + libc::munmap(mapped, page_size); + } + + copied += chunk_len; + } Ok(buf) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_32_bit_hiidb_address() { + let contents = [ + 0x06, 0x00, 0x00, 0x00, // flags + 0x34, 0x12, 0x00, 0x00, // length + 0x78, 0x56, 0x34, 0x12, // address + ]; + + let var = HiiDBEFIVar::parse(&contents).unwrap(); + + assert_eq!(var.flags, 0x6); + assert_eq!(var.length, 0x1234); + assert_eq!(var.address, 0x12345678); + } + + #[test] + fn parses_64_bit_hiidb_address() { + let contents = [ + 0x06, 0x00, 0x00, 0x00, // flags + 0x34, 0x12, 0x00, 0x00, // length + 0x00, 0xf0, 0xcc, 0xa4, 0x00, 0x02, 0x00, 0x00, // address + ]; + + let var = HiiDBEFIVar::parse(&contents).unwrap(); + + assert_eq!(var.flags, 0x6); + assert_eq!(var.length, 0x1234); + assert_eq!(var.address, 0x200a4ccf000); + } +}