Skip to content

AtomicBucket<T> unconditionally implements Send/Sync #190

Closed
@JOE1994

Description

@JOE1994

Hello,
we (Rust group @sslab-gatech) found a memory-safety/soundness issue in this crate while scanning Rust code on crates.io for potential vulnerabilities.

Issue Description

AtomicBucket<T> automatically implements Send/Sync unconditionally, due to the fact that Block<T> unconditionally implements Send/Sync.

// https://docs.rs/metrics-util/0.6.2/src/metrics_util/bucket.rs.html#99-100
unsafe impl<T> Send for Block<T> {}
unsafe impl<T> Sync for Block<T> {}

Unconditional impl of Sync allows users to create data race to T: !Sync by using the AtomicBucket::data_with API to concurrently access &T from multiple threads.

Unconditional impl of Send allows users to send T: !Send to another thread by pushing T to AtomicBucket<T> and then moving it to another thread.

This issue exists in older versions too (v0.4.0-alpha.1),
but I used the newer version of the crate (v0.6.2) in the poc below,
as the older version fails to build on my pc at the moment..

Reproduction

Below is an example program that exhibits undefined behavior using safe APIs of metrics-util.

Show Detail

#![forbid(unsafe_code)]
use metrics_util::AtomicBucket;

use std::cell::Cell;
use std::sync::Arc;

#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
enum RefOrInt {
    Ref(&'static u64),
    Int(u64),
}

// The bug triggered in this poc exists in older versions too (v0.4.0-alpha.1),
// but I used the newer version of the crate as the older version fails to build
// on my pc at the time..

static SOME_INT: u64 = 123;
fn main() {
    let cell = Cell::new(RefOrInt::Ref(&SOME_INT));
    let bucket = Arc::new(AtomicBucket::new());
    bucket.push(cell);

    let bucket_clone = bucket.clone();
    std::thread::spawn(move || {
        let bucket = bucket_clone;

        // Repeatedly write Ref(&addr) and Int(0xdeadbeef) into the cell.
        bucket.data_with(|arr| {
            for cell in arr.iter() {
                loop {
                    cell.set(RefOrInt::Ref(&SOME_INT));
                    cell.set(RefOrInt::Int(0xdeadbeef));
                }
            }
        });
    });

    bucket.data_with(|arr| {
        for cell in arr.iter() {
            loop {
                if let RefOrInt::Ref(addr) = cell.get() {
                    if addr as *const u64  == &SOME_INT as *const u64 {
                        continue;
                    }
                    println!("Pointer is now {:p}", addr);
                    println!("Dereferencing addr will now segfault: {}", *addr);
                }
            }
        }
    });
}

Output:

Pointer is now 0xdeadbeef

Terminated with signal 11 (SIGSEGV)

Tested Environment

  • Crate: metrics-util
  • Version: 0.6.2
  • OS: Ubuntu 18.04.5 LTS
  • Rustc version: rustc 1.50.0 (cb75ad5db 2021-02-10)

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-utilComponent: utility classes and helpers.E-intermediateEffort: intermediate.T-bugType: bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions