### Direct Pointer Accesses
With `core::ptr::{read,write,copy, ...}`, Rust provides functions to
access values behind raw pointers, without ever obtaining a proper
Rust reference. If the soundness of these methods is not affected by
aliasing of the memory regions in question (`core::ptr::copy` is even
explicitly designed to work on overlapping memory regions), they could
be a viable alternative to using volatile reads/writes. Presumably,
none of the Rust reference aliasing rules would be violated, given
that no references to the userspace buffers are constructed at any
time.
Compared to the current interface, both of these approaches would have
the significant disadvantage of not being able to obtain a Rust slice
from an `AppSlice`, that can be passed down to lower layers. Instead,
all buffer operations would need to go through a custom interface,
offering methods such as
- `AppSlice::copy_from_slice(&mut self, slice: &[u8])`
- `AppSlice::copy_to_slice(&mut self, slice: &mut [u8])`
- `AppSlice::copy_from_app_slice(&mut self, other: &AppSlice)`
It might be possible to implement the `Index` operator for convenience
and sub-slicing, but full compatibility with Rust slices can not be
achieved.
--
You received this message because you are subscribed to the Google Groups "Tock Embedded OS Development Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tock-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/CAJqTQ1hv7y0i3WbVF0jLsT5A0VNmuTzkM6S8zCzbztE_7mHiFQ%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/CADEg7HnDFPu8_osovtCgmi%3DBR9BGcRCro%2BJS1anzns%2BeFQgjcw%40mail.gmail.com.
> Cell<T> is transparent, so the transmute should be fine (as long as the caller is aware the kernel is entitled to do this and also only uses a Cell<T>; given the user process is subordinate to the kernel I don't see an issue here).
What if the user process is written in C?
--
You received this message because you are subscribed to a topic in the Google Groups "Tock Embedded OS Development Discussion" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/tock-dev/cY0-eKc6aos/unsubscribe.
To unsubscribe from this group and all its topics, send an email to tock-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/05fb1745-4dbc-8c7c-9d97-c62399112a90%40amitlevy.com.
On May 11, 2021, at 8:59 AM, Brad Campbell <bra...@gmail.com> wrote:It seems there are two key questions to be answered:
- Is having overlapping `allow`ed buffers a desirable feature, regardless of any rust safety considerations?
- What does "significant" mean in terms of what level of per-allow-call overhead would be acceptable?
I don't have a use case for #1 in mind. I would think that any capsule that wants to do something complicated with memory could have a process allow a (large) RW appslice and manage it internally.
> As for same buffer used each time - what if this buffer would be allowed to another driver in the middle?
I don't think this is a problem if we use the `&[Cell<u8>]` version. It's perfectly fine (type-safety-wise) for multiple capsules to have a handle on overlapping process memory of that type.
> I'd just accept possibility of unsoundness, document it and use for benefits when possible.
As Johnathan says, this is unacceptable for a number of reasons, including Tock's security posture. Opening yourself up to miscompilation means that any pain and suffering gone through to use Rust is lost, and we may well have written C. And as anyone who has been on the receiving end of one of my code reviews knows, not even C is that easy. =)
Not to pile on, but unsoundness in the kernel at the system-call boundary is a non-starter (I mean, we have that now with allow, but we have to fix it).
In particular, a process cannot be allowed to interact with the kernel in such a way that could result in unsafe behavior, even if achieving unsafe behavior would require coordinating with a capsule.
Said another way, once the kernel crate has created Rust structs based on system call arguments, it must be the case that these constructs do not violate Rust guarantees and, thus, cannot invoke undefined behavior. To the extent this is not currently enforced, it is a bug and should be fixed.
Aside: the same restrictions do not apply to processes. Whatever
kernel interface is provided should allow a processes to
use internally safe language constructs, but that is not as much
of a requirement---e.g., there might need to be a library layer
within a process that enforces additional requirements on the
kernel system call boundary to ensure process safety. (I
don't anticipate this being an issue in this case, though)
--
You received this message because you are subscribed to the Google Groups "Tock Embedded OS Development Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tock-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/865ebe55-9001-4d53-7c0e-67146a7ad240%40amitlevy.com.
Another aside on:
Beyond run-time check nothing will work since you can circumvent everything with unsafe code.
I think it's worth re-iterating Tock's trust model here, so we're all on the same page.
## Who shouldn't be trusted for memory safety:
Neither processes nor capsules are trusted for memory safety (capsules are trusted for liveness, e.g. they can block system progress with a `loop {}`).
Processes are "easy" as memory safety is enforced in a similar way as is enforced in most operating systems: through dynamic hardware enforcement of memory accesses (of course most OSs use virtual memory while Tock primarily relies on memory protection of a single address space, but the spirit is the same).
Restricting capsule's use of memory relies on the Rust type-system. Broadly, as long as the Rust type systems semantics are preserved, capsules can only access memory/values they are explicitly granted access to (via references, local variables, etc), through narrow interfaces (e.g. traits or other types). Importantly, capsules are restricted, by the compiler, from using the `unsafe` keyword. In upstream capsules this is enforced with the `#![forbid(unsafe)]` directive at the top of `capsules/src/lib.rs`.
## What does this mean for construct safe abstractions within the kernel?
"You can circumvent everything with unsafe code" is, of course, true, except we restrict who can use unsafe code. It is, therefore, both safety critical, and performance beneficial, to ensure that abstractions provided to capsules by the core kernel adhere to Rust's safety semantics---for example, that they don't exercise undefined behavior.
It's critical for safety because even hand-coded runtime checks might be incorrectly elided in the presence of undefined behavior. This isn't specific to Rust, and is an endemic problem in C ([1] is just one of many examples). If there is undefined behavior, all bets are off, especially as compilers become more aggressive (which is of course important for performance).
It's beneficial to performance because dynamic checks are expensive. System calls in Tock are already fairly expensive because the security model, and the fact that process are not necessarily type-safe, requires us to perform many dynamic checks (undoubtedly there is room for optimizations, but at least some large portion of the overhead is fundamental).
The more we can enforce statically, without relying on runtime checks, the better. Certainly in some cases there is an argument for defense in depth (so static enforcement shouldn't necessarily preclude dynamic checks).
In any case, this rant is an aside to the discussion of the
options for preserving safety in this case, and should not be
taken as an argument in favor of one of the options Leon laid out
vs another.
[1]:
https://www.usenix.org/system/files/conference/osdi12/osdi12-final-88.pdf
--
You received this message because you are subscribed to the Google Groups "Tock Embedded OS Development Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tock-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/f12d1a37-6bfe-0966-261f-a9a6eae55fdf%40amitlevy.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/5369DEA2-E739-429C-9025-38F233D1257F%40cs.stanford.edu.
On May 10, 2021, at 7:38 AM, Leon Schuermann <le...@is.currently.online> wrote:1. Perform runtime-checks to ensure that no two userspace buffers can
overlap.
This might have a significant performance & resource overhead, as
presumably a central data structure must keep information about all
buffers already shared by userspace. Furthermore, every `allow`
operation (sharing a new memory region) must walk over the entire
list to check for an overlapping region. In a sorted data structure,
it might be possible to perform a binary search instead.
On May 16, 2021, at 5:18 PM, Amit Levy <am...@amitlevy.com> wrote:Not a comprehensive response, but FYI, you should be able to get that without assembly using `u32#leading_zeros` (https://doc.rust-lang.org/std/primitive.u32.html#method.leading_zeros) or `u32#trailing_zeros` (or the same methods for any other primitive integer type). It's implemented in the compiler but I _believe_ it uses arch specific instructions, such as `clz`, when possible.
--
You received this message because you are subscribed to the Google Groups "Tock Embedded OS Development Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tock-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/1918DE0D-8C42-42CC-BE78-B5D35D40B5D0%40cs.stanford.edu.
map_or
and other similar functions are more commonly used, and prevent a better, less error-prone API (with take() it is easy to forget to replace the buffer once you get to more
complex state machines). Of course, we could do the check on map_or()
/
mut_map_or()
etc. as well, as those methods are already fallible. I think we want to avoid a design which creates another opportunity for apps to panic the kernel, but given that accessing an AppSlice can already fail if the process does not exist
it seems fine to fail in the case an AppSlice overlaps. On May 16, 2021, at 7:58 PM, 'Vadim Sukhomlinov' via Tock Embedded OS Development Discussion <tock...@googlegroups.com> wrote:
To reduce overhead per application lists can be used, so inserts will be faster due to fewer items to look at. Maybe simple inserts in an array with moving elements would be faster for the scale of the problem (i'm not sure what self.insert_into(start, end) implementation is though, so please disregard if n/a.
On May 21, 2021, at 8:52 AM, Hudson Randal Ayers <hay...@stanford.edu> wrote:> FWIW, TRD 104 says that you can’t pass overlapping buffers and that you have to return INVALID if they are. 4.4:My understanding was that TRD 104 was written that way because we assumed it to be necessary for soundness in the kernel. Given that no longer seems to be the case, I think that is something acceptable to revisit before actually releasing Tock 2.0
> “When userspace shares a buffer, it can no longer access it.This has been mentioned elsewhere by Brad, but this requirement is unenforceable by the kernel on most devices currently supported by Tock (not enough MPU regions / granularity) and as a result is just untrue. We could change it to "should no longer access it in order to ensure correct operation", perhaps.
On May 10, 2021, at 4:31 PM, Vadim Sukhomlinov <sukho...@google.com> wrote:Overlapping mutable references are quite useful for crypto - say output can be in any location, but the same location as one of the sources can be used to save memory assuming correct implementation where source data is read before destination is modified.In Rust asm it is specified 'inlateout', though used in different contexts. Above use is common in C code, but Rust explicitly prohibits it (can't borrow shared reference while borrowing it as mutable). This requires additional Rust wrappers around C code to handle this case, where only one mutable reference exists, but passed as a pointer twice to C code. But I'm not sure if it is a good idea to support it in Tock.
--
You received this message because you are subscribed to the Google Groups "Tock Embedded OS Development Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tock-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tock-dev/87h7i9gxjq.fsf%40silicon.