Skip to content

std::thread::JoinGuard (and scoped) are unsound because of reference cycles #24292

Closed
@arielb1

Description

@arielb1

You can use a reference cycle to leak a JoinGuard and then the scoped thread can access freed memory:

use std::thread;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::SeqCst;
use std::rc::Rc;
use std::cell::RefCell;

struct Evil<'a> {
    link: RefCell<Option<Rc<Rc<Evil<'a>>>>>,
    arm: thread::JoinGuard<'a, ()>
}

// This reliably observes an immutable reference mutating.
fn bad(g: &AtomicBool, v: &u64) {
    let jg = thread::scoped(move || {
        while !g.load(SeqCst) { }; // Wait for the reference to change
        println!("{}", *v);        // Observe it
        g.store(false, SeqCst);    // Safely exit without crashing
    });
    let e = Rc::new(Evil {
        link: RefCell::new(None),
        arm: jg
    });
    *e.link.borrow_mut() = Some(Rc::new(e.clone())); // Create a cycle
}

#[inline(never)] // Prevent DSE
fn helper(v: &mut Result<u64, &'static str>) {
    let g = AtomicBool::new(false); // Used as a barrier to ensure reliable execution
    if let &mut Ok(ref v) = v { bad(&g, &v) };
    *v = Err("foo");

    g.store(true, SeqCst);
    while g.load(SeqCst) {};
}

fn main() {
    helper(&mut Ok(4));
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions