Patina: R-EFI 6.0 migration#1480
Conversation
QEMU Validation FailedQEMU validation did not complete successfully or did not shutdown as expected. Workflow run: https://github.com/OpenDevicePartnership/patina/actions/runs/26255574795
|
| Job | Result |
|---|---|
| Gather Incoming PR Metadata | ✅ |
| Run Patina QEMU Validation / Post In-Progress Notification | ✅ |
| Run Patina QEMU Validation / Preflight Checks | ✅ |
| Run Patina QEMU Validation / Get Constants / Get Repository Constants | ✅ |
| Run Patina QEMU Validation / Validate QEMU - SBSA (Linux) | ❌ |
| Run Patina QEMU Validation / Validate QEMU - Q35 (Linux) | ❌ |
| Run Patina QEMU Validation / Validate QEMU Q35 (Windows) | ❌ |
| Run Patina QEMU Validation / Emit PR Metadata | ✅ |
Error Details
qemu-validation-logs-Linux-SBSA/sbsa-linux.log (15 error/warning sections)
… (truncated)
error[E0308]: mismatched types
--> src/q35/component/service/smbios_platform.rs:125:30
|
125 | security_status: 0x02,
| ^^^^ expected `SecurityStatus`, found integer
error[E0308]: mismatched types
--> src/q35/component/service/smbios_platform.rs:155:28
|
155 | feature_flags: 0x01, // Board is a hosting board
| ^^^^ expected `FeatureFlags`, found integer
|
help: call `Into::into` on this expression to convert `{integer}` into `FeatureFlags`
|
155 | feature_flags: 0x01.into(), // Board is a hosting board
| +++++++
error[E0308]: mismatched types
--> src/q35/component/service/smbios_platform.rs:158:25
|
158 | board_type: 0x0A, // Motherboard
| ^^^^ expected `BoardType`, found integer
error[E0308]: mismatched types
--> src/q35/component/service/smbios_test.rs:45:49
|
45 | boot_services.locate_protocol_unchecked(&SMBIOS_PROTOCOL_GUID, core::ptr::null_mut()).map_err(|e| {
| ------------------------- ^^^^^^^^^^^^^^^^^^^^^ expected `r_efi::base::Guid`, found a different `r_efi::base::Guid`
| |
| arguments to this method are incorrect
|
note: there are multiple different versions of crate `r_efi` in the dependency graph
--> /.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/r-efi-6.0.0/src/base.rs:392:1
|
392 | pub struct Guid {
| ^^^^^^^^^^^^^^^ this is the expected type
|
::: /.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/r-efi-5.3.0/src/base.rs:392:1
|
392 | pub struct Guid {
| --------------- this is the found type
= help: you can use `cargo tree` to explore your dependency tree
note: method defined here
--> /__w/patina/patina/sdk/patina/src/boot_services.rs:826:15
|
826 | unsafe fn locate_protocol_unchecked(
| ^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `qemu_dxe_core` (lib) due to 12 previous errors
warning: build failed, waiting for other jobs to finish...
[cargo-make] ERROR - Error while running duckscript: Source: Unknown Line: 113 - Error while executing command, exit code: 101
qemu-validation-logs-Linux-Q35/q35-linux.log (15 error/warning sections)
… (truncated)
error[E0308]: mismatched types
--> src/q35/component/service/smbios_platform.rs:125:30
|
125 | security_status: 0x02,
| ^^^^ expected `SecurityStatus`, found integer
error[E0308]: mismatched types
--> src/q35/component/service/smbios_platform.rs:155:28
|
155 | feature_flags: 0x01, // Board is a hosting board
| ^^^^ expected `FeatureFlags`, found integer
|
help: call `Into::into` on this expression to convert `{integer}` into `FeatureFlags`
|
155 | feature_flags: 0x01.into(), // Board is a hosting board
| +++++++
error[E0308]: mismatched types
--> src/q35/component/service/smbios_platform.rs:158:25
|
158 | board_type: 0x0A, // Motherboard
| ^^^^ expected `BoardType`, found integer
error[E0308]: mismatched types
--> src/q35/component/service/smbios_test.rs:45:49
|
45 | boot_services.locate_protocol_unchecked(&SMBIOS_PROTOCOL_GUID, core::ptr::null_mut()).map_err(|e| {
| ------------------------- ^^^^^^^^^^^^^^^^^^^^^ expected `r_efi::base::Guid`, found a different `r_efi::base::Guid`
| |
| arguments to this method are incorrect
|
note: there are multiple different versions of crate `r_efi` in the dependency graph
--> /.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/r-efi-6.0.0/src/base.rs:392:1
|
392 | pub struct Guid {
| ^^^^^^^^^^^^^^^ this is the expected type
|
::: /.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/r-efi-5.3.0/src/base.rs:392:1
|
392 | pub struct Guid {
| --------------- this is the found type
= help: you can use `cargo tree` to explore your dependency tree
note: method defined here
--> /__w/patina/patina/sdk/patina/src/boot_services.rs:826:15
|
826 | unsafe fn locate_protocol_unchecked(
| ^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `qemu_dxe_core` (lib) due to 12 previous errors
warning: build failed, waiting for other jobs to finish...
[cargo-make] ERROR - Error while running duckscript: Source: Unknown Line: 113 - Error while executing command, exit code: 101
qemu-validation-logs-Windows-Q35/q35-windows.log (15 error/warning sections)
… (truncated)
error[E0308]: mismatched types
--> src\q35\component\service\smbios_platform.rs:125:30
|
125 | security_status: 0x02,
| ^^^^ expected `SecurityStatus`, found integer
error[E0308]: mismatched types
--> src\q35\component\service\smbios_platform.rs:155:28
|
155 | feature_flags: 0x01, // Board is a hosting board
| ^^^^ expected `FeatureFlags`, found integer
|
help: call `Into::into` on this expression to convert `{integer}` into `FeatureFlags`
|
155 | feature_flags: 0x01.into(), // Board is a hosting board
| +++++++
error[E0308]: mismatched types
--> src\q35\component\service\smbios_platform.rs:158:25
|
158 | board_type: 0x0A, // Motherboard
| ^^^^ expected `BoardType`, found integer
error[E0308]: mismatched types
--> src\q35\component\service\smbios_test.rs:45:49
|
45 | boot_services.locate_protocol_unchecked(&SMBIOS_PROTOCOL_GUID, core::ptr::null_mut()).map_err(|e| {
| ------------------------- ^^^^^^^^^^^^^^^^^^^^^ expected `r_efi::base::Guid`, found a different `r_efi::base::Guid`
| |
| arguments to this method are incorrect
|
note: there are multiple different versions of crate `r_efi` in the dependency graph
--> C:\Users\runneradmin\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\r-efi-6.0.0\src\base.rs:392:1
|
392 | pub struct Guid {
| ^^^^^^^^^^^^^^^ this is the expected type
|
::: C:\Users\runneradmin\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\r-efi-5.3.0\src\base.rs:392:1
|
392 | pub struct Guid {
| --------------- this is the found type
= help: you can use `cargo tree` to explore your dependency tree
note: method defined here
--> D:\a\patina\patina\sdk\patina\src\boot_services.rs:826:15
|
826 | unsafe fn locate_protocol_unchecked(
| ^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `qemu_dxe_core` (lib) due to 12 previous errors
warning: build failed, waiting for other jobs to finish...
[cargo-make] ERROR - Error while running duckscript: Source: Unknown Line: 113 - Error while executing command, exit code: 101
Dependencies
| Repository | Ref |
|---|---|
| patina | 6114c86 |
| patina-dxe-core-qemu | af9c5f5 |
| patina-fw-patcher | d6d246b |
| patina-qemu firmware | v3.0.0 |
| patina-qemu build script | 965bc5f |
This comment was automatically generated by the Patina QEMU PR Validation Post workflow.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
I haven't looked at the PR changes yet, but it should target the |
102fbd3 to
ede86cc
Compare
Michael, The branch is now updated to target major instead of main. |
ede86cc to
3e6fd6c
Compare
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
3e6fd6c to
cc656fc
Compare
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
Signed-off-by: Vineel Kovvuri[MSFT] <vineelko@microsoft.com>
cc656fc to
6114c86
Compare
| /// # Safety | ||
| /// | ||
| /// `ptr` must be non-null and must point to a valid, well-formed UEFI device path | ||
| /// node that remains valid for as long as the returned `DevicePathPtr` is used. |
There was a problem hiding this comment.
nit: I think in "Rust-ese" this would be
| /// node that remains valid for as long as the returned `DevicePathPtr` is used. | |
| /// node that remains valid for the lifetime of the returned `DevicePathPtr`. |
| } | ||
|
|
||
| // SAFETY: caller must ensure that "memory" is a valid pointer. It is null-checked above. | ||
| let address = unsafe { memory.read_unaligned() }; |
There was a problem hiding this comment.
memory is treated differently depending on the allocation type.
https://uefi.org/specs/UEFI/2.11/07_Services_Boot_Services.html#efi-boot-services-allocatepages
For AllocateAnyPages, the address pointed to by memory is supposed to be ignored on input.
Can you please read through the API constraints in the spec and update this implementation to be compliant?
| /// ``` | ||
| /// | ||
| pub unsafe fn core_disconnect_controller( | ||
| pub fn core_disconnect_controller( |
There was a problem hiding this comment.
I notice that you changed core_disconnect_controller() from unsafe to safe, and I understand the reasoning, given the platform/environment uncertainties during disconnect versus conditions controlled by the caller for connect. While the nuance is fine, at the function level, the asymmetry is more apparent by the fact that core_connect_controller() remains unsafe.
Can you please add a brief clarifying comment about this in the core_disconnect_controller() doc comment?
| pub unsafe fn new(ptr: *mut Protocol) -> Self { | ||
| // SAFETY: Caller guarantees ptr is non-null and valid. | ||
| debug_assert!(!ptr.is_null()); | ||
| Self(ptr) | ||
| } |
There was a problem hiding this comment.
What's the difference betwen this and just directly using NonNull<Protocol>?
From the function safety comment:
ptrmust be non-null and must point to a valid, well-formed UEFI device path
node that remains valid for as long as the returnedDevicePathPtris used.
DevicePathPtr::new() let's this be violated in cases where debug asserts are not enabled:
pub unsafe fn new(ptr: *mut Protocol) -> Self {
// SAFETY: Caller guarantees ptr is non-null and valid.
debug_assert!(!ptr.is_null());
Self(ptr)
}Versus an implementation like that in NonNull::new():
| pub unsafe fn new(ptr: *mut Protocol) -> Self { | |
| // SAFETY: Caller guarantees ptr is non-null and valid. | |
| debug_assert!(!ptr.is_null()); | |
| Self(ptr) | |
| } | |
| pub unsafe fn new(ptr: *mut Protocol) -> Option<Self> { | |
| if !ptr.is_null() { | |
| // SAFETY: The pointer is already checked and is not null | |
| Some(unsafe { Self::new_unchecked(ptr) }) | |
| } else { | |
| None | |
| } | |
| } |
(the example above references a non-existing DevicePathPtr::new_unchecked(ptr) function)
My other concerns is the constructor just stores the pointer. A pointer storage operation can trivially verify that the pointer is non-null but the "validity" part of this really applies to dereference time.
All of this gives the impression that DevicePathPtr promises more safety than is actually delivered which might end up complicating safe handling of device paths overall. For example, I saw other code in image.rs leaning on safety assumptions of the type.
If this stays mostly as it is, could it just be a transparent wrapper around NonNull. Something like:
#[repr(transparent)]
#[derive(Copy, Clone, Debug)]
pub struct DevicePathPtr(NonNull<Protocol>);
//...
impl DevicePathPtr {
pub unsafe fn new_unchecked(ptr: *mut Protocol) -> Self {
// SAFETY: caller guarantees non-null.
Self(unsafe { NonNull::new_unchecked(ptr) })
}
/// Safe constructor. Returns `None` on null. No unsafe needed.
pub fn new(ptr: *mut Protocol) -> Option<Self> {
NonNull::new(ptr).map(Self)
}
pub fn as_ptr(self) -> *mut Protocol { self.0.as_ptr() }
/// Reads the device path node header.
///
/// # Safety
/// The pointee must be a valid, well-formed UEFI device path node and remain
/// live for the duration of the read.
pub unsafe fn read_header(self) -> Protocol { /* ... */ }
// ...
}This simplifies implementation reusing NonNull logic, focuses new() on storage and the nullness check, and moves header reads to be in their own unsafe blocks in callers where they have to uphold the safety invariant at the time it is relevant.
But, really, it seems to me like just using NonNull<Protocol> (this type removed) wouldn't lose much, be a little more clear, and reduce maintenance.
| desc.memory_type, | ||
| uefi_size_to_pages!(desc.memory_length as usize), | ||
| &mut address as *mut efi::PhysicalAddress, | ||
| address as efi::PhysicalAddress, |
There was a problem hiding this comment.
You shouldn't need to cast address here.
| address as efi::PhysicalAddress, | |
| address, |
| /// Uninstalls a protocol interface on a handle. | ||
| /// | ||
| /// This function is safe because `interface` is an opaque pointer used for | ||
| /// comparision but never dereferenced. All other parameters are value types. |
There was a problem hiding this comment.
| /// comparision but never dereferenced. All other parameters are value types. | |
| /// comparison but never dereferenced. All other parameters are value types. |
| /// # Safety | ||
| /// | ||
| /// `handle` must be a valid pointer to an `efi::Handle` (may point to a null handle for new handle | ||
| /// creation). `protocol` must be a valid pointer to an `efi::Guid`. Both are null checked, but | ||
| /// validity of the referenced memory is the caller's responsibility. Throughout the lifetime of the | ||
| /// interface reference, the caller must ensure it remains valid. |
There was a problem hiding this comment.
Can you clarify this to specifically call out the lifetime of the protocol database (e.g. until uninstalled)? That will take the explanation from generic to more precise.
| @@ -1561,22 +1885,41 @@ impl BootServices for StandardBootServices { | |||
| self, | |||
| ) | |||
| }); | |||
| Err((s, data)) | |||
| Err((status, data)) | |||
| } | |||
| _ => Ok(()), | |||
| } | |||
There was a problem hiding this comment.
I'm not sure that this is doing what is intended.
-
I believe
exit_data.as_ptr()is returning the address ofexit_dataon the stack (not the contained value). -
So,
exit_data.as_ptr().is_null()is always false because the stack address is not null.- Then, in here:
BootServicesBox::from_raw_parts_mut( exit_data.as_mut_ptr() as *mut u8, exit_data_size.assume_init(), self, )
The same thing happens. BootServicesBox is built from the address of the local stack variable with the correct exit_data_size value.
Also, are you sure exit_data.assume_init() is valid? The write to it in start_image_efiapi() is conditionalized.
My understanding is you can clearly get the exit data pointer and size and then do the null check and box construction. Like (not tested):
let exit_data_ptr = unsafe { exit_data.assume_init() };
let exit_data_len = unsafe { exit_data_size.assume_init() };
let data = (!exit_data_ptr.is_null() && exit_data_len > 0).then(|| unsafe {
BootServicesBox::from_raw_parts_mut(exit_data_ptr as *mut u8, exit_data_len, self)
});There was a problem hiding this comment.
Wow that's clearly a bug! Let me fix it properly. Original code incorrectly assumed as_ptr() instead of assume_init().
| // This is triggered by the fact that efi::Handle aliases to *mut c_void, but | ||
| // it is an opaque handle used as a database key. | ||
| #[allow(clippy::not_unsafe_ptr_arg_deref)] | ||
| fn load_image( |
There was a problem hiding this comment.
load_image() is marked safe but still has a function safety comment. This is confusing/not enforceable.
| // This is triggered by the fact that efi::Event aliases to *mut c_void, but | ||
| // it is an opaque handle used as a database key. | ||
| #[allow(clippy::not_unsafe_ptr_arg_deref)] | ||
| fn exit_boot_services(&self, image_handle: efi::Handle, map_key: usize) -> Result<(), efi::Status> { |
There was a problem hiding this comment.
Since there already is a "Safety Considerations" section, can you please include that the entire Boot Services table is no longer callable after this function successfully completes?
Description
R-EFI 6.0 Migration
Upstream r-efi 6.0 marks all
extern "efiapi"function pointers in EFI BootServices, Runtime Services, and other protocols as
unsafe, since their safetycannot be enforced by the Rust type system at compile time.
This PR audits almost all usage of UEFI service function pointers across the Patina
codebase for correctness and safety.
There are three primary areas where
unsafeusage has been audited:extern "efiapi"functionsBootServicestrait thateventually call into the FFI
Not all functions in the above categories need to be marked
unsafe. Forexample,
extern "efiapi" close_eventis not markedunsafebecause it cansafely be called with an arbitrary event parameter without causing undefined
behavior in the Patina implementation. The
eventparameter is treated as anopaque pointer and is never dereferenced.
That said, any indirect caller that dereferences this Boot Services function
pointer must still use an
unsafeblock, since the function pointer itself isdefined as
unsafein r-efi 6.0. In addition, inherently unsafe Rustfunctions (such as
core_free_pool()) are now explicitly markedunsafe.Each inspected function or call site is documented with appropriate safety
comments where necessary, and with explanations where
unsafeis notrequired.
There are no functional changes.
Clippy flags public functions that accept raw pointer parameters and pass them
across an FFI boundary without being marked
unsafe, with the followingwarning:
"this public function might dereference a raw pointer but is not marked
unsafe"In Patina, this pattern is common for functions that take
efi::Eventorefi::Handleparameters and call Boot Services function pointers. Weexplicitly suppress this lint for such functions because these types are
treated as opaque pointers and are never dereferenced:
#[allow(clippy::not_unsafe_ptr_arg_deref)]Geiger Unsafe Stats
How This Was Tested
Booted to UEFI Shell Q35/SBSA
Integration Instructions
All consumers of Patina will need to pick the newer Patina version when published and also should update their r-efi version to 6.0.0.