extern crate alloc;
use core::ffi::c_void;
use alloc::boxed::Box;
use patina::{
component::{Storage, component},
error::EfiError,
log_debug_assert,
standard::efi::{self, protocols::decompress},
uefi::{
boot_services::BootServices,
decompress::{DecompressionAlgorithm, decompress_into_with_algo},
},
};
#[derive(Default)]
pub(crate) struct DecompressProtocolInstaller;
#[component]
impl DecompressProtocolInstaller {
fn entry_point(self, storage: &mut Storage) -> patina::error::Result<()> {
let protocol = Box::new(decompress::Protocol { get_info, decompress });
match storage.boot_services().install_protocol_interface(None, protocol) {
Ok(_) => Ok(()),
Err(err) => EfiError::status_to_result(err),
}
}
}
unsafe extern "efiapi" fn get_info(
_: *mut decompress::Protocol,
src: *mut c_void,
src_size: u32,
dst_size: *mut u32,
scratch_size: *mut u32,
) -> efi::Status {
if src.is_null() | dst_size.is_null() | scratch_size.is_null() {
return efi::Status::INVALID_PARAMETER;
}
if src_size < 8 {
return efi::Status::INVALID_PARAMETER;
}
let compressed_size = unsafe { src.cast::<u32>().read_unaligned() };
if (src_size < compressed_size + 8) || compressed_size.checked_add(8).is_none() {
return efi::Status::INVALID_PARAMETER;
}
unsafe { dst_size.write_volatile(src.cast::<u32>().add(1).read_unaligned()) };
unsafe { scratch_size.cast::<u32>().write_volatile(0) };
efi::Status::SUCCESS
}
unsafe extern "efiapi" fn decompress(
_: *mut decompress::Protocol,
source_buffer: *mut c_void,
source_size: u32,
destination_buffer: *mut c_void,
destination_size: u32,
_scratch_buffer: *mut c_void,
_scratch_size: u32,
) -> efi::Status {
if source_buffer.is_null() || destination_buffer.is_null() {
log_debug_assert!("DecompressProtocol::decompress called with null pointer");
return efi::Status::INVALID_PARAMETER;
}
let src = unsafe { core::slice::from_raw_parts(source_buffer as *const u8, source_size as usize) };
let dst = unsafe { core::slice::from_raw_parts_mut(destination_buffer as *mut u8, destination_size as usize) };
match decompress_into_with_algo(src, dst, DecompressionAlgorithm::UefiDecompress) {
Ok(()) => efi::Status::SUCCESS,
Err(_) => efi::Status::INVALID_PARAMETER,
}
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
use super::*;
use core::ptr;
fn compressed_header(compressed_size: u32, orig_size: u32) -> [u8; 16] {
let mut buf = [0u8; 16];
buf[0..4].copy_from_slice(&compressed_size.to_le_bytes());
buf[4..8].copy_from_slice(&orig_size.to_le_bytes());
buf
}
#[test]
fn test_decompress_get_info_rejects_invalid_parameters() {
let mut src = compressed_header(8, 100);
let mut dst_size = 0u32;
let mut scratch_size = 0u32;
let status = unsafe { get_info(ptr::null_mut(), ptr::null_mut(), 16, &mut dst_size, &mut scratch_size) };
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let status = unsafe {
get_info(ptr::null_mut(), src.as_mut_ptr() as *mut c_void, 16, ptr::null_mut(), &mut scratch_size)
};
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let status =
unsafe { get_info(ptr::null_mut(), src.as_mut_ptr() as *mut c_void, 16, &mut dst_size, ptr::null_mut()) };
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let mut small = [0u8; 4];
let status = unsafe {
get_info(ptr::null_mut(), small.as_mut_ptr() as *mut c_void, 4, &mut dst_size, &mut scratch_size)
};
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let mut undersized = compressed_header(100, 50);
let status = unsafe {
get_info(ptr::null_mut(), undersized.as_mut_ptr() as *mut c_void, 16, &mut dst_size, &mut scratch_size)
};
assert_eq!(status, efi::Status::INVALID_PARAMETER);
}
#[test]
fn test_decompress_get_info_returns_sizes_on_valid_input() {
let mut src = compressed_header(8, 100);
let mut dst_size = 0xFFFF_FFFFu32;
let mut scratch_size = 0xFFFF_FFFFu32;
let status =
unsafe { get_info(ptr::null_mut(), src.as_mut_ptr() as *mut c_void, 16, &mut dst_size, &mut scratch_size) };
assert_eq!(status, efi::Status::SUCCESS);
assert_eq!(dst_size, 100);
assert_eq!(scratch_size, 0);
}
#[test]
fn test_decompress_decompress_rejects_invalid_parameters() {
let mut src = compressed_header(8, 0);
let mut dst = [0u8; 8];
let status = unsafe {
decompress(ptr::null_mut(), ptr::null_mut(), 16, dst.as_mut_ptr() as *mut c_void, 8, ptr::null_mut(), 0)
};
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let status = unsafe {
decompress(ptr::null_mut(), src.as_mut_ptr() as *mut c_void, 16, ptr::null_mut(), 8, ptr::null_mut(), 0)
};
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let mut malformed = [0u8; 4];
let status = unsafe {
decompress(
ptr::null_mut(),
malformed.as_mut_ptr() as *mut c_void,
4,
dst.as_mut_ptr() as *mut c_void,
8,
ptr::null_mut(),
0,
)
};
assert_eq!(status, efi::Status::INVALID_PARAMETER);
}
#[test]
fn test_decompress_decompress_succeeds_with_zero_original_size() {
let mut src = compressed_header(8, 0);
let mut dst = [0u8; 8];
let status = unsafe {
decompress(
ptr::null_mut(),
src.as_mut_ptr() as *mut c_void,
16,
dst.as_mut_ptr() as *mut c_void,
0,
ptr::null_mut(),
0,
)
};
assert_eq!(status, efi::Status::SUCCESS);
}
}