-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
5 changed files
with
138 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters