Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
144 changes: 132 additions & 12 deletions src/lib/hii/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Self> {
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<Vec<u8>> {
// I haven't seen any documentation on extracting HiiDB anywhere on the internet
// So this is directly based on what hiitool does.
Expand All @@ -48,18 +92,94 @@ pub fn extract_db() -> Result<Vec<u8>> {
.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<Vec<u8>> {
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);
}
}