2025-10-14 18:11:41 -07:00
|
|
|
use anyhow::{Context, Result, bail};
|
|
|
|
|
use std::ffi::c_void;
|
|
|
|
|
use uefi::proto::device_path::DevicePath;
|
|
|
|
|
use uefi::proto::device_path::build::DevicePathBuilder;
|
|
|
|
|
use uefi::proto::device_path::build::media::Vendor;
|
|
|
|
|
use uefi::proto::media::load_file::LoadFile2;
|
|
|
|
|
use uefi::{Guid, Handle};
|
|
|
|
|
use uefi_raw::protocol::device_path::DevicePathProtocol;
|
|
|
|
|
use uefi_raw::protocol::media::LoadFile2Protocol;
|
|
|
|
|
use uefi_raw::{Boolean, Status};
|
|
|
|
|
|
|
|
|
|
pub mod constants;
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
/// The media loader protocol.
|
2025-10-14 18:11:41 -07:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
#[repr(C)]
|
2025-10-18 23:49:00 -07:00
|
|
|
struct MediaLoaderProtocol {
|
2025-10-19 23:03:28 -07:00
|
|
|
/// This is the standard EFI LoadFile2 protocol.
|
2025-10-14 18:11:41 -07:00
|
|
|
pub load_file: unsafe extern "efiapi" fn(
|
|
|
|
|
this: *mut MediaLoaderProtocol,
|
|
|
|
|
file_path: *const DevicePathProtocol,
|
|
|
|
|
boot_policy: Boolean,
|
|
|
|
|
buffer_size: *mut usize,
|
|
|
|
|
buffer: *mut c_void,
|
|
|
|
|
) -> Status,
|
2025-10-19 23:03:28 -07:00
|
|
|
/// A pointer to a Box<[u8]> containing the data to load.
|
2025-10-14 18:11:41 -07:00
|
|
|
pub address: *mut c_void,
|
2025-10-19 23:03:28 -07:00
|
|
|
/// The length of the data to load.
|
2025-10-14 18:11:41 -07:00
|
|
|
pub length: usize,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 23:49:00 -07:00
|
|
|
/// Represents a media loader which has been registered in the UEFI stack.
|
|
|
|
|
/// You MUST call [MediaLoaderHandle::unregister] when ready to unregister.
|
|
|
|
|
/// [Drop] is not implemented for this type.
|
2025-10-14 18:11:41 -07:00
|
|
|
pub struct MediaLoaderHandle {
|
2025-10-19 23:03:28 -07:00
|
|
|
/// The vendor GUID of the media loader.
|
2025-10-18 23:49:00 -07:00
|
|
|
guid: Guid,
|
2025-10-19 23:03:28 -07:00
|
|
|
/// The handle of the media loader in the UEFI stack.
|
2025-10-18 23:49:00 -07:00
|
|
|
handle: Handle,
|
2025-10-19 23:03:28 -07:00
|
|
|
/// The protocol interface pointer.
|
2025-10-18 23:49:00 -07:00
|
|
|
protocol: *mut MediaLoaderProtocol,
|
2025-10-19 23:03:28 -07:00
|
|
|
/// The device path pointer.
|
2025-10-18 23:49:00 -07:00
|
|
|
path: *mut DevicePath,
|
2025-10-14 18:11:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MediaLoaderHandle {
|
2025-10-18 23:49:00 -07:00
|
|
|
/// The behavior of this function is derived from how Linux calls it.
|
|
|
|
|
///
|
2025-10-24 15:54:58 -07:00
|
|
|
/// Linux calls this function by first passing a NULL `buffer`.
|
|
|
|
|
/// We must set the size of the buffer it should allocate in `buffer_size`.
|
2025-10-18 23:49:00 -07:00
|
|
|
/// The next call will pass a buffer of the right size, and we should copy
|
|
|
|
|
/// data into that buffer, checking whether it is safe to copy based on
|
|
|
|
|
/// the buffer size.
|
2025-10-27 23:24:35 -04:00
|
|
|
///
|
|
|
|
|
/// SAFETY: `this.address` and `this.length` are set by leaking a Box<[u8]>, so we can
|
|
|
|
|
/// be sure their pointers are valid when this is called. The caller must call this function
|
|
|
|
|
/// while inside UEFI boot services to ensure pointers are valid. Copying to `buffer` is
|
|
|
|
|
/// assumed valid because the caller must ensure `buffer` is valid by function contract.
|
2025-10-14 18:11:41 -07:00
|
|
|
unsafe extern "efiapi" fn load_file(
|
|
|
|
|
this: *mut MediaLoaderProtocol,
|
|
|
|
|
file_path: *const DevicePathProtocol,
|
|
|
|
|
boot_policy: Boolean,
|
|
|
|
|
buffer_size: *mut usize,
|
|
|
|
|
buffer: *mut c_void,
|
|
|
|
|
) -> Status {
|
2025-10-19 23:03:28 -07:00
|
|
|
// Check if the pointers are non-null first.
|
2025-10-14 18:11:41 -07:00
|
|
|
if this.is_null() || buffer_size.is_null() || file_path.is_null() {
|
|
|
|
|
return Status::INVALID_PARAMETER;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Boot policy must not be true, and if it is, that is special behavior that is irrelevant
|
2025-10-18 23:49:00 -07:00
|
|
|
// for the media loader concept.
|
2025-10-14 18:11:41 -07:00
|
|
|
if boot_policy == Boolean::TRUE {
|
|
|
|
|
return Status::UNSUPPORTED;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 23:49:00 -07:00
|
|
|
// SAFETY: Validated as safe because this is checked to be non-null. It is the caller's
|
|
|
|
|
// responsibility to ensure that the right pointer is passed for [this].
|
2025-10-14 18:11:41 -07:00
|
|
|
unsafe {
|
2025-10-19 23:03:28 -07:00
|
|
|
// Check if the length and address are valid.
|
2025-10-14 18:11:41 -07:00
|
|
|
if (*this).length == 0 || (*this).address.is_null() {
|
|
|
|
|
return Status::NOT_FOUND;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Check if the buffer is large enough.
|
|
|
|
|
// If it is not, we need to set the buffer size to the length of the data.
|
|
|
|
|
// This is the way that Linux calls this function, to check the size to allocate
|
|
|
|
|
// for the buffer that holds the data.
|
2025-10-14 18:11:41 -07:00
|
|
|
if buffer.is_null() || *buffer_size < (*this).length {
|
|
|
|
|
*buffer_size = (*this).length;
|
|
|
|
|
return Status::BUFFER_TOO_SMALL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Copy the data into the buffer.
|
2025-10-14 18:11:41 -07:00
|
|
|
buffer.copy_from((*this).address, (*this).length);
|
2025-10-19 23:03:28 -07:00
|
|
|
// Set the buffer size to the length of the data.
|
2025-10-14 18:11:41 -07:00
|
|
|
*buffer_size = (*this).length;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// We've successfully loaded the data.
|
2025-10-14 18:11:41 -07:00
|
|
|
Status::SUCCESS
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
/// Creates a new device path for the media loader based on a vendor `guid`.
|
2025-10-24 19:27:43 -07:00
|
|
|
fn device_path(guid: Guid) -> Result<Box<DevicePath>> {
|
2025-10-19 23:03:28 -07:00
|
|
|
// The buffer for the device path.
|
2025-10-14 18:11:41 -07:00
|
|
|
let mut path = Vec::new();
|
2025-10-19 23:03:28 -07:00
|
|
|
// Build a device path for the media loader with a vendor-specific guid.
|
2025-10-14 18:11:41 -07:00
|
|
|
let path = DevicePathBuilder::with_vec(&mut path)
|
|
|
|
|
.push(&Vendor {
|
|
|
|
|
vendor_guid: guid,
|
|
|
|
|
vendor_defined_data: &[],
|
|
|
|
|
})
|
2025-10-24 19:27:43 -07:00
|
|
|
.context("unable to produce device path")?
|
2025-10-14 18:11:41 -07:00
|
|
|
.finalize()
|
2025-10-24 19:27:43 -07:00
|
|
|
.context("unable to produce device path")?;
|
2025-10-19 23:03:28 -07:00
|
|
|
// Convert the device path to a boxed device path.
|
|
|
|
|
// This is safer than dealing with a pooled device path.
|
2025-10-24 19:27:43 -07:00
|
|
|
Ok(path.to_boxed())
|
2025-10-14 18:11:41 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
/// Checks if the media loader is already registered with the UEFI stack.
|
2025-10-14 18:11:41 -07:00
|
|
|
fn already_registered(guid: Guid) -> Result<bool> {
|
2025-10-19 23:03:28 -07:00
|
|
|
// Acquire the device path for the media loader.
|
2025-10-24 19:27:43 -07:00
|
|
|
let path = Self::device_path(guid)?;
|
2025-10-14 18:11:41 -07:00
|
|
|
|
|
|
|
|
let mut existing_path = path.as_ref();
|
2025-10-19 23:03:28 -07:00
|
|
|
|
|
|
|
|
// Locate the LoadFile2 protocol for the media loader based on the device path.
|
2025-10-14 18:11:41 -07:00
|
|
|
let result = uefi::boot::locate_device_path::<LoadFile2>(&mut existing_path);
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// If the result is okay, the media loader is already registered.
|
2025-10-14 18:11:41 -07:00
|
|
|
if result.is_ok() {
|
|
|
|
|
return Ok(true);
|
|
|
|
|
} else if let Err(error) = result
|
|
|
|
|
&& error.status() != Status::NOT_FOUND
|
2025-10-19 23:03:28 -07:00
|
|
|
// If the error is not found, that means it's not registered.
|
2025-10-14 18:11:41 -07:00
|
|
|
{
|
|
|
|
|
bail!("unable to locate media loader device path: {}", error);
|
|
|
|
|
}
|
2025-10-19 23:03:28 -07:00
|
|
|
// The media loader is not registered.
|
2025-10-14 18:11:41 -07:00
|
|
|
Ok(false)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 15:54:58 -07:00
|
|
|
/// Registers the provided `data` with the UEFI stack as media loader.
|
2025-10-14 18:11:41 -07:00
|
|
|
/// This uses a special device path that other EFI programs will look at
|
|
|
|
|
/// to load the data from.
|
|
|
|
|
pub fn register(guid: Guid, data: Box<[u8]>) -> Result<MediaLoaderHandle> {
|
2025-10-19 23:03:28 -07:00
|
|
|
// Acquire the vendor device path for the media loader.
|
2025-10-24 19:27:43 -07:00
|
|
|
let path = Self::device_path(guid)?;
|
2025-10-14 18:11:41 -07:00
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Check if the media loader is already registered.
|
|
|
|
|
// If it is, we can't register it again safely.
|
2025-10-14 18:11:41 -07:00
|
|
|
if Self::already_registered(guid)? {
|
|
|
|
|
bail!("media loader already registered");
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Leak the device path to pass it to the UEFI stack.
|
|
|
|
|
let path = Box::leak(path);
|
|
|
|
|
|
|
|
|
|
// Install a protocol interface for the device path.
|
|
|
|
|
// This ensures it can be located by other EFI programs.
|
2025-10-14 18:11:41 -07:00
|
|
|
let mut handle = unsafe {
|
|
|
|
|
uefi::boot::install_protocol_interface(
|
|
|
|
|
None,
|
|
|
|
|
&DevicePathProtocol::GUID,
|
|
|
|
|
path.as_ffi_ptr() as *mut c_void,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
.context("unable to install media loader device path handle")?;
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Leak the data we need to pass to the UEFI stack.
|
2025-10-14 18:11:41 -07:00
|
|
|
let data = Box::leak(data);
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Allocate a new box for the protocol interface.
|
2025-10-14 18:11:41 -07:00
|
|
|
let protocol = Box::new(MediaLoaderProtocol {
|
|
|
|
|
load_file: Self::load_file,
|
|
|
|
|
address: data.as_ptr() as *mut _,
|
|
|
|
|
length: data.len(),
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Leak the protocol interface to pass it to the UEFI stack.
|
2025-10-14 18:11:41 -07:00
|
|
|
let protocol = Box::leak(protocol);
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Install a protocol interface for the load file protocol for the media loader protocol.
|
2025-10-14 18:11:41 -07:00
|
|
|
handle = unsafe {
|
|
|
|
|
uefi::boot::install_protocol_interface(
|
|
|
|
|
Some(handle),
|
|
|
|
|
&LoadFile2Protocol::GUID,
|
|
|
|
|
protocol as *mut _ as *mut c_void,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
.context("unable to install media loader load file handle")?;
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Check if the media loader is registered.
|
|
|
|
|
// If it is not, we can't continue safely because something went wrong.
|
2025-10-14 18:11:41 -07:00
|
|
|
if !Self::already_registered(guid)? {
|
|
|
|
|
bail!("media loader not registered when expected to be registered");
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Return a handle to the media loader.
|
2025-10-14 18:11:41 -07:00
|
|
|
Ok(Self {
|
|
|
|
|
guid,
|
|
|
|
|
handle,
|
|
|
|
|
protocol,
|
|
|
|
|
path,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Unregisters a media loader from the UEFI stack.
|
|
|
|
|
/// This will free the memory allocated by the passed data.
|
|
|
|
|
pub fn unregister(self) -> Result<()> {
|
2025-10-19 23:03:28 -07:00
|
|
|
// Check if the media loader is registered.
|
|
|
|
|
// If it is not, we don't need to do anything.
|
2025-10-14 18:11:41 -07:00
|
|
|
if !Self::already_registered(self.guid)? {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// SAFETY: We know that the media loader is registered, so we can safely uninstall it.
|
|
|
|
|
// We should have allocated the pointers involved, so we can safely free them.
|
2025-10-14 18:11:41 -07:00
|
|
|
unsafe {
|
2025-10-19 23:03:28 -07:00
|
|
|
// Uninstall the protocol interface for the device path protocol.
|
2025-10-14 18:11:41 -07:00
|
|
|
uefi::boot::uninstall_protocol_interface(
|
|
|
|
|
self.handle,
|
|
|
|
|
&DevicePathProtocol::GUID,
|
|
|
|
|
self.path as *mut c_void,
|
|
|
|
|
)
|
|
|
|
|
.context("unable to uninstall media loader device path handle")?;
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Uninstall the protocol interface for the load file protocol.
|
2025-10-14 18:11:41 -07:00
|
|
|
uefi::boot::uninstall_protocol_interface(
|
|
|
|
|
self.handle,
|
|
|
|
|
&LoadFile2Protocol::GUID,
|
|
|
|
|
self.protocol as *mut _ as *mut c_void,
|
|
|
|
|
)
|
|
|
|
|
.context("unable to uninstall media loader load file handle")?;
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Retrieve a box for the device path and protocols.
|
2025-10-18 23:49:00 -07:00
|
|
|
let path = Box::from_raw(self.path);
|
|
|
|
|
let protocol = Box::from_raw(self.protocol);
|
2025-10-19 23:03:28 -07:00
|
|
|
|
|
|
|
|
// Retrieve a box for the data we passed in.
|
2025-10-18 23:49:00 -07:00
|
|
|
let slice =
|
|
|
|
|
std::ptr::slice_from_raw_parts_mut(protocol.address as *mut u8, protocol.length);
|
|
|
|
|
let data = Box::from_raw(slice);
|
|
|
|
|
|
2025-10-19 23:03:28 -07:00
|
|
|
// Drop all the allocations explicitly, as we don't want to leak them.
|
2025-10-18 23:49:00 -07:00
|
|
|
drop(path);
|
|
|
|
|
drop(protocol);
|
|
|
|
|
drop(data);
|
2025-10-14 18:11:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|