Skip to content

Fix data race in Arc and Weak types - #139

Merged
p-avital merged 1 commit into
ZettaScaleLabs:mainfrom
Ollie-Pearce:fix-data-race
Aug 18, 2026
Merged

Fix data race in Arc and Weak types#139
p-avital merged 1 commit into
ZettaScaleLabs:mainfrom
Ollie-Pearce:fix-data-race

Conversation

@Ollie-Pearce

Copy link
Copy Markdown
Contributor

Refcount decrements for Arc use Relaxed. This can result in a data race when two threads drop an owned instance of a type duplicated via Clone.

Reproduction:

Test Case:

#[test]
fn racy_test() {
    let a: Arc<u64, RustAlloc> = Arc::new_in(7, RustAlloc::new());
    let b = a.clone();
    std::thread::scope(|s| {
        s.spawn(move || drop(b));
        drop(a);
    });
}

Running with Miri:

MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo +nightly-2025-08-20 miri test -p stabby-abi --test repro

Miri output:

error: Undefined Behavior: Data race detected between (1) atomic store on thread `race_arc_drop_v` and (2) deallocation on thread `unnamed-3` at alloc688906+0x20
    |
 82 | /         alloc_rs::alloc::dealloc(
 83 | |             dealloc_start,
 84 | |             core::alloc::Layout::from_size_align_unchecked(prev_layout.size, prev_layout.align),
 85 | |         )
    | |_________^ (2) just happened here

Fix:

Synchronise drops of std::sync::Arc using a release ordering to decrement the reference counter and a fence before memory is freed. This mirrors the synchronisation used by std::sync::Arc

@p-avital p-avital left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'll release this soon :)

@p-avital
p-avital merged commit 3ff0b3e into ZettaScaleLabs:main Aug 18, 2026
21 of 22 checks passed
@Ollie-Pearce

Copy link
Copy Markdown
Contributor Author

@p-avital Thanks! Would you mind if I filed a RustSec advisory to document this?

meta-codesync Bot pushed a commit to facebook/starlark-rust that referenced this pull request Aug 25, 2026
Summary:
When `PinnedPagableArc` is dropped the pin counter is decremented with a `Relaxed` ordering. This can result in a data race when two threads drop an owned instance of a type duplicated via `Clone`.

## Reproduction:

### Test Case:
```rust
#[test]
fn racy_test() {
    let backend = InMemoryPagableStorage::new();
    let foo = PinnedPagableArc::new(false, PagableStorageHandle::new(backend.handle()));
    let bar = foo.clone();
    std::thread::scope(|s| {
        s.spawn(move || drop(bar));
        let _read = *foo;
        drop(foo);
    });
}
```

### Running with Miri:
`MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo +nightly miri test -p pagable --test repro`

### Miri output:
```
error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `racy_test`
and (2) retag write of type `pagable::pagable_arc::PagableArcInnerData<bool>` on thread `unnamed-3`
at alloc768762+0x20
   --> pagable/src/pagable_arc.rs:877:44
    |
877 |                 let data: &mut _ = unsafe { &mut *ptr.data.get() };
    |                                             ^^^^^^^^^^^^^^^^^^^^ (2) just happened here
help: and (1) occurred earlier here
   --> pagable/src/pagable_arc.rs:716:14
```

## Fix:

Synchronise the `PinnedPagableArc` pin counter with `Release`/`Acquire` memory orderings and use a fence before data is unpinned.

This mirrors the synchronisation used by [`std::sync::Arc`](https://doc.rust-lang.org/src/alloc/sync.rs.html#2827).
If you'd like to see another example [I found a similar bug in the `stabby` crate](ZettaScaleLabs/stabby#139)

Pull Request resolved: #232

Reviewed By: 8Keep

Differential Revision: D117200708

Pulled By: ndmitchell

fbshipit-source-id: 0ca54c4d87d8a5314af6b58ef631da71907fe72b
meta-codesync Bot pushed a commit to facebook/buck2 that referenced this pull request Aug 25, 2026
Summary:
When `PinnedPagableArc` is dropped the pin counter is decremented with a `Relaxed` ordering. This can result in a data race when two threads drop an owned instance of a type duplicated via `Clone`.

## Reproduction:

### Test Case:
```rust
#[test]
fn racy_test() {
    let backend = InMemoryPagableStorage::new();
    let foo = PinnedPagableArc::new(false, PagableStorageHandle::new(backend.handle()));
    let bar = foo.clone();
    std::thread::scope(|s| {
        s.spawn(move || drop(bar));
        let _read = *foo;
        drop(foo);
    });
}
```

### Running with Miri:
`MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo +nightly miri test -p pagable --test repro`

### Miri output:
```
error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `racy_test`
and (2) retag write of type `pagable::pagable_arc::PagableArcInnerData<bool>` on thread `unnamed-3`
at alloc768762+0x20
   --> pagable/src/pagable_arc.rs:877:44
    |
877 |                 let data: &mut _ = unsafe { &mut *ptr.data.get() };
    |                                             ^^^^^^^^^^^^^^^^^^^^ (2) just happened here
help: and (1) occurred earlier here
   --> pagable/src/pagable_arc.rs:716:14
```

## Fix:

Synchronise the `PinnedPagableArc` pin counter with `Release`/`Acquire` memory orderings and use a fence before data is unpinned.

This mirrors the synchronisation used by [`std::sync::Arc`](https://doc.rust-lang.org/src/alloc/sync.rs.html#2827).
If you'd like to see another example [I found a similar bug in the `stabby` crate](ZettaScaleLabs/stabby#139)

X-link: facebook/starlark-rust#232

Reviewed By: 8Keep

Differential Revision: D117200708

Pulled By: ndmitchell

fbshipit-source-id: 0ca54c4d87d8a5314af6b58ef631da71907fe72b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants