-
-
Notifications
You must be signed in to change notification settings - Fork 417
Description
The MergeOperator trait appears to be missing (at least) a Send bound, allowing non-Send types to be misused when data is merged on another thread. I'm aware that removal of the merge API is planned but this seems like a pretty serious soundness issue and should hopefully be easy to fix.
Expected result
This issue can be reproduced with the following code.
use std::hint::black_box;
use std::rc::Rc;
use std::thread;
use sled::Config;
fn main() {
let config = Config::new().temporary(true);
let db = config.open().unwrap();
let cant_share = Rc::new(69);
let cant_share_copy = Rc::clone(&cant_share);
let bad_access = move |_: &[u8], _: Option<&[u8]>, _: &[u8]| {
for _ in 0..10_000 {
let cant_share_copy = Rc::clone(&cant_share_copy);
black_box(&*cant_share_copy);
drop(cant_share_copy);
}
None
};
db.set_merge_operator(bad_access);
let db_copy = db.clone();
let handle = thread::spawn(move || {
db_copy.merge("test", b"anything".to_vec()).unwrap();
});
for _ in 0..10_000 {
let cant_share_copy = Rc::clone(&cant_share);
black_box(&*cant_share_copy);
drop(cant_share_copy);
}
handle.join().unwrap();
}Since the closure bad_access captures an Rc<i32>, it implements neither Send nor Sync and should not be usable as a merge operator in the concurrent database db. The code should not compile.
Actual result
The result of running the code is not predictable, but I observed the following errors in debug mode, indicating that undefined behavior is occurring.
$ cargo run
Compiling sled_ub v0.1.0 ([...]/sled_ub)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/sled_ub`
thread 'main' (370158) panicked at [...]/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/rc.rs:3579:13:
unsafe precondition(s) violated: hint::assert_unchecked must never be called when the condition is false
This indicates a bug in the program. This Undefined Behavior check is optional, and cannot be relied on for safety.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread caused non-unwinding panic. aborting.
[1] 370158 IOT instruction (core dumped) cargo run
$ cargo run
Compiling sled_ub v0.1.0 ([...]/sled_ub)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/sled_ub`
[1] 376531 segmentation fault (core dumped) cargo run
Sled version
$ cat Cargo.toml
[package]
name = "sled_ub"
version = "0.1.0"
edition = "2024"
[dependencies]
sled = "0.34.7"
Rust version
$ rustc --version
rustc 1.89.0 (29483883e 2025-08-04)
Operating system
I'm running Ubuntu 24.04 on 64-bit x86.
Backtrace
Running with RUST_BACKTRACE=full seems to confirm that the issue is caused by the non-Send closure.
$ RUST_BACKTRACE=full cargo run
[...]
18: 0x5749ca7ee59c - core::hint::assert_unchecked::h31232f484bd204f4
at [...]/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ub_checks.rs:77:17
19: 0x5749ca7ee59c - alloc::rc::RcInnerPtr::inc_strong::h78e74a5aefe294f8
at [...]/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/rc.rs:3567:13
20: 0x5749ca7ee6f5 - <alloc::rc::Rc<T,A> as core::clone::Clone>::clone::he8ecaed6f4485f92
at [...]/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/rc.rs:2337:26
21: 0x5749ca7f18c9 - sled_ub::main::{{closure}}::hb11d0918d7c5f48c
at [...]/sled_ub/src/main.rs:16:35
22: 0x5749ca8d2c9d - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::ha9d7004ecd973fe0
at [...]/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:1980:9
23: 0x5749ca89a856 - sled::tree::Tree::merge_inner::ha722e930d94a23b1
at [...]/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/sled-0.34.7/src/tree.rs:1043:23
24: 0x5749ca7ef336 - sled::tree::Tree::merge::hf6aa52a95b96feca
at [...]/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/sled-0.34.7/src/tree.rs:1008:37
[...]
Note that assert_unchecked failed within Rc::clone called by the closure invoked by Tree::merge_inner. The data race caused the reference count to underflow, as detected by the following standard library code, which could cause a use-after-free if not caught by debug checks.
// SAFETY: The reference count will never be zero when this is
// called.
unsafe {
hint::assert_unchecked(strong != 0);
}Thank you for your time.