Skip to content

uefi: alloc_pages() and alloc_pool() now return NonNull<[u8]> #1606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

phip1611
Copy link
Member

@phip1611 phip1611 commented Apr 6, 2025

uefi: alloc_pages() and alloc_pool() now return NonNull<[u8]>. This aligns the signature with the Rust allocator API and also makes more sense.

Checklist

  • Sensible git history (for example, squash "typo" or "fix" commits). See the Rewriting History guide for help.
  • Update the changelog (if necessary)

@phip1611 phip1611 requested a review from nicholasbishop April 6, 2025 19:17
@phip1611 phip1611 mentioned this pull request Apr 6, 2025
13 tasks
@phip1611 phip1611 self-assigned this Apr 7, 2025

Ok(NonNull::new(ptr).expect("allocate_pool must not return a null pointer if successful"))
if let Some(ptr) = NonNull::new(ptr) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let Some(ptr) = NonNull::new(ptr) {
NonNull::new(ptr).map(|ptr| NonNull::slice_from_raw_parts(ptr, size)).ok_or(Status::OUT_OF_RESOURCES.into())

@@ -281,7 +281,7 @@ impl MemoryMapBackingMemory {
pub(crate) fn new(memory_type: MemoryType) -> crate::Result<Self> {
let memory_map_meta = boot::memory_map_size();
let len = Self::safe_allocation_size_hint(memory_map_meta);
let ptr = boot::allocate_pool(memory_type, len)?.as_ptr();
let ptr = boot::allocate_pool(memory_type, len)?.cast::<u8>().as_ptr();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making the new API compatible with existing code; shortcut

@@ -100,7 +102,8 @@ unsafe impl GlobalAlloc for Allocator {
// `allocate_pool` always provides eight-byte alignment, so we can
// use `allocate_pool` directly.
boot::allocate_pool(memory_type, size)
.map(|ptr| ptr.as_ptr())
.map(|mut ptr: NonNull<[u8]>| unsafe { ptr.as_mut() })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is unsound: as_mut creates a reference, but that's not valid because the allocated memory is uninitialized. I think instead this can be:

            boot::allocate_pool(memory_type, size)
                .map(|ptr: NonNull<[u8]>| ptr.as_ptr().cast::<u8>())
                .unwrap_or(ptr::null_mut())

(Potentially in the future it could be further simplified to ptr.as_mut_ptr(), but it's not yet stable.)

Copy link
Member Author

@phip1611 phip1611 Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is unsound: as_mut creates a reference, but that's not valid because the allocated memory is uninitialized.

Discussion continues here: #1606 (comment)

let addr = ptr.as_ptr() as usize;
assert_eq!(addr % 4096, 0, "Page pointer is not page-aligned");

let buffer = unsafe { ptr.as_mut() };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here, this is unsound with uninitialized memory. I don't think we have to use write_volatile like the code here did originally, but we do need to use some pointer function like ptr::write or ptr::write_bytes.

Since so many of the NonNull<[T]>/*mut [T] functions are unstable, I think it's most convenient to cast it back to *mut u8. Then you can do unsafe { ptr.write_bytes(...) }.

Ditto for the docstring examples on boot::allocate_pages/boot::allocate_pool.

Copy link
Member Author

@phip1611 phip1611 Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the concern you raised is not correct: #1606 (comment)

This aligns the signature with the Rust allocator API.
This aligns the signature with the Rust allocator API.
@phip1611
Copy link
Member Author

phip1611 commented Apr 15, 2025

I think we are safe if we write the following into the documentation of each function:

image

I think this is valid as Miri verifies my assumptions in this code:

#![feature(allocator_api)]

use std::alloc::{Allocator, GlobalAlloc, Layout, System};

fn main() {
    let layout = Layout::from_size_align(4096, 4096).unwrap();
    let mut buffer_ptr = System.allocate(layout).unwrap();
    let buffer = unsafe { buffer_ptr.as_mut() };
    buffer[1] = 42;
    let _x = buffer[1];
    let _y = buffer[2];

    unsafe {
        System.dealloc(buffer_ptr.as_ptr().cast(), layout);
    }
}

What do you think, are we on the same page?

@nicholasbishop
Copy link
Member

Miri is a runtime checker, so it can't fully detect all cases of UB. I'm pretty confident that creating a reference to uninitialized memory is always UB, regardless of whether the reference is read from, unless you use MaybeUninit or a similar construct. The MaybeUninit doc has some examples of instant-UB that don't require a read.

So there's no documentation-level exemption we can give ourselves -- the allocation must be initialized via the pointer API (or &[MaybeUninit<u8>]).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants