ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.92k stars 2.55k forks source link

coercion of mutable pointers' child type causes a soundness issue #20738

Open Fri3dNstuff opened 3 months ago

Fri3dNstuff commented 3 months ago

Zig Version

0.14.0-dev.367+a57479afc

Steps to Reproduce and Observed Behavior

Attempt to compile the following code:

pub fn main() void {
    const n: u8 align(2) = 42;
    const m: u8 align(1) = 0;

    var t: *align(2) const u8 = &n;
    var u: *align(1) const u8 = &m;
    _ = .{ &t, &u };

    const pt: **align(2) const u8 = &t;
    const pu: **align(1) const u8 = pt; // this coercion is bad!

    pu.* = u;
    // oh no! we've stored an align(1) value in a
    // location that expects to hold an align(2) value
}

The code compiles without errors.

Expected Behavior

The code should have triggered a compilation error.

A mutable pointer to (one / many / slice / C) a type T can coerce into a mutable pointer of type U without soundness problems if-and-only-if:

These are, I believe, the most liberal coercion rules that do not result in soundness problems - I will leave the discussion about whether Zig should be this liberal with its coercion rules to another issue.

rohlem commented 3 months ago

It's already noted in the issue text, but important to note that coercion of pointers-to-const *const *align(2) T -> *const *align(1) T can be allowed. We only need to prevent it when the target pointer-to-mutable broadens the set of valid pointee values.

Fri3dNstuff commented 1 month ago

I just stumbled upon another manifestation of the issue, now with sentinel-terminated types:

test {
    var x: [*:0]const u8 = "abc";
    const good_ref: *[*:0]const u8 = &x;
    const evil_ref: *[*]const u8 = good_ref; // this coercion is bad!

    evil_ref.* = @as([*]const u8, &.{ 'a', 'b', 'c' });
    // oops! `x` is now storing a
    // non-sentinel-terminated pointer!
}

this is the classic unsoundness of covariance of mutable references. to lay out the sound conversions:

in Zig we also care that the coercion is possible in-memory (i.e. we just reinterpret bits), and that mutable references to T can be cast into mutable references to U and back if T and U are in-memory coercible into each other