|
|
Subscribe / Log in / New account

Trusted slice lengths

Trusted slice lengths

Posted Sep 4, 2025 20:14 UTC (Thu) by NYKevin (subscriber, #129325)
In reply to: Trusted slice lengths by matthias
Parent article: Tracking trust with Rust in the kernel

> If you want to imply that the length is not trusted, this would be sth. like Untrusted<&mut [u8]>. This would be indeed very strange as not only the length would now be untrusted but also the pointer and the associated lifetime.

The lifetime parameter is indeed trusted, because the borrow checker "lifts" it out of the type parameter and treats it roughly the same as a top-level lifetime parameter (i.e. if you have Untrusted<&'a T>, then Rust automatically infers that the Untrusted object must not outlive 'a, because &'a T must not outlive 'a).

Technically, it's more complicated than that, and the precise behavior depends on the fields of Untrusted, see [1] for details. But in short, Rust will automatically infer relationships between Untrusted<&'a T> and &'a T as appropriate to prevent you from using a reference whose lifetime has ended, and at no point does it consider the possibility that &'a T points to a T object that fails to live for at least 'a. Even if &'a T only appears in PhantomData, a relationship is still inferred (Rust interprets this as "I logically own some &'a T even though you can't see it listed in my fields").

But it gets even worse. If you ever expose a real &'a T to safe code (i.e. code that does not live inside the implementation of Untrusted or its owning crate), and it points into userspace-controlled memory, then you have probably violated soundness in multiple different ways:

* If userspace modifies the T while the reference exists (e.g. from another thread), and T is not UnsafeCell<U>, then the behavior is undefined. If T is UnsafeCell, then you have to turn it into a raw pointer (*mut U) via UnsafeCell::get() to do anything anyway. We can't have e.g. T = Mutex<U>, because then the whole Mutex object lives in userspace, and userspace is not beholden to Rust's safety rules (so it could modify the inner value without taking the lock, or even stomp on the futex and break everything).
* Safe Rust assumes that you can dereference &'a T at any time during 'a, and that it will always succeed and produce a T instance. In particular, the dereference operation is not allowed to fail with EFAULT or the like, so that would have to panic (obviously an unacceptable response to "userspace gave me a bad pointer").
* The T instance must be initialized unless T is MaybeUninit<U> (or some equivalent, since unlike UnsafeCell, MaybeUninit is not magic and can be implemented by hand). I have no idea how that rule even applies when you're pointing into shared memory, but I think it doesn't have a good answer because...
* Safe references are never volatile, so it's pretty much always incorrect to produce or dereference a safe reference pointing into shared memory. This is the case even if you fully serialize all reads and writes with some external locking, because LLVM (or GCC etc.) is permitted to optimize under the assumption that nobody else is reaching into your memory and modifying it behind your back (if the pointer escapes, then the compiler probably has to pessimistically assume that foreign code might fiddle with it, but that does not mean it is valid to do this in general). The "correct" way to do it is with functions like read_volatile[2], but those all take raw pointers, not safe references.

On the other side of things, once you have all of the necessary guardrails around a userspace pointer, there is not much point in giving it a lifetime parameter. You're already (by assumption) calling fallible functions like copy_from_user() or get_user() (or their Rust equivalents) instead of directly dereferencing an address. You already have runtime error checking, and cannot violate soundness by holding onto the pointer for too long. Static borrow checking would provide no benefit, and might as well be disabled for ergonomic reasons.

This is one of (probably) several reasons why UserPtr<T> has to exist and can't be spelled as Untrusted<&'a T> or any similar signature (note the lack of a lifetime).

[1]: https://doc.rust-lang.org/reference/subtyping.html#variance
[2]: https://doc.rust-lang.org/std/ptr/fn.read_volatile.html


to post comments


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds