Skip to content

Commit

Permalink
test-runner: add test for LoadFile and LoadFile2 protocols
Browse files Browse the repository at this point in the history
This actually tests multiple things:
- LoadFile and LoadFile2 wrappers work
- (un)install_protocol_interface works
- how our API is suited to create a custom implementation of an existing
  protocol definition
- The workflow used in Linux loaders to enable the Linux EFI stub to load
  the initrd via the LOAD_FILE2 protocol.

Further what I'm doing in this test, is already deployed in the
wild: https://github.com/nix-community/lanzaboote/blob/b7f68a50e6902f28c07a9f8d41df76f4c0a9315b/rust/uefi/linux-bootloader/src/linux_loader.rs#L142
  • Loading branch information
phip1611 committed Aug 15, 2024
1 parent 737347c commit bf2a54f
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 1 deletion.
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 uefi-test-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ publish = false
edition = "2021"

[dependencies]
uefi-raw = { path = "../uefi-raw" }
uefi = { path = "../uefi", features = ["alloc", "global_allocator", "panic_handler", "logger", "qemu"] }

log.workspace = true
Expand Down
131 changes: 131 additions & 0 deletions uefi-test-runner/src/proto/load.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::ffi::c_void;
use core::pin::Pin;
use core::ptr;
use core::ptr::addr_of;
use uefi::prelude::BootServices;
use uefi::proto::device_path::build::DevicePathBuilder;
use uefi::proto::media::load_file::{LoadFile, LoadFile2};
use uefi::proto::BootPolicy;
use uefi::{Guid, Handle};
use uefi_raw::protocol::device_path::DevicePathProtocol;
use uefi_raw::protocol::media::{LoadFile2Protocol, LoadFileProtocol};
use uefi_raw::Status;

unsafe extern "efiapi" fn raw_load_file(
this: *mut LoadFile2Protocol,
_file_path: *const DevicePathProtocol,
_boot_policy: bool,
buffer_size: *mut usize,
buffer: *mut c_void,
) -> Status {
log::debug!("Called static extern \"efiapi\" `raw_load_file` glue function");
let this = this.cast::<CustomLoadFile2Protocol>().as_ref().unwrap();
this.load_file(buffer_size, buffer.cast())
}

#[repr(C)]
struct CustomLoadFile2Protocol {
inner: LoadFile2Protocol,
file_data: Vec<u8>,
}

impl CustomLoadFile2Protocol {
fn new(file_data: Vec<u8>) -> Pin<Box<Self>> {
let inner = Self {
inner: LoadFile2Protocol {
load_file: raw_load_file,
},
file_data,
};
Box::pin(inner)
}

fn load_file(&self, buf_len: *mut usize, buf: *mut c_void) -> Status {
if buf.is_null() || unsafe { *buf_len } < self.file_data.len() {
log::debug!("Returning buffer size");
unsafe { *buf_len = self.file_data.len() };
Status::BUFFER_TOO_SMALL
} else {
log::debug!("Writing file content to buffer");
unsafe {
ptr::copy_nonoverlapping(self.file_data.as_ptr(), buf.cast(), self.file_data.len());
}
Status::SUCCESS
}
}
}

unsafe fn install_protocol(
bt: &BootServices,
handle: Handle,
guid: Guid,
protocol: &mut CustomLoadFile2Protocol,
) {
bt.install_protocol_interface(Some(handle), &guid, addr_of!(*protocol).cast())
.unwrap();
}

unsafe fn uninstall_protocol(
bt: &BootServices,
handle: Handle,
guid: Guid,
protocol: &mut CustomLoadFile2Protocol,
) {
bt.uninstall_protocol_interface(handle, &guid, addr_of!(*protocol).cast())
.unwrap();
}

/// This tests the LoadFile and LoadFile2 protocols. As this protocol is not
/// implemented in OVMF for the default handle, we implement it manually using
/// `install_protocol_interface`. Then, we load a file from our custom installed
/// protocol leveraging our protocol abstraction.
///
/// The way we are implementing the LoadFile(2) protocol is roughly what certain
/// Linux loaders do so that Linux can find its initrd [0, 1].
///
/// [0] https://github.com/u-boot/u-boot/commit/ec80b4735a593961fe701cc3a5d717d4739b0fd0#diff-1f940face4d1cf74f9d2324952759404d01ee0a81612b68afdcba6b49803bdbbR171
/// [1] https://github.com/torvalds/linux/blob/ee9a43b7cfe2d8a3520335fea7d8ce71b8cabd9d/drivers/firmware/efi/libstub/efi-stub-helper.c#L550
pub fn test(bt: &BootServices) {
let image = bt.image_handle();

let load_data_msg = "Example file content.";
let load_data = load_data_msg.to_string().into_bytes();
let mut proto_load_file = CustomLoadFile2Protocol::new(load_data);
// Get the ptr to the inner value, not the wrapping smart pointer type.
let proto_load_file_ptr = proto_load_file.as_mut().get_mut();

// Install our custom protocol implementation as LoadFile and LoadFile2
// protocol.
unsafe {
install_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr);
install_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr);
}

let mut dvp_vec = Vec::new();
let dvp = DevicePathBuilder::with_vec(&mut dvp_vec);
let dvp = dvp.finalize().unwrap();

let mut opened_load_file_protocol = bt.open_protocol_exclusive::<LoadFile>(image).unwrap();
let loadfile_file = opened_load_file_protocol
.load_file(dvp, BootPolicy::BootSelection)
.unwrap();
let loadfile_file_string = String::from_utf8(loadfile_file.to_vec()).unwrap();

let mut opened_load_file2_protocol = bt.open_protocol_exclusive::<LoadFile2>(image).unwrap();
let loadfile2_file = opened_load_file2_protocol.load_file(dvp).unwrap();
let loadfile2_file_string = String::from_utf8(loadfile2_file.to_vec()).unwrap();

assert_eq!(load_data_msg, &loadfile_file_string);
assert_eq!(load_data_msg, &loadfile2_file_string);

// Cleanup: Uninstall protocols again.
drop(opened_load_file_protocol);
drop(opened_load_file2_protocol);
unsafe {
uninstall_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr);
uninstall_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr);
}
}
2 changes: 2 additions & 0 deletions uefi-test-runner/src/proto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub fn test(st: &mut SystemTable<Boot>) {
debug::test(bt);
device_path::test(bt);
driver::test(bt);
load::test(bt);
loaded_image::test(bt);
media::test(bt);
network::test(bt);
Expand Down Expand Up @@ -78,6 +79,7 @@ mod console;
mod debug;
mod device_path;
mod driver;
mod load;
mod loaded_image;
mod media;
mod misc;
Expand Down
4 changes: 3 additions & 1 deletion uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ details of the new `system`/`boot`/`runtime` modules, and upcoming deprecations.
This comes with some changes. Read below. We recommend to directly use the
implementations instead of the traits.
- Added `LoadFile` and `LoadFile2` which abstracts over the `LOAD_FILE` and
`LOAD_FILE2` protocols.
`LOAD_FILE2` protocols. The UEFI test runner includes an integration test
that shows how Linux loaders can use this to implement the initrd loading
mechanism used in Linux.

## Changed
- **Breaking:** `uefi::helpers::init` no longer takes an argument.
Expand Down

0 comments on commit bf2a54f

Please sign in to comment.