//! Bounded UEFI file-read gate. This stage does not execute the Image. use core::ffi::c_void; use core::ptr::{addr_of_mut, null_mut}; use r_efi::{ efi, protocols::{file, loaded_image, simple_file_system}, }; use turkos_bootinfo::{KernelFileLengthError, validate_kernel_file_length}; use crate::{serial, serial_hex}; pub const CAPACITY: usize = 8 * 1024 * 1024; static mut KERNEL_FILE_BUFFER: [u8; CAPACITY] = [0; CAPACITY]; #[cfg(feature = "kernel-hash-test")] pub fn loaded_bytes(len: usize) -> &'static [u8] { // SAFETY: `load()` initialized exactly this prefix; the single-CPU EFI // acceptance path does not write it again while the digest is computed. unsafe { core::slice::from_raw_parts(core::ptr::addr_of!(KERNEL_FILE_BUFFER).cast(), len) } } fn expected_bytes() -> Result { let mut value = 0u64; for digit in env!("TURKOS_RELEASE_IMAGE_BYTES").bytes() { if !digit.is_ascii_digit() { return Err(efi::Status::INVALID_PARAMETER); } value = value .checked_mul(10) .and_then(|n| n.checked_add(u64::from(digit - b'0'))) .ok_or(efi::Status::BAD_BUFFER_SIZE)?; } if value == 0 || value > CAPACITY as u64 { return Err(efi::Status::BAD_BUFFER_SIZE); } Ok(value) } fn length_status(error: KernelFileLengthError) -> efi::Status { serial("TURKOS/UEFI-KERNEL-FILE-REJECT: "); match error { KernelFileLengthError::Empty => serial("EMPTY\n"), KernelFileLengthError::TooLarge => serial("TOO-LARGE\n"), KernelFileLengthError::DeclaredMismatch => serial("SIZE-MISMATCH\n"), KernelFileLengthError::ShortRead => serial("SHORT-READ\n"), KernelFileLengthError::OverRead => serial("OVER-READ\n"), } efi::Status::BAD_BUFFER_SIZE } pub fn load( image_handle: efi::Handle, services: *mut efi::BootServices, ) -> Result { let expected = expected_bytes()?; let mut root: *mut file::Protocol = null_mut(); let mut image_file: *mut file::Protocol = null_mut(); let result = (|| -> Result { let mut loaded_guid = loaded_image::PROTOCOL_GUID; let mut loaded: *mut c_void = null_mut(); // SAFETY: UEFI provides image_handle and BootServices for this EFI app. let status = unsafe { ((*services).handle_protocol)(image_handle, &mut loaded_guid, &mut loaded) }; if status != efi::Status::SUCCESS { return Err(status); } if loaded.is_null() { return Err(efi::Status::COMPROMISED_DATA); } // SAFETY: HandleProtocol with the loaded-image GUID returned this ABI. let device = unsafe { (*loaded.cast::()).device_handle }; let mut fs_guid = simple_file_system::PROTOCOL_GUID; let mut fs_raw: *mut c_void = null_mut(); // SAFETY: the loaded-image device handle owns the file system protocol. let status = unsafe { ((*services).handle_protocol)(device, &mut fs_guid, &mut fs_raw) }; if status != efi::Status::SUCCESS { return Err(status); } if fs_raw.is_null() { return Err(efi::Status::COMPROMISED_DATA); } let fs = fs_raw.cast::(); // SAFETY: HandleProtocol returned a valid SimpleFileSystem protocol. let status = unsafe { ((*fs).open_volume)(fs, &mut root) }; if status != efi::Status::SUCCESS { return Err(status); } if root.is_null() { return Err(efi::Status::COMPROMISED_DATA); } let mut path = [ b'T' as u16, b'U' as u16, b'R' as u16, b'K' as u16, b'O' as u16, b'S' as u16, b'.' as u16, b'I' as u16, b'M' as u16, b'G' as u16, 0, ]; // SAFETY: root and nul-terminated UTF-16 path are valid until call returns. let status = unsafe { ((*root).open)(root, &mut image_file, path.as_mut_ptr(), file::MODE_READ, 0) }; if status != efi::Status::SUCCESS { return Err(status); } if image_file.is_null() { return Err(efi::Status::COMPROMISED_DATA); } let mut info_words = [0u64; 128]; let mut info_size = core::mem::size_of_val(&info_words); let mut info_guid = file::INFO_ID; // SAFETY: the aligned 1024-byte buffer can receive EFI_FILE_INFO. let status = unsafe { ((*image_file).get_info)( image_file, &mut info_guid, &mut info_size, info_words.as_mut_ptr().cast(), ) }; if status != efi::Status::SUCCESS { return Err(status); } if info_size < core::mem::size_of::>() { return Err(efi::Status::COMPROMISED_DATA); } // SAFETY: the returned size covers the fixed prefix of EFI_FILE_INFO. let info = unsafe { &*(info_words.as_ptr().cast::>()) }; if info.attribute & file::DIRECTORY != 0 { return Err(efi::Status::INVALID_PARAMETER); } validate_kernel_file_length(expected, info.file_size, None, CAPACITY as u64) .map_err(length_status)?; let mut read_bytes = expected as usize; // SAFETY: expected is <= CAPACITY and the static destination is writable. let status = unsafe { ((*image_file).read)( image_file, &mut read_bytes, addr_of_mut!(KERNEL_FILE_BUFFER).cast(), ) }; if status != efi::Status::SUCCESS { return Err(status); } validate_kernel_file_length( expected, info.file_size, Some(read_bytes as u64), CAPACITY as u64, ) .map_err(length_status)?; serial("TURKOS/UEFI-KERNEL-FILE: expected=0x"); serial_hex(expected); serial(" declared=0x"); serial_hex(info.file_size); serial(" read=0x"); serial_hex(read_bytes as u64); serial(" PASS\n"); Ok(read_bytes) })(); // SAFETY: Close is called only for handles returned by Open/OpenVolume. unsafe { if !image_file.is_null() { ((*image_file).close)(image_file); } if !root.is_null() { ((*root).close)(root); } } result }