-
-
Notifications
You must be signed in to change notification settings - Fork 770
Update TRD104 to more clearly state the expectations for allowed buffers. #2617
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
Conversation
…104-buffer-reading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these changes look good! One minor question though. Never mind, turns out it makes sense! 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, I have one (optional) suggestion
Leon asked me a question that made me realize that I have not fully thought through The ChallengeTo start, consider what a naive Allow API would look like in fn allow_ro_static(driver_id: u32, buffer_id: u32, new_buffer: &'static [u8])
-> Result<&'static [u8], (ErrorCode, &'static [u8])>;
fn allow_rw_static(driver_id: u32, buffer_id: u32, new_buffer: &'static mut [u8])
-> Result<&'static mut [u8], (ErrorCode, &'static mut [u8])>;
impl TakeCell {
// Retrieves a mutable reference if no other reference exists.
fn take_mut(&self) -> Option<&'static mut [u8]>;
// Returns the mutable reference.
fn return_mut(&self, val: &'static mut [u8]);
// Retrieves a shared reference if no other reference exists.
fn take_ref(&self) -> Option<&'static [u8]>;
// Returns the shared reference
fn return_ref(&self, val: &'static [u8]);
}
fn main() {
let mut variable = [0];
let take_cell = TakeCell::new(&mut variable);
let ref1: &[u8] = take_cell.take_ref().unwrap();
let ref2: &[u8] = ref1; // Copies the reference.
take_cell.return_ref(ref1);
let mut_ref: &mut [u8] = take_cell.take_mut().unwrap();
// mut_ref and ref2 are both alive here, which is undefined behavior
} The problem is that One apparent solution is to have fn allow_ro_static(driver_id: u32, buffer_id: u32, new_buffer: &'static mut [u8])
-> Result<&'static mut [u8], (ErrorCode, &'static mut [u8])>;
fn allow_rw_static(driver_id: u32, buffer_id: u32, new_buffer: &'static mut [u8])
-> Result<&'static mut [u8], (ErrorCode, &'static mut [u8])>; but now Idea: Move-Only Immutable ReferenceInstead of using struct AllowRef { slice: core::ptr::NonNull<[u8]> }
impl std::ops::Deref for AllowRef {
type Target = [u8];
fn deref(&self) -> &[u8] { /* omitted */ }
} Then we make Read-Only Allow use fn allow_ro_static(driver_id: u32, buffer_id: u32, new_buffer: AllowRef)
-> Result<AllowRef, (ErrorCode, AllowRef)>;
fn allow_rw_static(driver_id: u32, buffer_id: u32, new_buffer: &'static mut [u8])
-> Result<&'static mut [u8], (ErrorCode, &'static mut [u8])>; Then we need two struct RoAllowBuffer<const LEN: usize> { buffer: [u8; LEN] }
impl<const LEN: usize> RoAllowBuffer<LEN> {
// Does not need to perform runtime checks, as there can't be unique references to the buffer
pub fn get(&self) -> AllowRef { /* omitted */ }
}
struct RwAllowBuffer<const LEN: usize> {
borrowed: Cell<bool>,
buffer: UnsafeCell<[u8; LEN]>,
}
impl<const LEN: usize> RwAllowBuffer<LEN> {
// Returns a reference if not already borrowed.
pub fn get_mut(&self) -> Option<&'static mut [u8]> { /* omitted */ }
// Undoes the borrow. Must check that buffer == self.buffer at runtime.
pub fn return_mut(&self, buffer: &'static mut [u8]) { /* omitted */ }
// Returns a read-only reference if not already borrowed.
pub fn get(&self) -> Option<AllowRef> { /* omitted */ }
// Undoes the borrow. Must check that buffer == self.buffer at runtime.
pub fn return_ref(&self, buffer: AllowRef) { /* omitted */ }
} As an alternative, we could modify Other options?Every other sound Allow API I can think of has considerably more overhead, so I think this is what For @phil-levis and @lschuermann: I do not think this impacts the TRD. :-) |
3a40f00
multiple times and it's up to the kernel to make sure this doesn't cause a problem.
OK, I have rewritten the text so that it does not require the kernel to return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me.
bors r+ |
Awesome! As documented in the TRD, the kernel's AppSlice infrastructure as of now does not match the specification. I'll create a PR proposal with the slice-of-cells based mechanism @hudson-ayers and I worked on, as proposed on the |
As of 8f66d98 ("Merge #2617"), it is explicitly permitted for Tock applications to Allow overlapping memory regions to multiple Allow slots in both the same and different capsules simultaneously. This has the implication of making the current interface for accessing process memory (`AppSlice`) unsound. This commit rewrites the interface for interacting with process memory, such that it is valid for the kernel to have multiple process buffers which can be both writeable and overlapping. This is achieved by developing an interface around `&[Cell<u8>]` (slice of Cells), which does permit interior mutability due to the usage of `UnsafeCell<u8>`. It further adapts a consistent naming scheme: - App -> Process, as potentially multiple processes of the same application can run simultaneously. - AppSlice -> ProcessBuf, as this is not really a slice, but rather represents a bounded memory region (buffer) within some process memory. - introduction of new `ProcessSlice`s, which are `#[repr(transparent)]` wrappers around slices of Cells, providing convenient APIs and, in the case of read-only Allowed memory, limiting to read-only accesses. - introduction of a `ReadableProcessByte`, which is a `#[repr(transparent)]` wrapper around a `Cell<u8>`, limiting to read-only accesses. In general, this manages to provide a convenient interface for accessing userspace memory, while directly operating on Rust slices with their inherent aliasing restrictions. However, the `ProcessSlices` obtained are incompatible with regular Rust slices, which sometimes makes copying over an intermediate buffer necessary. Usually, Rust slices only feature a `copy_from_slice` method, taking a mutable `&mut self` reference. However, given that a Rust slice cannot copy from a `ProcessSlice`, these feature an inverse method `copy_to_slice`, which can write the contents to a regular Rust slice. Signed-off-by: Leon Schuermann <[email protected]> Co-authored-by: Pat Pannuto <[email protected]> Co-authored-by: Brad Campbell <[email protected]>
As of 8f66d98 ("Merge tock#2617"), it is explicitly permitted for Tock applications to Allow overlapping memory regions to multiple Allow slots in both the same and different capsules simultaneously. This has the implication of making the current interface for accessing process memory (`AppSlice`) unsound. This commit rewrites the interface for interacting with process memory, such that it is valid for the kernel to have multiple process buffers which can be both writeable and overlapping. This is achieved by developing an interface around `&[Cell<u8>]` (slice of Cells), which does permit interior mutability due to the usage of `UnsafeCell<u8>`. It further adapts a consistent naming scheme: - App -> Process, as potentially multiple processes of the same application can run simultaneously. - AppSlice -> ProcessBuffer, as this is not really a slice, but rather represents a bounded memory region (buffer) within some process memory. - introduction of new `ProcessSlice`s, which are `#[repr(transparent)]` wrappers around slices of Cells, providing convenient APIs and, in the case of read-only Allowed memory, limiting to read-only accesses. - introduction of a `ReadableProcessByte`, which is a `#[repr(transparent)]` wrapper around a `Cell<u8>`, limiting to read-only accesses. In general, this manages to provide a convenient interface for accessing userspace memory, while directly operating on Rust slices with their inherent aliasing restrictions. However, the `ProcessSlices` obtained are incompatible with regular Rust slices, which sometimes makes copying over an intermediate buffer necessary. Usually, Rust slices only feature a `copy_from_slice` method, taking a mutable `&mut self` reference. However, given that a Rust slice cannot copy from a `ProcessSlice`, these feature an inverse method `copy_to_slice`, which can write the contents to a regular Rust slice. Signed-off-by: Leon Schuermann <[email protected]> Co-authored-by: Pat Pannuto <[email protected]> Co-authored-by: Brad Campbell <[email protected]>
Pull Request Overview
This pull request grew out of #2590.
It does a bunch of cleanup on TRD104. The most notable change is a clearer definition of the expectations on allowed buffers. The prior text said that userspace cannot access a shared buffer. These changes update that text to be clearer and also state the assumptions that the kernel can make. Specifically:
The first wording captures the intent. We cannot state that userspace MUST NOT access the buffer because there is no reasonable way to check or enforce this either through software or code review: a userspace bug could cause it to inadvertently access a buffer. The point is not that userspace APIs should not be designed such that they expect or use concurrent access. Therefore, if we in the future use an MPU to protect an allowed buffer we won't break userspace APIs.
The second wording captures that buffers can disappear unexpectedly. While userspace shouldn't be touching shared buffers, those buffers can change at any time. Userspace might revoke the buffer through another allow, or the process might exit (removing access to the buffer).
After a lot of discussion back and forth about concurrent kernel/user access to allowed buffers, we concluded this should be a different system call, which we will specify in a separate document.
Testing Strategy
This pull request was tested by a huge number of discussions and debates.
TODO or Help Wanted
This pull request still needs lots more careful reads.
Documentation Updated
/docs
, or no updates are required.Formatting
make prepush
.