rust-lang / rust-lang/rust

LLVM assumes that unused allocations always succeed, leading to violated invariants

Open
#162,214 24 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-LLVM C-bug I-miscompile I-unsound P-low T-opsem
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

View all comments

See rust-lang/unsafe-code-guidelines#328

LLVM assumes that calls to the global allocator (and local variable cration) do not fail when trying to optimize away unused allocations. This results in calls that are always going to fail (because they together exceed the address space) being able to observably violate invariants of rust types, for example by making &muts provably overlap.

Note that this is extremely contrived. Maybe someone can come up with an example that shows this better by more "directly" demonstrating hypothetically overlapping allocations? Also note that using any of the allocations in a way that is not optimized away causes the call to alloc to reappear alongside its null checks.

The example demonstrates this on 32 bit because LLVM's max allocation size is 1 << 61 bytes. It can be made to work on 64-bit, but I got lazy ^^
https://godbolt.org/z/6PWfYnno7

use std::mem::*;

fn mk_arr<const N: usize>() -> &'static mut [MaybeUninit<u8>; N] {
    Box::leak(Box::new([const { MaybeUninit::uninit() }; N]))
}

fn pidgeon_hole(arrs: [&mut [MaybeUninit<u8>]; 3]) {
    let [a, b, c] = arrs;
    // SAFETY: By pigeonhole principle, the total size of pointees behind
    // different `&mut T`s can never overflow, due to the type invariant
    // of their allocations not overlapping.
    unsafe { 
        size_of_val(a)
            .checked_add(size_of_val(b)).unwrap_unchecked()
            .checked_add(size_of_val(c)).unwrap_unchecked();
    }
}

pub fn main() {
    const N: usize = usize::MAX / 2;

    // LLVM just assumes this doesn't fail because
    // we're not using the allocations
    let a = mk_arr::<N>();
    let b = mk_arr::<N>();
    let c = mk_arr::<N>();

    // If LLVM can prove that we make it here, we have UB.
    // LLVM assumes the allocations succeed, so this will miscompile.
    pidgeon_hole([a, b, c]);    
}

@rustbot label I-unsound I-miscompile A-LLVM T-opsem

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the 32-bit example from the issue and reviewing rust-lang/unsafe-code-guidelines#328, including the linked Godbolt case. Work out the applicable LLVM allocation and optimization assumptions; done means the demonstrated allocation-failure case no longer permits the described invariant violation or miscompilation, with the behavior covered by an appropriate regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.