From 0b74f6dea3428564f6f412c1775d47dfb259fe2b Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Tue, 14 Oct 2025 18:11:41 -0700 Subject: [PATCH] generify the linux media initrd mechanism to allow reuse for other things --- src/actions/chainload.rs | 9 +- src/utils.rs | 2 +- src/utils/linux_media_initrd.rs | 182 ---------------------------- src/utils/media_loader.rs | 176 +++++++++++++++++++++++++++ src/utils/media_loader/constants.rs | 3 + 5 files changed, 186 insertions(+), 186 deletions(-) delete mode 100644 src/utils/linux_media_initrd.rs create mode 100644 src/utils/media_loader.rs create mode 100644 src/utils/media_loader/constants.rs diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 2a23f2d..00a0f93 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -1,6 +1,7 @@ use crate::context::SproutContext; use crate::utils; -use crate::utils::linux_media_initrd::{register_linux_initrd, unregister_linux_initrd}; +use crate::utils::media_loader::MediaLoaderHandle; +use crate::utils::media_loader::constants::LINUX_EFI_INITRD_MEDIA_GUID; use anyhow::{Context, Result, bail}; use log::info; use serde::{Deserialize, Serialize}; @@ -78,7 +79,7 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat let content = utils::read_file_contents(context.root().loaded_image_path()?, &initrd_path) .context("unable to read linux initrd")?; initrd_handle = Some( - register_linux_initrd(content.into_boxed_slice()) + MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice()) .context("unable to register linux initrd")?, ); } @@ -87,7 +88,9 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat info!("loaded image: base={:#x} size={:#x}", base.addr(), size); let result = uefi::boot::start_image(image).context("unable to start image"); if let Some(initrd_handle) = initrd_handle { - unregister_linux_initrd(initrd_handle).context("unable to unregister linux initrd")?; + initrd_handle + .unregister() + .context("unable to unregister linux initrd")?; } result.context("unable to start image")?; drop(options_holder); diff --git a/src/utils.rs b/src/utils.rs index b9ebeb3..176ee67 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,7 +7,7 @@ use uefi::proto::media::fs::SimpleFileSystem; use uefi::{CString16, Handle}; pub mod framebuffer; -pub mod linux_media_initrd; +pub mod media_loader; /// Parses the input [path] as a [DevicePath]. /// Uses the [DevicePathFromText] protocol exclusively, and will fail if it cannot acquire the protocol. diff --git a/src/utils/linux_media_initrd.rs b/src/utils/linux_media_initrd.rs deleted file mode 100644 index fdef147..0000000 --- a/src/utils/linux_media_initrd.rs +++ /dev/null @@ -1,182 +0,0 @@ -use anyhow::{Context, Result, bail}; -use log::info; -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::proto::unsafe_protocol; -use uefi::{Guid, Handle, guid}; -use uefi_raw::protocol::device_path::DevicePathProtocol; -use uefi_raw::protocol::media::LoadFile2Protocol; -use uefi_raw::{Boolean, Status}; - -#[derive(Debug)] -#[repr(C)] -pub struct LinuxMediaInitrdProtocol { - pub load_file: unsafe extern "efiapi" fn( - this: *mut LinuxMediaInitrdProtocol, - file_path: *const DevicePathProtocol, - boot_policy: Boolean, - buffer_size: *mut usize, - buffer: *mut c_void, - ) -> Status, - pub address: *mut c_void, - pub length: usize, -} - -impl LinuxMediaInitrdProtocol { - pub const GUID: Guid = guid!("5568e427-68fc-4f3d-ac74-ca555231cc68"); - - pub fn device_path() -> Box { - let mut path = Vec::new(); - let path = DevicePathBuilder::with_vec(&mut path) - .push(&Vendor { - vendor_guid: LinuxMediaInitrdProtocol::GUID, - vendor_defined_data: &[], - }) - .unwrap() - .finalize() - .unwrap(); - path.to_boxed() - } -} - -#[repr(transparent)] -#[unsafe_protocol(LinuxMediaInitrdProtocol::GUID)] -pub struct LinuxMediaInitrd(LinuxMediaInitrdProtocol); - -pub struct LinuxMediaInitrdHandle { - pub handle: Handle, - pub protocol: *mut LinuxMediaInitrdProtocol, - pub path: *mut DevicePath, -} - -unsafe extern "efiapi" fn load_initrd_file( - this: *mut LinuxMediaInitrdProtocol, - file_path: *const DevicePathProtocol, - boot_policy: Boolean, - buffer_size: *mut usize, - buffer: *mut c_void, -) -> Status { - if this.is_null() || buffer_size.is_null() || file_path.is_null() { - return Status::INVALID_PARAMETER; - } - - if boot_policy == Boolean::TRUE { - return Status::UNSUPPORTED; - } - - unsafe { - if (*this).length == 0 || (*this).address.is_null() { - return Status::NOT_FOUND; - } - - if buffer.is_null() || *buffer_size < (*this).length { - *buffer_size = (*this).length; - return Status::BUFFER_TOO_SMALL; - } - - buffer.copy_from((*this).address, (*this).length); - *buffer_size = (*this).length; - } - - Status::SUCCESS -} - -fn already_registered() -> Result { - let path = LinuxMediaInitrdProtocol::device_path(); - - let mut existing_path = path.as_ref(); - let result = uefi::boot::locate_device_path::(&mut existing_path); - - if result.is_ok() { - return Ok(true); - } else if let Err(error) = result - && error.status() != Status::NOT_FOUND - { - bail!("unable to locate initrd device path: {}", error); - } - Ok(false) -} - -/// Registers the provided [data] with the UEFI stack as a Linux initrd. -/// This uses a special device path that Linux EFI stub will look at -/// to load the initrd from. -pub fn register_linux_initrd(data: Box<[u8]>) -> Result { - let path = LinuxMediaInitrdProtocol::device_path(); - let path = Box::leak(path); - - if already_registered()? { - bail!("linux initrd already registered"); - } - - let mut handle = unsafe { - uefi::boot::install_protocol_interface( - None, - &DevicePathProtocol::GUID, - path.as_ffi_ptr() as *mut c_void, - ) - } - .context("unable to install linux initrd device path handle")?; - - let data = Box::leak(data); - - let protocol = Box::new(LinuxMediaInitrdProtocol { - load_file: load_initrd_file, - address: data.as_ptr() as *mut _, - length: data.len(), - }); - - let protocol = Box::leak(protocol); - - handle = unsafe { - uefi::boot::install_protocol_interface( - Some(handle), - &LoadFile2Protocol::GUID, - protocol as *mut _ as *mut c_void, - ) - } - .context("unable to install linux initrd load file handle")?; - - if !already_registered()? { - bail!("linux initrd not registered when expected to be registered"); - } - - info!("linux initrd registered"); - - Ok(LinuxMediaInitrdHandle { - handle, - protocol, - path, - }) -} - -/// Unregisters a Linux initrd from the UEFI stack. -/// This will free the memory allocated by the initrd. -pub fn unregister_linux_initrd(handle: LinuxMediaInitrdHandle) -> Result<()> { - if !already_registered()? { - return Ok(()); - } - - unsafe { - uefi::boot::uninstall_protocol_interface( - handle.handle, - &DevicePathProtocol::GUID, - handle.path as *mut c_void, - ) - .context("unable to uninstall linux initrd device path handle")?; - - uefi::boot::uninstall_protocol_interface( - handle.handle, - &LoadFile2Protocol::GUID, - handle.protocol as *mut _ as *mut c_void, - ) - .context("unable to uninstall linux initrd load file handle")?; - - let _path = Box::from_raw(handle.path); - let _protocol = Box::from_raw(handle.protocol); - } - - Ok(()) -} diff --git a/src/utils/media_loader.rs b/src/utils/media_loader.rs new file mode 100644 index 0000000..ddfd30f --- /dev/null +++ b/src/utils/media_loader.rs @@ -0,0 +1,176 @@ +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; + +#[derive(Debug)] +#[repr(C)] +pub struct MediaLoaderProtocol { + 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, + pub address: *mut c_void, + pub length: usize, +} + +pub struct MediaLoaderHandle { + pub guid: Guid, + pub handle: Handle, + pub protocol: *mut MediaLoaderProtocol, + pub path: *mut DevicePath, +} + +impl MediaLoaderHandle { + 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 { + if this.is_null() || buffer_size.is_null() || file_path.is_null() { + return Status::INVALID_PARAMETER; + } + + if boot_policy == Boolean::TRUE { + return Status::UNSUPPORTED; + } + + unsafe { + if (*this).length == 0 || (*this).address.is_null() { + return Status::NOT_FOUND; + } + + if buffer.is_null() || *buffer_size < (*this).length { + *buffer_size = (*this).length; + return Status::BUFFER_TOO_SMALL; + } + + buffer.copy_from((*this).address, (*this).length); + *buffer_size = (*this).length; + } + + Status::SUCCESS + } + + pub fn device_path(guid: Guid) -> Box { + let mut path = Vec::new(); + let path = DevicePathBuilder::with_vec(&mut path) + .push(&Vendor { + vendor_guid: guid, + vendor_defined_data: &[], + }) + .unwrap() + .finalize() + .unwrap(); + path.to_boxed() + } + + fn already_registered(guid: Guid) -> Result { + let path = Self::device_path(guid); + + let mut existing_path = path.as_ref(); + let result = uefi::boot::locate_device_path::(&mut existing_path); + + if result.is_ok() { + return Ok(true); + } else if let Err(error) = result + && error.status() != Status::NOT_FOUND + { + bail!("unable to locate media loader device path: {}", error); + } + Ok(false) + } + + /// Registers the provided [data] with the UEFI stack as media loader. + /// 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 { + let path = Self::device_path(guid); + let path = Box::leak(path); + + if Self::already_registered(guid)? { + bail!("media loader already registered"); + } + + 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")?; + + let data = Box::leak(data); + + let protocol = Box::new(MediaLoaderProtocol { + load_file: Self::load_file, + address: data.as_ptr() as *mut _, + length: data.len(), + }); + + let protocol = Box::leak(protocol); + + 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")?; + + if !Self::already_registered(guid)? { + bail!("media loader not registered when expected to be registered"); + } + + 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<()> { + if !Self::already_registered(self.guid)? { + return Ok(()); + } + + unsafe { + uefi::boot::uninstall_protocol_interface( + self.handle, + &DevicePathProtocol::GUID, + self.path as *mut c_void, + ) + .context("unable to uninstall media loader device path handle")?; + + 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")?; + + let _path = Box::from_raw(self.path); + let _protocol = Box::from_raw(self.protocol); + } + + Ok(()) + } +} diff --git a/src/utils/media_loader/constants.rs b/src/utils/media_loader/constants.rs new file mode 100644 index 0000000..9787642 --- /dev/null +++ b/src/utils/media_loader/constants.rs @@ -0,0 +1,3 @@ +use uefi::{Guid, guid}; + +pub const LINUX_EFI_INITRD_MEDIA_GUID: Guid = guid!("5568e427-68fc-4f3d-ac74-ca555231cc68");