-
Notifications
You must be signed in to change notification settings - Fork 14
Description
Problem
Gc (and WeakGc / WeakMap ) are auto- Send / Sync because they only contain NonNull + PhantomData , so Gc becomes Send / Sync whenever T is. This is unsound because the collector and GC headers are explicitly single-threaded and rely on Cell for mutable state. The root count and color flags in GcHeader are non-atomic ( Cell ), so increments/decrements and color flips can race across threads. A Gc dropped on another thread can concurrently mutate GcHeader while collect() is marking/sweeping, which is UB and can lead to premature collection or use-after-free.
Code locations:
- Gc stores an erased pointer with no thread-safety marker: gc.rs
- The erased arena pointer is NonNull + PhantomData , so it’s auto- Send / Sync : alloc.rs
- GC header mutability is via Cell (non-atomic): gc_header.rs
- Collector is explicitly single-threaded (RefCell + no Sync): mod.rs
- GcRefCell is marked Send even though it uses Cell for borrow state: cell.rs
This is a soundness hole in safe Rust. The type system currently allows moving Gc across threads and dropping it while a collection is running elsewhere. Because root counts and header colors are mutated with Cell , any concurrent access is a data race (UB). Worse, lost updates on root_count can make live objects appear dead, causing premature reclamation and use-after-free. This is exactly the class of bug Rust’s Send/Sync model is intended to prevent.
Expected Behavior
Gc and related handles should not be Send / Sync unless the collector is thread-safe. The code above should fail to compile, or the runtime should be provably data-race free through atomic synchronization.
Actual Behavior
The code compiles, and Gc can be sent across threads. This enables concurrent, unsynchronized mutation of GcHeader and can lead to memory corruption or UAF.
Suggested Fix / Investigation Direction
- Make Gc , WeakGc , WeakMap<K,V> , and the erased pointer types explicitly !Send and !Sync for the mark-sweep collector.
- Use negative impls ( impl !Send for Gc {} / impl !Sync for Gc {} ), or
- Add a PhantomData<alloc::rc::Rc<()>> marker to force !Send / !Sync in no_std+alloc, or
- Tie Gc to a non- Send collector marker type so cross-thread moves are rejected.
- Revisit unsafe impl Send for GcRefCell since it enables cross-thread moves of a non-atomic borrow state.
- If multi-threaded GC is a goal, the header fields ( root_count , color) must be atomic and the collector API must enforce synchronization.
References
- Rustonomicon on Send/Sync and auto traits: https://doc.rust-lang.org/nomicon/send-and-sync.html
- Rustonomicon on data races and interior mutability: https://doc.rust-lang.org/nomicon/races.html
- “Thread Safety and Data Races” in Rust Reference: https://doc.rust-lang.org/reference/behavior-considered-undefined.html#data-races