microsoft / microsoft/litebox

RFC: "Raw" pointer improvements

Open
#505 3 comments 0 reactions 0 assignees View on GitHub
discussion layer-litebox
Dominant language
Rust
Stars
2.7k
Forks
144
Avg merge
12h 21m
Merged PRs (30d)
146

Description

LiteBox has evolved such that "raw" pointers essentially mean "guest"/"user" pointers--they represent untrusted addresses under user control, outside of the Rust memory model. Although one can imagine uses of LiteBox where the pointers _can_ be trusted, no one writing generic shim code can _assume_ this.

As such, there are several challenges with the current design:

## Safety

Many of the pointer trait methods are `unsafe`, but the safety contract is unclear and/or unenforceable. These pointers are inherently untrusted--they come from code on the other side of the security barrier (the Linux process, the OPTEE TA).

And there is no mechanism that the shim could use to validate a address ahead of time, before constructing a pointer, since in most cases this would be a TOCTOU bug--the user process can change the page mappings between pointer validation and pointer use. The best we can do is validate that the address is outside of the kernel address range... but we might as well defer that to access time if we're going to perform the check at all (on some architectures, it can be combined with the access).

We should fix this so that all trait methods on raw pointers are safe, relying on the platform to ensure this. For userland platforms, where this is not practical and there is no security boundary, this will have to be best-effort.

### COW

The pointer trait methods provide ways to get borrowed `Cow` references to the user data but, as discussed above, this is always unsafe and undefined behavior--guest memory lives outside the Rust memory model. We should eliminate this altogether.

### Safe transmutability

Currently, the pointer trait access methods do not require that the underlying data can be safely transmuted to/from bytes--it's up to the caller to not read an `Arc` or `&'static Platform` from guest memory. We can leverage the `zerocopy` crate to provide useful bounds while still allowing safe access of user types.

### Atomic accesses

Some code (currently, just the futex manager) needs a guarantee that a given memory access is performed atomically. There is no mechanism for this today, and so code that has this requirement currently transmutes the address to an `&AtomicU32` and hopes for the best.

## Ergonomics

The current pointers are difficult to use correctly for some use cases.

* There are multiple overlapping methods to read/write data, but no simple `read`/`write` methods for the common case where you just want to access the pointed-to data.
* It is difficult to cast a pointer between types or to construct a pointer that is offset from another one. You can cast through `usize`, but the traits warn you not to do this.
* Accessing a slice of guest memory requires extra care since you are responsible for all bounds checks. There's no mechanism to create a "raw slice".
* Because LiteBox code uses the platform pointer objects directly, it can be awkward to describe the pointer type in generic code. See uses of `IoReadVec` for an example.

# Proposal

To address all these, we should split the guest pointer infrastructure into two parts:

1. A simple `RawPointer` trait implemented by the platform to provide low-level memory access in a safe way.
2. Rich `XxxPointer` types provided by `litebox` that the shims use to actually access memory.

The low-level trait would look something like:

```rust
trait RawPointer {
// Safe if `T` is safe to read. Guaranteed to be atomic if `T` is 1,2,4,8 byes and aligned.
unsafe fn read(self) -> Result;
// Safe if `T` is safe to write.
unsafe fn write_ref(self, v: &T) -> Result<(), Fault>;
// Always safe.
fn read_bytes(self, dst: &mut [u8]) -> Result<(), Fault>;
fn write_bytes(self, src: &[u8]) -> Result<(), Fault>;
fn from_addr(_: usize) -> Self;
fn addr(self) -> usize;
fn offset(self, offset: isize) -> Self;
}
```

And the high-level objects would look like:

```rust
struct MutPointer(Platform::RawPointer, PhantomData<*mut T>);

impl MutPointer {
fn read(self) -> Result where T: zerocopy::FromBytes { ... }
fn write(self, v: T) -> Result<(), Fault> where T: zerocopy::IntoBytes {...}
fn from_addr(_: usize) -> Self {...}
fn offset(self, count: isize) -> Self {...}
}

impl MutPointer<[T]> {
fn from_addr_and_len(addr: usize, len: usize) -> Self {...}
fn copy_from_slice(self, data: &[T]) -> Result<(), Fault> where T: zerocopy::FromBytes {...}
fn copy_into_slice(self, buf: &mut [T]) -> Result<(), Fault> where T: zerocopy::IntoBytes {...}
fn len(self) -> usize;
fn get(self, range: I) -> Option {...}
}

impl MutPointer {
fn cast_const(self) -> ConstPointer {...}
fn cast(self) -> MutPointer {...}
fn addr(self) -> usize {...}
}
```

This approach clearly delineates the responsibilities between the platform and LiteBox:

* The platform is responsible for ensuring the correctness, safety, and performance of the raw operations, e.g., ensuring that the memory accesses are really to guest memory, that aligned accesses are atomic, etc.
* LiteBox is responsible for ensuring Rust-level safety (adding in the `zerocopy` bounds, for example) and ergonomics (generic for avoiding pointer confusion, support for slice types, etc.).

Contributor guide

Open the contributing guide

Research direction

No implementation files or tests are named. Start by tracing the existing platform pointer traits and the uses of IoReadVec, then map how guest-memory access is divided between platform code and LiteBox. Done would require an agreed design and implementation covering the proposed RawPointer and high-level pointer responsibilities, including zerocopy bounds and atomic access guarantees.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
operating-systems, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.