zig icon indicating copy to clipboard operation
zig copied to clipboard

coercion of mutable pointers' child type causes a soundness issue

Open Fri3dNstuff opened this issue 1 year ago • 1 comments

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:

  • T and U are in-memory coercible, and
  • every valid bit-pattern of the only is a valid bit-pattern of the other.

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.

Fri3dNstuff avatar Jul 22 '24 14:07 Fri3dNstuff

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.

rohlem avatar Jul 22 '24 21:07 rohlem

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:

  • a mutable reference is invariant on its child type
  • a const reference is covariant on its child type
  • a write-only reference (though such type does not exist in Zig...) is contravariant on its child type

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

Fri3dNstuff avatar Sep 22 '24 08:09 Fri3dNstuff

fixed by #22243

Fri3dNstuff avatar Dec 20 '24 07:12 Fri3dNstuff