Skip to content

addr_of[_mut]! docs should be more precise about what's sound #114902

Closed
@joshlf

Description

@joshlf

This example comes from this URLO thread, which in turn was inspired by this zerocopy issue.

The addr_of! and addr_of_mut! docs imply that they can handle unaligned pointers so long as a reference is never materialized from that pointer (which would be insta-UB). However, running the following program under Miri fails:

fn main() {
    #[repr(C, packed)]
    struct Unalign<T>(T);
    
    #[repr(C)]
    struct Foo {
        a: u8,
        b: u16,
    }
    
    // Has alignment `align_of::<T>()`, and the `Unalign<T>`
    // is at byte offset 1; so long as `align_of::<T>() > 1`,
    // the contained `T` is misaligned.
    #[repr(C)]
    struct Misalign<T>(u8, Unalign<T>, [T; 0]);
    
    let u = Misalign(0, Unalign(Foo{ a: 1, b: 2 }), []);
    let u_ptr: *const Unalign<Foo> = &u.1;
    // Sound because `Unalign` contains a `T` and nothing else.
    let f_ptr: *const Foo = u_ptr.cast();
    // Should be sound because we never construct a reference.
    let addr_of_b: *const u16 = unsafe { core::ptr::addr_of!((*f_ptr).b) };
    println!("{:?}", addr_of_b);
}

Here's the failure:

error: Undefined Behavior: accessing memory with alignment 1, but alignment 2 is required
  --> src/main.rs:22:42
   |
22 |     let addr_of_b: *const u16 = unsafe { core::ptr::addr_of!((*f_ptr).b) };
   |                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment 1, but alignment 2 is required
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: BACKTRACE:
   = note: inside `main` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:1980:5: 1980:22
   = note: this error originates in the macro `core::ptr::addr_of` (in Nightly builds, run with -Z macro-backtrace for more info)

@cuviper pointed out my issue:

The example shows an unaligned field (since it's packed), but that doesn't excuse everything. "Note, however, that the expr in addr_of!(expr) is still subject to all the usual rules." -- I think this would include alignment for (*f_ptr), although the docs only call out null pointers.

Miri is happy with addr_of!((*u_ptr).0.b).

The docs strongly imply that this would be sound. It does this in two ways. First, when contrasting references and raw pointers, it specifically calls out that references need to be aligned:

Creating a reference with &/&mut is only allowed if the pointer is properly aligned and points to initialized data. For cases where those requirements do not hold, raw pointers should be used instead.

Second, it seems to enumerate the cases in which addr_of! would be unsound, and operating on unaligned pointers isn't on the list:

Note, however, that the expr in addr_of!(expr) is still subject to all the usual rules. In particular, addr_of!(*ptr::null()) is Undefined Behavior because it dereferences a null pointer.

IMO the docs as currently written are both vague and misleading with respect to what's allowed. It would be good if the docs were more precise on exactly what is and isn't allowed. I was pretty confident in the soundness of the code I'd written, and only discovered the issue thanks to Miri.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.T-opsemRelevant to the opsem team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions