Skip to content

Commit d3206d6

Browse files
Protocol safety improvements (#460)
* Add BootServices::get_handle_for_protocol This is a convenience method to get any arbitrary handle that supports a particular `Protocol`. * Use open_protocol in shim-lock test * Use open_protocol in multiprocessor test * Use open_protocol in device path test * Use open_protocol in pointer test * Use open_protocol in graphics test * Use open_protocol in file system test * Use open_protocol in the serial device test * Simplify event callback with context test Rather than opening the Output protocol, which isn't important for this test, just write through the context pointer and assert that the expected data was written. * Mark handle_protocol as unsafe This function is already marked deprecated, mark it unsafe as well and update the documentation to describe why. * Deprecate BootServices::locate_protocol and mark it unsafe This method has the same problems as `handle_protocol`; it does not mark the handle and protocol as in use. Calls to `locate_protocol` can be replaced by calling `get_handle_for_protocol` and `open_protocol`. #359
1 parent c171a4e commit d3206d6

File tree

14 files changed

+205
-48
lines changed

14 files changed

+205
-48
lines changed

Diff for: CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
1010
which is now marked as deprecated.
1111
- Implemented `core::fmt::Write` for the `Serial` protocol.
1212
- Added the `MemoryProtection` protocol.
13+
- Added `BootServices::get_handle_for_protocol`.
14+
15+
### Changed
16+
17+
- Marked `BootServices::handle_protocol` as `unsafe`. (This method is
18+
also deprecated -- use `open_protocol` instead.)
19+
- Deprecated `BootServices::locate_protocol` and marked it `unsafe`. Use
20+
`BootServices::get_handle_for_protocol` and
21+
`BootServices::open_protocol` instead.
1322

1423
### Fixed
1524

Diff for: src/table/boot.rs

+71-4
Original file line numberDiff line numberDiff line change
@@ -576,14 +576,21 @@ impl BootServices {
576576
/// protections must be implemented by user-level code, for example via a
577577
/// global `HashSet`.
578578
///
579+
/// # Safety
580+
///
581+
/// This method is unsafe because the handle database is not
582+
/// notified that the handle and protocol are in use; there is no
583+
/// guarantee that they will remain valid for the duration of their
584+
/// use. Use [`open_protocol`] instead.
585+
///
579586
/// [`open_protocol`]: BootServices::open_protocol
580587
#[deprecated(note = "it is recommended to use `open_protocol` instead")]
581-
pub fn handle_protocol<P: ProtocolPointer + ?Sized>(
588+
pub unsafe fn handle_protocol<P: ProtocolPointer + ?Sized>(
582589
&self,
583590
handle: Handle,
584591
) -> Result<&UnsafeCell<P>> {
585592
let mut ptr = ptr::null_mut();
586-
(self.handle_protocol)(handle, &P::GUID, &mut ptr).into_with_val(|| unsafe {
593+
(self.handle_protocol)(handle, &P::GUID, &mut ptr).into_with_val(|| {
587594
let ptr = P::mut_ptr_from_ffi(ptr) as *const UnsafeCell<P>;
588595
&*ptr
589596
})
@@ -649,6 +656,57 @@ impl BootServices {
649656
}
650657
}
651658

659+
/// Find an arbitrary handle that supports a particular
660+
/// [`Protocol`]. Returns [`NOT_FOUND`] if no handles support the
661+
/// protocol.
662+
///
663+
/// This method is a convenient wrapper around
664+
/// [`BootServices::locate_handle_buffer`] for getting just one
665+
/// handle. This is useful when you don't care which handle the
666+
/// protocol is opened on. For example, [`DevicePathToText`] isn't
667+
/// tied to a particular device, so only a single handle is expected
668+
/// to exist.
669+
///
670+
/// [`NOT_FOUND`]: Status::NOT_FOUND
671+
/// [`DevicePathToText`]: uefi::proto::device_path::text::DevicePathToText
672+
///
673+
/// # Example
674+
///
675+
/// ```
676+
/// use uefi::proto::device_path::text::DevicePathToText;
677+
/// use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};
678+
/// use uefi::Handle;
679+
/// # use uefi::Result;
680+
///
681+
/// # fn get_fake_val<T>() -> T { todo!() }
682+
/// # fn test() -> Result {
683+
/// # let boot_services: &BootServices = get_fake_val();
684+
/// # let image_handle: Handle = get_fake_val();
685+
/// let handle = boot_services.get_handle_for_protocol::<DevicePathToText>()?;
686+
/// let device_path_to_text = boot_services.open_protocol::<DevicePathToText>(
687+
/// OpenProtocolParams {
688+
/// handle,
689+
/// agent: image_handle,
690+
/// controller: None,
691+
/// },
692+
/// OpenProtocolAttributes::Exclusive,
693+
/// )?;
694+
/// # Ok(())
695+
/// # }
696+
/// ```
697+
pub fn get_handle_for_protocol<P: Protocol>(&self) -> Result<Handle> {
698+
// Delegate to a non-generic function to potentially reduce code size.
699+
self.get_handle_for_protocol_impl(&P::GUID)
700+
}
701+
702+
fn get_handle_for_protocol_impl(&self, guid: &Guid) -> Result<Handle> {
703+
self.locate_handle_buffer(SearchType::ByProtocol(guid))?
704+
.handles()
705+
.first()
706+
.cloned()
707+
.ok_or_else(|| Status::NOT_FOUND.into())
708+
}
709+
652710
/// Load an EFI image into memory and return a [`Handle`] to the image.
653711
///
654712
/// There are two ways to load the image: by copying raw image data
@@ -961,9 +1019,18 @@ impl BootServices {
9611019
/// Returns a protocol implementation, if present on the system.
9621020
///
9631021
/// The caveats of `BootServices::handle_protocol()` also apply here.
964-
pub fn locate_protocol<P: ProtocolPointer + ?Sized>(&self) -> Result<&UnsafeCell<P>> {
1022+
///
1023+
/// # Safety
1024+
///
1025+
/// This method is unsafe because the handle database is not
1026+
/// notified that the handle and protocol are in use; there is no
1027+
/// guarantee that they will remain valid for the duration of their
1028+
/// use. Use [`BootServices::get_handle_for_protocol`] and
1029+
/// [`BootServices::open_protocol`] instead.
1030+
#[deprecated(note = "it is recommended to use `open_protocol` instead")]
1031+
pub unsafe fn locate_protocol<P: ProtocolPointer + ?Sized>(&self) -> Result<&UnsafeCell<P>> {
9651032
let mut ptr = ptr::null_mut();
966-
(self.locate_protocol)(&P::GUID, ptr::null_mut(), &mut ptr).into_with_val(|| unsafe {
1033+
(self.locate_protocol)(&P::GUID, ptr::null_mut(), &mut ptr).into_with_val(|| {
9671034
let ptr = P::mut_ptr_from_ffi(ptr) as *const UnsafeCell<P>;
9681035
&*ptr
9691036
})

Diff for: uefi-test-runner/src/boot/misc.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use core::ffi::c_void;
22
use core::ptr::NonNull;
33

4-
use uefi::proto::console::text::Output;
54
use uefi::table::boot::{BootServices, EventType, TimerTrigger, Tpl};
65
use uefi::Event;
76

@@ -37,28 +36,35 @@ fn test_event_callback(bt: &BootServices) {
3736
}
3837

3938
fn test_callback_with_ctx(bt: &BootServices) {
39+
let mut data = 123u32;
40+
4041
extern "efiapi" fn callback(_event: Event, ctx: Option<NonNull<c_void>>) {
4142
info!("Inside the event callback with context");
43+
// Safety: this callback is run within the parent function's
44+
// scope, so the context pointer is still valid.
4245
unsafe {
43-
let ctx = &mut *(ctx.unwrap().as_ptr() as *mut Output);
44-
// Clear the screen as a quick test that we successfully passed context
45-
ctx.clear().expect("Failed to clear screen");
46+
let ctx = ctx.unwrap().as_ptr().cast::<u32>();
47+
*ctx = 456;
4648
}
4749
}
4850

49-
let ctx = unsafe { &mut *(bt.locate_protocol::<Output>().unwrap().get()) };
51+
let ctx: *mut u32 = &mut data;
52+
let ctx = NonNull::new(ctx.cast::<c_void>()).unwrap();
5053

5154
let event = unsafe {
5255
bt.create_event(
5356
EventType::NOTIFY_WAIT,
5457
Tpl::CALLBACK,
5558
Some(callback),
56-
Some(NonNull::new_unchecked(ctx as *mut _ as *mut c_void)),
59+
Some(ctx),
5760
)
5861
.expect("Failed to create event with context")
5962
};
6063

6164
bt.check_event(event).expect("Failed to check event");
65+
66+
// Check that `data` was updated inside the event callback.
67+
assert_eq!(data, 456);
6268
}
6369

6470
fn test_watchdog(bt: &BootServices) {

Diff for: uefi-test-runner/src/proto/console/gop.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
use uefi::prelude::*;
22
use uefi::proto::console::gop::{BltOp, BltPixel, FrameBuffer, GraphicsOutput, PixelFormat};
3-
use uefi::table::boot::BootServices;
3+
use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};
44

55
pub fn test(image: Handle, bt: &BootServices) {
66
info!("Running graphics output protocol test");
7-
if let Ok(gop) = bt.locate_protocol::<GraphicsOutput>() {
8-
let gop = unsafe { &mut *gop.get() };
7+
if let Ok(handle) = bt.get_handle_for_protocol::<GraphicsOutput>() {
8+
let gop = &mut bt
9+
.open_protocol::<GraphicsOutput>(
10+
OpenProtocolParams {
11+
handle,
12+
agent: image,
13+
controller: None,
14+
},
15+
// For this test, don't open in exclusive mode. That
16+
// would break the connection between stdout and the
17+
// video console.
18+
OpenProtocolAttributes::GetProtocol,
19+
)
20+
.expect("failed to open Graphics Output Protocol");
921

1022
set_graphics_mode(gop);
1123
fill_color(gop);

Diff for: uefi-test-runner/src/proto/console/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ pub fn test(image: Handle, st: &mut SystemTable<Boot>) {
66
stdout::test(st.stdout());
77

88
let bt = st.boot_services();
9-
serial::test(bt);
9+
serial::test(image, bt);
1010
gop::test(image, bt);
11-
pointer::test(bt);
11+
pointer::test(image, bt);
1212
}
1313

1414
mod gop;

Diff for: uefi-test-runner/src/proto/console/pointer.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
use uefi::proto::console::pointer::Pointer;
2-
use uefi::table::boot::BootServices;
2+
use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};
3+
use uefi::Handle;
34

4-
pub fn test(bt: &BootServices) {
5+
pub fn test(image: Handle, bt: &BootServices) {
56
info!("Running pointer protocol test");
6-
if let Ok(pointer) = bt.locate_protocol::<Pointer>() {
7-
let pointer = unsafe { &mut *pointer.get() };
7+
if let Ok(handle) = bt.get_handle_for_protocol::<Pointer>() {
8+
let mut pointer = bt
9+
.open_protocol::<Pointer>(
10+
OpenProtocolParams {
11+
handle,
12+
agent: image,
13+
controller: None,
14+
},
15+
OpenProtocolAttributes::Exclusive,
16+
)
17+
.expect("failed to open pointer protocol");
818

919
pointer
1020
.reset(false)

Diff for: uefi-test-runner/src/proto/console/serial.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
use uefi::proto::console::serial::{ControlBits, Serial};
2-
use uefi::table::boot::BootServices;
2+
use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};
3+
use uefi::Handle;
34

4-
pub fn test(bt: &BootServices) {
5+
pub fn test(image: Handle, bt: &BootServices) {
56
info!("Running serial protocol test");
6-
if let Ok(serial) = bt.locate_protocol::<Serial>() {
7+
if let Ok(handle) = bt.get_handle_for_protocol::<Serial>() {
8+
let mut serial = bt
9+
.open_protocol::<Serial>(
10+
OpenProtocolParams {
11+
handle,
12+
agent: image,
13+
controller: None,
14+
},
15+
// For this test, don't open in exclusive mode. That
16+
// would break the connection between stdout and the
17+
// serial device.
18+
OpenProtocolAttributes::GetProtocol,
19+
)
20+
.expect("failed to open serial protocol");
721
// BUG: there are multiple failures in the serial tests on AArch64
822
if cfg!(target_arch = "aarch64") {
923
return;
1024
}
1125

12-
let serial = unsafe { &mut *serial.get() };
13-
1426
let old_ctrl_bits = serial
1527
.get_control_bits()
1628
.expect("Failed to get device control bits");

Diff for: uefi-test-runner/src/proto/device_path.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,30 @@ pub fn test(image: Handle, bt: &BootServices) {
2929
.expect("Failed to open DevicePath protocol");
3030

3131
let device_path_to_text = bt
32-
.locate_protocol::<DevicePathToText>()
32+
.open_protocol::<DevicePathToText>(
33+
OpenProtocolParams {
34+
handle: bt
35+
.get_handle_for_protocol::<DevicePathToText>()
36+
.expect("Failed to get DevicePathToText handle"),
37+
agent: image,
38+
controller: None,
39+
},
40+
OpenProtocolAttributes::Exclusive,
41+
)
3342
.expect("Failed to open DevicePathToText protocol");
34-
let device_path_to_text = unsafe { &*device_path_to_text.get() };
3543

3644
let device_path_from_text = bt
37-
.locate_protocol::<DevicePathFromText>()
45+
.open_protocol::<DevicePathFromText>(
46+
OpenProtocolParams {
47+
handle: bt
48+
.get_handle_for_protocol::<DevicePathFromText>()
49+
.expect("Failed to get DevicePathFromText handle"),
50+
agent: image,
51+
controller: None,
52+
},
53+
OpenProtocolAttributes::Exclusive,
54+
)
3855
.expect("Failed to open DevicePathFromText protocol");
39-
let device_path_from_text = unsafe { &*device_path_from_text.get() };
4056

4157
for path in device_path.node_iter() {
4258
info!(

Diff for: uefi-test-runner/src/proto/media/mod.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,18 @@ fn test_file_system_info(directory: &mut Directory) {
2727
pub fn test(image: Handle, bt: &BootServices) {
2828
info!("Testing Media Access protocols");
2929

30-
if let Ok(sfs) = bt.locate_protocol::<SimpleFileSystem>() {
31-
let sfs = unsafe { &mut *sfs.get() };
30+
if let Ok(handle) = bt.get_handle_for_protocol::<SimpleFileSystem>() {
31+
let mut sfs = bt
32+
.open_protocol::<SimpleFileSystem>(
33+
OpenProtocolParams {
34+
handle,
35+
agent: image,
36+
controller: None,
37+
},
38+
OpenProtocolAttributes::Exclusive,
39+
)
40+
.expect("failed to open SimpleFileSystem protocol");
41+
3242
let mut directory = sfs.open_volume().unwrap();
3343
let mut buffer = vec![0; 128];
3444
loop {

Diff for: uefi-test-runner/src/proto/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub fn test(image: Handle, st: &mut SystemTable<Boot>) {
1717
loaded_image::test(image, bt);
1818
media::test(image, bt);
1919
network::test(image, bt);
20-
pi::test(bt);
20+
pi::test(image, bt);
2121
rng::test(image, bt);
2222

2323
#[cfg(any(
@@ -26,7 +26,7 @@ pub fn test(image: Handle, st: &mut SystemTable<Boot>) {
2626
target_arch = "arm",
2727
target_arch = "aarch64"
2828
))]
29-
shim::test(bt);
29+
shim::test(image, bt);
3030
}
3131

3232
fn find_protocol(bt: &BootServices) {

Diff for: uefi-test-runner/src/proto/pi/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use uefi::prelude::*;
22

3-
pub fn test(bt: &BootServices) {
3+
pub fn test(image: Handle, bt: &BootServices) {
44
info!("Testing Platform Initialization protocols");
55

6-
mp::test(bt);
6+
mp::test(image, bt);
77
}
88

99
mod mp;

Diff for: uefi-test-runner/src/proto/pi/mp.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,30 @@ use core::ffi::c_void;
22
use core::sync::atomic::{AtomicUsize, Ordering};
33
use core::time::Duration;
44
use uefi::proto::pi::mp::MpServices;
5-
use uefi::table::boot::BootServices;
6-
use uefi::Status;
5+
use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};
6+
use uefi::{Handle, Status};
77

88
/// Number of cores qemu is configured to have
99
const NUM_CPUS: usize = 4;
1010

11-
pub fn test(bt: &BootServices) {
11+
pub fn test(image: Handle, bt: &BootServices) {
1212
// These tests break CI. See #103.
1313
if cfg!(feature = "ci") {
1414
return;
1515
}
1616

1717
info!("Running UEFI multi-processor services protocol test");
18-
if let Ok(mp_support) = bt.locate_protocol::<MpServices>() {
19-
let mp_support = unsafe { &mut *mp_support.get() };
18+
if let Ok(handle) = bt.get_handle_for_protocol::<MpServices>() {
19+
let mp_support = &bt
20+
.open_protocol::<MpServices>(
21+
OpenProtocolParams {
22+
handle,
23+
agent: image,
24+
controller: None,
25+
},
26+
OpenProtocolAttributes::Exclusive,
27+
)
28+
.expect("failed to open multi-processor services protocol");
2029

2130
test_get_number_of_processors(mp_support);
2231
test_get_processor_info(mp_support);

0 commit comments

Comments
 (0)