Skip to content

Let's discuss UEFI's pointer conventions #40

Closed
@HadrienG2

Description

@HadrienG2

The "Calling conventions" section of the the "Overview" chapter of the UEFI spec starts with an interesting read about what can be expected of UEFI when passing pointers to the API, which may or may not influence our FFI interface (the "extern" functions that we use internally), unsafe APIs (those that ingest pointers), and what we consider to be safe APIs.

This is from page 20 of UEFI spec 2.7A, with numbering added to ease discussion:

When passing pointer arguments to Boot Services, Runtime Services, and Protocol Interfaces, the
caller has the following responsibilities:

  1. It is the caller’s responsibility to pass pointer parameters that reference physical memory
    locations. If a pointer is passed that does not point to a physical memory location (i.e., a
    memory mapped I/O region), the results are unpredictable and the system may halt.
  2. It is the caller’s responsibility to pass pointer parameters with correct alignment. If an
    unaligned pointer is passed to a function, the results are unpredictable and the system may
    halt.
  3. It is the caller’s responsibility to not pass in a NULL parameter to a function unless it is explicitly
    allowed. If a NULL pointer is passed to a function, the results are unpredictable and the system
    may hang.
  4. Unless otherwise stated, a caller should not make any assumptions regarding the state of
    pointer parameters if the function returns with an error.

Requirement 2 (correct alignment) is in line with Unsafe Rust's normal expectations: unaligned pointers are very special in Rust, and may only be used when one has special permission to do so (here, the answer is "never in the public API"). So we don't need to do anything about it.

Requirement 3 (no NULLs unless given permission) is also common in Unsafe Rust. If we wanted to encode the non-nullness requirement in the type system, we could use NonNulls (which are repr(transparent) and therefore ABI-compatible with C pointers), but a lot of unsafe Rust APIs don't bother and we probably don't need to either.

Requirement 1 (only physical memory, no MMIO and such) is where things become more interesting. The concern is very specific to low-level code, and this is not a normal expectation of an unsafe Rust API. It is also prohibitively expensive to check in software (you basically need to take a memory map and walk through it). Therefore, it could be a contract which we want to expose in the FFI, or in unsafe APIs that consume pointers like memmove/memset. Since every UEFI entry point is concerned by this contract, a way to move it into the API would be to take inspiration from NonNull and build our own wrapper type (e.g. "EfiPtr") which encodes this requirement.

Finally, requirement 4 (EFI may freely garble pointer parameters on error) is I think the most interesting from the perspective of Rust's safety guarantees. A pessimistic interpretation of this sentence would mean that every EFI function is unsafe with an unknown contract, since an out-of-bounds pointer access can corrupt everything, and so the only thing to do on error would be to abort immediately. Not something very pleasant to build upon. But if we disregard the spec's advice and make the IMO reasonable assumption that all invalid pointer accesses during erronerous execution will be in bounds, then it becomes something that we can build safe APIs upon. The only thing we need to take care of is that any function which takes as input a mutable pointer parameter to a type which has safety invariants must be kept unsafe with a "may corrupt input on error" contract.

I'll try to see if I can encode these requirements in the unsafe APIs and check the safe APIs against them somehow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions