Sitelet https://github.com/OpenByteDev/dlopen2/issues/28
Skip to content

UB: unsound Send for Ref #28

Description

@fereidani

Hey I found this using my UB static analyzer while scanning top 5000 downloaded crates:

Ref publicly contains a shared reference (src/symbor/reference.rs:12-20) and publicly exposes it as &T through Deref (src/symbor/reference.rs:42-46). Requiring only T: Send is insufficient: sending a Ref<Cell<i32>> to another thread gives both threads non-atomic access to the same Cell, even though Cell<i32>: Send but !Sync.

Sample:

use std::cell::Cell;
use dlopen2::symbor::Ref;

let cell = Cell::new(0_i32);
let r = Ref::new(&cell);

std::thread::scope(|scope| {
    let sender = scope.spawn(move || {
        for _ in 0..100_000 { r.set(r.get() + 1); }
    });
    for _ in 0..100_000 { cell.set(cell.get() + 1); }
    sender.join().unwrap();
});

Miri result:

error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `main` and (2) non-atomic read on thread `unnamed-1` at alloc160
   --> /home/kevin/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:558:18
    |
558 |         unsafe { *self.value.get() }
    |                  ^^^^^^^^^^^^^^^^^ (2) just happened here
    |
help: and (1) occurred earlier here
   --> src/main.rs:15:13
    |
 15 |             cell.set(cell.get() + 1);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: this is on thread `unnamed-1`
    = note: stack backtrace:
            0: std::cell::Cell::<i32>::get
                at /home/kevin/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:558:18: 558:35
            1: main::{closure#0}::{closure#0}
                at src/main.rs:11:23: 11:30
note: the last function in that backtrace got called indirectly due to this code
   --> src/main.rs:9:22
    |
  9 |           let sender = scope.spawn(move || {
    |  ______________________^
 10 | |             for _ in 0..100_000 {
 11 | |                 r.set(r.get() + 1);
 12 | |             }
 13 | |         });
    | |__________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Fix requires T: Sync (not merely T: Send) for the Send implementation, e.g. unsafe impl<'lib, T: Sync> Send for Ref<'lib, T> {}, matching the existing Sync bound.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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