Skip to content

incorrect UB when when a ! place is constructed (but not loaded!) #117288

Closed

Description

@RalfJung

This code shouldn't have UB but Miri reports UB and rustc generates a SIGILL:

#![feature(never_type)]
fn main() {
    unsafe {
        let x = 3u8;
        let x: *const ! = &x as *const u8 as *const _;
        let _ = *x;
    }
}

Thanks to @Nadrieril for finding this.

Zulip discussion

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Oct 27, 2023
  2. changed the title [-]Miri reports UB when when a `!` place is constructed (but not loaded!)[/-] [+]UB when when a `!` place is constructed (but not loaded!)[/+] on Oct 27, 2023
  3. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on Oct 27, 2023
  4. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Oct 27, 2023
  5. RalfJung commented on Oct 27, 2023

    @RalfJung
    MemberAuthor

    Labeling as "unsound" since under the rules that @rust-lang/opsem decided for *, and how we've been treating _ in general, this shouldn't SIGILL.

  6. Nadrieril commented on Oct 27, 2023

    @Nadrieril
    Member

    Note that this triggers for ! but not enum Void{} so it's likely a type-based special case

  7. RalfJung commented on Oct 27, 2023

    @RalfJung
    MemberAuthor

    This is already wrong in the initially generated MIR:

            _6 = &_2;
            _5 = &raw const (*_6);
            _4 = move _5 as *const ! (PtrToPtr);
            AscribeUserType(_4, o, UserTypeProjection { base: UserType(1), projs: [] });
            _3 = _4;
            StorageDead(_5);
            FakeRead(ForLet(None), _3);
            AscribeUserType(_3, o, UserTypeProjection { base: UserType(2), projs: [] });
            StorageDead(_6);
            StorageDead(_4);
            StorageLive(_7);
            StorageLive(_8);
            _8 = (*_3); // an actual load from the place!

    If we use bool instead we get just a PlaceMention, as we should

            _5 = &_1;
            _4 = &raw const (*_5);
            _3 = move _4 as *const bool (PtrToPtr);
            AscribeUserType(_3, o, UserTypeProjection { base: UserType(1), projs: [] });
            _2 = _3;
            StorageDead(_4);
            FakeRead(ForLet(None), _2);
            AscribeUserType(_2, o, UserTypeProjection { base: UserType(2), projs: [] });
            StorageDead(_5);
            StorageDead(_3);
            PlaceMention((*_2)); // no load

    @cjgillot any idea why we don't just get a PlaceMention for !?

  8. bjorn3 commented on Oct 27, 2023

    @bjorn3
    Member

    -Zunpretty=thir-tree shows a NeverToAny expression. Maybe this has something to do with it?

  9. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    T-opsemRelevant to the opsem team
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Oct 27, 2023
  10. Nadrieril commented on Oct 27, 2023

    @Nadrieril
    Member

    Oh, then this looks like fallback behavior: if I annotate let _: ! = *x; I only get a "entering unreachable code" error

  11. RalfJung commented on Oct 27, 2023

    @RalfJung
    MemberAuthor

    Yeah that's also wrong, but different.^^ MIR for that case:

            _6 = &_2;
            _5 = &raw const (*_6);
            _4 = move _5 as *const ! (PtrToPtr);
            AscribeUserType(_4, o, UserTypeProjection { base: UserType(1), projs: [] });
            _3 = _4;
            StorageDead(_5);
            FakeRead(ForLet(None), _3);
            AscribeUserType(_3, o, UserTypeProjection { base: UserType(2), projs: [] });
            StorageDead(_6);
            StorageDead(_4);
            PlaceMention((*_3));
            AscribeUserType((*_3), +, UserTypeProjection { base: UserType(4), projs: [] });
            StorageDead(_3);
            StorageDead(_2);
            unreachable;
    

    So we get a PlaceMention but then we also get a unreachable...

  12. RalfJung commented on Oct 28, 2023

    @RalfJung
    MemberAuthor

    -Zunpretty=thir-tree shows a NeverToAny expression. Maybe this has something to do with it?

    Yeah that's probably the right trail. MIR building might not even be at fault, the THIR is already wrong since it thinks there's a never value being constructed here. These THIR nodes seem to be constructed from Adjust::NeverToAny; no idea if there's any way to dump those.

    Who are the right people to ping for THIR issues...?

  13. changed the title [-]UB when when a `!` place is constructed (but not loaded!)[/-] [+]incorrect UB when when a `!` place is constructed (but not loaded!)[/+] on Oct 28, 2023
  14. 41 remaining items

  15. LunarLambda commented on Jun 27, 2024

    @LunarLambda

    No this bug is specific to !. Equivalent code works fine for empty enums

  16. RalfJung commented on Aug 21, 2024

    @RalfJung
    MemberAuthor

    This is now blocking #129248: making addr_of!(*ptr) a safe operation.

  17. compiler-errors commented on Aug 21, 2024

    @compiler-errors
    Contributor

    Well, I can put up a hack to unblock that specifically. As @Nadrieril pointed out, this is actually two different "bugs" in HIR typeck:

    #![feature(never_type)]
    fn main() {
        unsafe {
            let x = 3u8;
            let x: *const ! = &x as *const u8 as *const _;
            let _ /* (1.) */ = *x;
            /* (2.) */
        }
    }
    

    In the above code:

    1. At (1.), we actually do never type fallback to (). Coercing the ! to () does constitute a read, and therefore is UB.
    2. At (2.), HIR typeck considers the fact that *x evalauted to ! to mean that all following code diverges, and therefore counts the block's type as ! as well.

    Only (2.) actually matters for #129248, and if we were to land that PR right now, it would be unsound:

    #![feature(never_type)]
    
    fn make_up_a_value<T>() -> T {
        let x: *const ! = 0 as _;
        &raw const *x;
        // Since `*x` is `!`, HIR typeck thinks it diverges and allows the block to coerce to any value.
    }

    I will probably just put up a hack for (2.), since it's basically the only expression1 where this matters. This doesn't fix the let _: ! = *x; case, but that should remain unsafe at least, and therefore just regular UB and not unsound.

    Footnotes

    1. Other combinations of HIR, such as match scrutinees that have no reads should also ideally be fixed... but that's really hard to fix I fear. ↩

  18. RalfJung commented on Aug 21, 2024

    @RalfJung
    MemberAuthor

    I would say it is unsound in the sense that the reference doesn't say that this is UB. Admittedly the reference isn't clear on the entire point about let _ only evaluating the place expression, but that's what we've been consistently communicating for a while now and it has informed other decisions as well (around match, for instance).

    But fixing one of two bugs is still progress, so that'd still be a great first step. :)

  19. added a commit that references this issue on Aug 22, 2024
    4096397
  20. added 3 commits that reference this issue on Sep 6, 2024
    addf92c
    bdd04d9
    19c0326
  21. added a commit that references this issue on Oct 6, 2024
    a3e3233
  22. added a commit that references this issue on Oct 6, 2024
    9aaebd4
  23. added a commit that references this issue on Oct 6, 2024
    c2c0e52
  24. added a commit that references this issue on Oct 6, 2024
    50d6074
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

A-never-typeArea: `!` type. For issues not specific to `!`, use A-uninhabited-types.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamT-opsemRelevant to the opsem teamrequires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions