Enhance MmioAddress safety and fix AArch64 instruction emission#50
Enhance MmioAddress safety and fix AArch64 instruction emission#50phip1611 wants to merge 6 commits intorust-osdev:mainfrom
Conversation
mkroening
left a comment
There was a problem hiding this comment.
Looks good to me, generally. 👍
| /// # Safety | ||
| /// | ||
| /// This is `Copy` to enable convenient arithmetics on addresses. As this type | ||
| /// is private and not publicly exported, there can be no aliasing violations | ||
| /// from misuse of the API. | ||
| pub trait RegisterAddress: Copy + Clone + Debug + Sized + private::Sealed { |
There was a problem hiding this comment.
Both RegisterAddress and MmioAddress are public, though, no?
There was a problem hiding this comment.
I rephrased the comment.
/// All implementations and instances of this trait are created within this
/// crate and do follow all safety invariants. API users don't get access to the
/// underlying register addresses, nor can they construct one themselves, as this
/// type et al. are sealed.
Better?
There was a problem hiding this comment.
I think I understand it a bit better now, yes.
Now that I have taken a closer look at it, though, I think the interface is still problematic as is:
let ptr_max = ptr::with_exposed_provenance_mut(usize::MAX);
let Err(InvalidAddressError::InvalidBaseAddress(addr)) =
(unsafe { Uart16550::new_mmio(ptr_max, 1) })
else {
return;
};
// UB
addr.add_offset(10);Is there a reason for making RegisterAddress public instead of having MmioBackend::Address = NonNull<u8> and PioBackend::Address = u16?
Moreover, even if RegisterAccess were internal, add_offset would still be internally unsound if add_offset is not made unsafe or wrapping_add is used for MMIO addresses.
Lastly, why do new_mmio and new_port return MmioAddress and PortIoAddress on error instead of the provided argument or nothing at all since the arguments are Copy?
| base_address: *mut u8, | ||
| stride: u8, | ||
| ) -> Result<Self, InvalidAddressError<MmioAddress>> { | ||
| let base_address = NonNull::new(base_address).ok_or(InvalidAddressError::Null)?; |
There was a problem hiding this comment.
Perhaps it might make sense to put NonNull<u8> into the API instead of *mut u8. That way, this requirement is more explicit for users. Users who already have a NonNull can also use that directly instead of removing and recreating the type invariant.
There was a problem hiding this comment.
Hm, I see the value add. However, if one has to construct a NonNull, construction is more cumbersome.
Instead of passing new_mmio(0x1000 as *mut _) one has to write new_mmio(NonNull::new(0x1000 as *mut _).unwrap()).
I'm not sure if this additional boilerplate code is a win for users 🤔 what do you think?
There was a problem hiding this comment.
I would personally use the following:
let ptr = ptr::with_exposed_provenance_mut::<u8>(0x1000);
let ptr = NonNull::new(ptr).unwrap();
let uart = unsafe { Uart16550::new_mmio(ptr, 1) };I have also opened #52 about integer-to-pointer casts in general.
I can see why you would want to be able to do it in a single, unconvoluted line, but in my opinion, a lot is happening here, and being explicit helps readability and simplifies the API. I'd be fine with either one in the end, though.
There was a problem hiding this comment.
I didn't know the with_exposed_provenance_mut() helper - interesting
Enforce more safety rules in the type system.
Otherwise, VMMs won't be able to properly emulate MMIO writes. rust-lang/rust#131894
This PR improves the robustness of the MMIO backend and fixes a correctness issue on aarch64.
Safety hardening (
MmioAddress): Replace the internal*mut u8withNonNull<u8>, enforcing the non-null invariant at the type level rather than viadebug_assert. A dedicatedInvalidAddressError::Nullvariant makes null-pointer rejections explicit in the public API. There was no UB as none of this is public API, it just makes the internal API more robust.aarch64 fix:
read_volatile/write_volatilecan cause LLVM to emit post-increment addressing instructions (e.g.str w9, [x0], #4) that do not populateESR_EL2, preventing hypervisor MMIO emulation in protected VMs. The newmod archreplaces these with explicitldrb/strbinline assembly on aarch64, falling back toread/write_volatileon all other targets. See rust-lang/rust#131894.No user-visible behavior changes on x86. There was no UB in the previous code — these are robustness and correctness improvements.
This fixes #47.