Skip to content

Commit 3b31f08

Browse files
committed
test-runner: add test for LoadFile and LoadFile2 protocols
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
1 parent 6cb90ce commit 3b31f08

File tree

5 files changed

+135
-1
lines changed

5 files changed

+135
-1
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

uefi-test-runner/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ publish = false
66
edition = "2021"
77

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

1112
log.workspace = true

uefi-test-runner/src/proto/load.rs

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use alloc::boxed::Box;
2+
use alloc::string::{String, ToString};
3+
use alloc::vec::Vec;
4+
use core::ffi::c_void;
5+
use core::pin::Pin;
6+
use core::ptr;
7+
use core::ptr::addr_of;
8+
use uefi::prelude::BootServices;
9+
use uefi::proto::device_path::build::DevicePathBuilder;
10+
use uefi::proto::media::load_file::{LoadFile, LoadFile2};
11+
use uefi::{Guid, Handle};
12+
use uefi_raw::protocol::device_path::DevicePathProtocol;
13+
use uefi_raw::protocol::media::{LoadFile2Protocol, LoadFileProtocol};
14+
use uefi_raw::Status;
15+
16+
unsafe extern "efiapi" fn raw_load_file(
17+
this: *mut LoadFile2Protocol,
18+
_file_path: *const DevicePathProtocol,
19+
_boot_policy: bool,
20+
buffer_size: *mut usize,
21+
buffer: *mut c_void,
22+
) -> Status {
23+
log::debug!("hello");
24+
let this = this.cast::<CustomLoadFile2Protocol>().as_ref().unwrap();
25+
this.load_file(buffer_size, buffer.cast())
26+
}
27+
28+
#[repr(C)]
29+
struct CustomLoadFile2Protocol {
30+
inner: LoadFile2Protocol,
31+
file_data: Vec<u8>,
32+
}
33+
34+
impl CustomLoadFile2Protocol {
35+
fn new(file_data: Vec<u8>) -> Pin<Box<Self>> {
36+
let inner = Self {
37+
inner: LoadFile2Protocol {
38+
load_file: raw_load_file,
39+
},
40+
file_data,
41+
};
42+
Box::pin(inner)
43+
}
44+
45+
fn load_file(&self, buf_len: *mut usize, buf: *mut c_void) -> Status {
46+
log::debug!("hello");
47+
if buf.is_null() || unsafe { *buf_len } < self.file_data.len() {
48+
log::debug!("hello");
49+
unsafe { *buf_len = self.file_data.len() };
50+
Status::BUFFER_TOO_SMALL
51+
} else {
52+
unsafe {
53+
ptr::copy_nonoverlapping(self.file_data.as_ptr(), buf.cast(), self.file_data.len());
54+
}
55+
Status::SUCCESS
56+
}
57+
}
58+
}
59+
60+
unsafe fn install_protocol(
61+
bt: &BootServices,
62+
handle: Handle,
63+
guid: Guid,
64+
protocol: &mut CustomLoadFile2Protocol,
65+
) {
66+
bt.install_protocol_interface(Some(handle), &guid, addr_of!(*protocol).cast())
67+
.unwrap();
68+
}
69+
70+
unsafe fn uninstall_protocol(
71+
bt: &BootServices,
72+
handle: Handle,
73+
guid: Guid,
74+
protocol: &mut CustomLoadFile2Protocol,
75+
) {
76+
bt.uninstall_protocol_interface(handle, &guid, addr_of!(*protocol).cast())
77+
.unwrap();
78+
}
79+
80+
/// This tests the LoadFile and LoadFile2 protocols. As this protocol is not
81+
/// implemented in OVMF for the default handle, we implement it manually using
82+
/// `install_protocol_interface`. Then, we load a file from our custom installed
83+
/// protocol leveraging our protocol abstraction.
84+
///
85+
/// the way we are implementing the LoadFile(2) protocol is roughly what certain
86+
/// Linux loaders do so that Linux can find its initrd [0, 1].
87+
///
88+
/// [0] https://github.com/u-boot/u-boot/commit/ec80b4735a593961fe701cc3a5d717d4739b0fd0#diff-1f940face4d1cf74f9d2324952759404d01ee0a81612b68afdcba6b49803bdbbR171
89+
/// [1] https://github.com/torvalds/linux/blob/ee9a43b7cfe2d8a3520335fea7d8ce71b8cabd9d/drivers/firmware/efi/libstub/efi-stub-helper.c#L550
90+
pub fn test(bt: &BootServices) {
91+
let image = bt.image_handle();
92+
93+
let load_data_msg = "Hello World";
94+
let load_data = load_data_msg.to_string().into_bytes();
95+
let mut proto_load_file = CustomLoadFile2Protocol::new(load_data);
96+
// Get the ptr to the inner value, not the wrapping smart pointer type.
97+
let proto_load_file_ptr = proto_load_file.as_mut().get_mut();
98+
99+
// Install our custom protocol implementation as LoadFile and LoadFile2
100+
// protocol.
101+
unsafe {
102+
install_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr);
103+
install_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr);
104+
}
105+
106+
let mut dvp_vec = Vec::new();
107+
let dvp = DevicePathBuilder::with_vec(&mut dvp_vec);
108+
let dvp = dvp.finalize().unwrap();
109+
110+
let mut opened_load_file_protocol = bt.open_protocol_exclusive::<LoadFile>(image).unwrap();
111+
let loadfile_file = opened_load_file_protocol.load_file(dvp, true).unwrap();
112+
let loadfile_file_string = String::from_utf8(loadfile_file.to_vec()).unwrap();
113+
114+
let mut opened_load_file2_protocol = bt.open_protocol_exclusive::<LoadFile2>(image).unwrap();
115+
let loadfile2_file = opened_load_file2_protocol.load_file(dvp).unwrap();
116+
let loadfile2_file_string = String::from_utf8(loadfile2_file.to_vec()).unwrap();
117+
118+
assert_eq!(load_data_msg, &loadfile_file_string);
119+
assert_eq!(load_data_msg, &loadfile2_file_string);
120+
121+
// Cleanup: Uninstall protocols again.
122+
drop(opened_load_file_protocol);
123+
drop(opened_load_file2_protocol);
124+
unsafe {
125+
uninstall_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr);
126+
uninstall_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr);
127+
}
128+
}

uefi-test-runner/src/proto/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub fn test(st: &mut SystemTable<Boot>) {
1414
debug::test(bt);
1515
device_path::test(bt);
1616
driver::test(bt);
17+
load::test(bt);
1718
loaded_image::test(bt);
1819
media::test(bt);
1920
network::test(bt);
@@ -59,6 +60,7 @@ mod console;
5960
mod debug;
6061
mod device_path;
6162
mod driver;
63+
mod load;
6264
mod loaded_image;
6365
mod media;
6466
mod misc;

uefi/CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
This comes with some changes. Read below. We recommend to directly use the
1717
implementations instead of the traits.
1818
- Added `LoadFile` and `LoadFile2` which abstracts over the `LOAD_FILE` and
19-
`LOAD_FILE2` protocols.
19+
`LOAD_FILE2` protocols. The UEFI test runner includes an integration test
20+
that shows how Linux loaders can use this to implement the initrd loading
21+
mechanism used in Linux.
2022

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

0 commit comments

Comments
 (0)