Skip to content

Send bound needed on T (for Send impl of Bucket2) #2

Closed
@JOE1994

Description

@JOE1994

Hello 🦀 ,
while scanning crates.io, we (Rust group @sslab-gatech) have noticed a soundness/memory safety issue in this crate which allows safe Rust code to trigger undefined behavior.

Issue

Currently Send is implemented for Bucket2<T> even when T is not bound by Send.

unsafe impl<T> Send for Bucket2<T> {}

This makes it possible to use SyncPool<T> to send a non-Send object to other threads.

Proof of Concept

Below is an example program that exhibits undefined behavior using thesyncpool crate.
There is a data race on the internal reference count of Rc, and the program either crashes at runtime
(e.g. on Ubuntu: Illegal Instruction (Core Dumped))
, or panics at the end of the program (indicating a memory leak).
Such behavior can be observed when the program is compiled in Debug mode.

use syncpool::prelude::*;

use std::boxed::Box;
use std::rc::Rc;

const N_ITER: usize = 900_000;
const N_THREADS: usize = 6;
fn main() {
    // Non-Send object (to be sent to other threads).
    let rc = Rc::new(0_i32);

    let mut pools = vec![];
    for _ in 0..N_THREADS {
        let mut pool = SyncPool::new();
        let _dummy = pool.get();
        let malicious = Box::new(Rc::clone(&rc));
        pool.put(malicious);
        pools.push(pool);
    }

    let mut children = vec![];
    while let Some(pool) = pools.pop() {
        let c = std::thread::spawn(move || {
            // Moved `pool` to child thread.
            let mut pool = pool;
            let boxed_rc = pool.get();
    
            for _ in 0..N_ITER {
                // Data race on the internal ref count of `Rc`.
                Rc::clone(boxed_rc.as_ref());
            }
        });
        children.push(c);
    }
    // Join child threads.
    for child in children {
        child.join().unwrap();
    }

    assert_eq!(Rc::strong_count(&rc), 1);
}

The example is a bit contrived, but it triggers undefined behavior in safe Rust code.

How to fix the issue

The solution is to add a Send bound on T in the Send impl for Bucket2<T> as below.
I tested the above example using the modified version of the crate, and the compiler was able to successfully
revoke the program.

unsafe impl<T: Send> Send for Bucket2<T> {}

Thank you for checking out this issue 🦀

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions