Re: C aggregate passing (Rust kernel policy)

Benno Lossin
2025-02-24T22:03:15Z

On 24.02.25 17:57, Ventura Jack wrote:
> On Mon, Feb 24, 2025 at 5:47 AM Benno Lossin <benno.lossin@proton.me> wrote:
>>
>> On 24.02.25 13:21, Ventura Jack wrote:
>>>
>>> From what I can see in the documentation, `&UnsafeCell<T>` also does not
>>> behave like `T*` in C. In C, especially if "strict aliasing" is turned
>>> off in the
>>> compiler, `T*` does not have aliasing requirements. You can have multiple
>>> C `T*` pointers pointing to the same object, and mutate the same object.
>>
>> This is true for `&UnsafeCell<T>`. You can have multiple of those and
>> mutate the same value via only shared references. Note that
>> `UnsafeCell<T>` is `!Sync`, so it cannot be shared across threads, so
>> all of those shared references have to be on the same thread. (there is
>> the `SyncUnsafeCell<T>` type that is `Sync`, so it does allow for
>> across-thread mutations, but that is much more of a footgun, since you
>> still have to synchronize the writes/reads)
>>
>>> The documentation for `UnsafeCell` conversely spends a lot of space
>>> discussing invariants and aliasing requirements.
>>
>> Yes, since normally in Rust, you can either have exactly one mutable
>> reference, or several shared references (which cannot be used to mutate
>> a value). `UnsafeCell<T>` is essentially a low-level primitive that can
>> only be used with `unsafe` to build for example a mutex.
>>
>>> I do not understand why you claim:
>>>
>>> "`&UnsafeCell<T>` behaves like `T*` in C,"
>>>
>>> That statement is false as far as I can figure out, though I have taken it
>>> out of context here.
>>
>> Not sure how you arrived at that conclusion, the following code is legal
>> and sound Rust:
>>
>> let val = UnsafeCell::new(42);
>> let x = &val;
>> let y = &val;
>> unsafe {
>> *x.get() = 0;
>> *y.get() = 42;
>> *x.get() = 24;
>> }
>>
>> You can't do this with `&mut i32`.
>
> I think I see what you mean. The specific Rust "const reference"
> `&UnsafeCell<T>` sort of behaves like C `T*`. But you have to get a
> Rust "mutable raw pointer" `*mut T` when working with it using
> `UnsafeCell::get()`.

Exactly, you always have to use a raw pointer (as a reference would
immediately run into the aliasing issue), but while writing to the same
memory location, another `&UnsafeCell<T>` may still exist.

> And you have to be careful with lifetimes if you
> do any casts or share it or certain other things. And to dereference a
> Rust "mutable raw pointer", you must use unsafe Rust. And you have to
> understand aliasing.

Yes.

> One example I tested against MIRI:
>
> use std::cell::UnsafeCell;
>
> fn main() {
>
> let val: UnsafeCell<i32> = UnsafeCell::new(42);
> let x: & UnsafeCell<i32> = &val;
> let y: & UnsafeCell<i32> = &val;
>
> unsafe {
>
> // UB.
> //let pz: & i32 = & *val.get();
>
> // UB.
> //let pz: &mut i32 = &mut *val.get();
>
> // Okay.
> //let pz: *const i32 = &raw const *val.get();
>
> // Okay.
> let pz: *mut i32 = &raw mut *val.get();
>
> let px: *mut i32 = x.get();
> let py: *mut i32 = y.get();
>
> *px = 0;
> *py += 42;
> *px += 24;
>
> println!("x, y, z: {}, {}, {}", *px, *py, *pz);
> }
> }
>
> It makes sense that the Rust "raw pointers" `*const i32` and `*mut
> i32` are fine here, and that taking Rust "references" `& i32` and
> `&mut i32` causes UB, since Rust "references" have aliasing rules that
> must be followed.

So it depends on what exactly you do, since if you just uncomment one of
the "UB" lines, the variable never gets used and thus no actual UB
happens. But if you were to do this:

let x = UnsafeCell::new(42);
let y = unsafe { &mut *x.get() };
let z = unsafe { &*x.get() };
println!("{z}");
*y = 0;
println!("{z}");

Then you have UB, since the value that `z` points at changed (this is
obviously not allowed for shared references [^1]).

[^1]: Except of course values that lie behind `UnsafeCell` inside of the
value. For example:

struct Foo {
a: i32,
b: UnsafeCell<i32>,
}

when you have a `&Foo`, you can be sure that the value of `a`
stays the same, but the value of `b` might change during the
lifetime of that reference.

---
Cheers,
Benno