Skip to content

Add advisory for unbounded-spsc: Sender::send transmute UAF under tx/rx race#2884

Open
berkant-koc wants to merge 2 commits into
rustsec:mainfrom
berkant-koc:berkoc/unbounded-spsc-transmute-uaf-2026-05-14
Open

Add advisory for unbounded-spsc: Sender::send transmute UAF under tx/rx race#2884
berkant-koc wants to merge 2 commits into
rustsec:mainfrom
berkant-koc:berkoc/unbounded-spsc-transmute-uaf-2026-05-14

Conversation

@berkant-koc
Copy link
Copy Markdown
Contributor

Resubmission of the unbounded-spsc half of #2882, split per @djc's review and trimmed.

Sender::send (single-file crate, src/lib.rs:379-405 in 0.2.0 / commit 23a9ce7) transmutes *mut Producer<T> (a pointer, 8 bytes on 64-bit) into the bytes of a value-level Consumer<T>. The resulting Consumer::buffer.ptr is the address of a field on the Sender, not the real ArcInner<Buffer<T>>. consumer.try_pop() then reads Buffer<T> offsets that lie inside the Sender<T> frame (OOB), and Drop for the fake Arc calls dealloc on a non-allocated address.

Branch is only reachable single-threaded if the receiver-drop / connected = false guard is bypassed; the trigger is a TOCTOU race between Sender::send's connected.load() and Receiver::drop's compare_exchange(_, DISCONNECTED, ...). The author's own test suite carries a TODO at src/lib.rs:975 referencing the same assert that fires in the buggy path — the symptom was observed but misclassified.

The companion oneringbuf advisory is filed separately as a sibling PR.

Closes prior #2882 (superseded by this + the oneringbuf PR).

…rx race

The DISCONNECTED arm of Sender::send transmutes *mut Producer<T> as
a value-level Consumer<T>, yielding OOB read inside the Sender frame
and a fake-Arc drop on a non-allocated address. Reachable via TOCTOU
race between Receiver::drop and Sender::send.
@djc
Copy link
Copy Markdown
Member

djc commented May 14, 2026

Do you have approval from the maintainer to publish this?

@berkant-koc
Copy link
Copy Markdown
Contributor Author

Same — closing per your point on #2883. The private channel here bounced (spearman@gitlab.com is a dead address), so I opened spearman/unbounded-spsc#4 asking the maintainer to enable private vulnerability reporting or supply an alternate contact. Will reopen this advisory PR once the maintainer has engaged, or after a 30-day attempt window.

Maintainer @spearman published v0.3.0 on 2026-05-16 with the fix plus
regression test race_disconnect_does_not_corrupt_sender_or_abort
(commit 2714da2), accepted GHSA-6m57-8r3p-pqx6, and closed upstream
issue rustsec#4. All prior versions (0.2.0, 0.1.x) are yanked on crates.io.

- patched = [">= 0.3.0"]
- aliases = ["GHSA-6m57-8r3p-pqx6"]
- url = spearman/unbounded-spsc#4
@berkant-koc berkant-koc reopened this May 17, 2026
@berkant-koc
Copy link
Copy Markdown
Contributor Author

Reopening: maintainer @spearman published v0.3.0 on crates.io (2026-05-16) with the fix plus a regression test (race_disconnect_does_not_corrupt_sender_or_abort), accepted GHSA-6m57-8r3p-pqx6, and closed the upstream issue #4. All prior versions (0.2.0 + every 0.1.x) are yanked.

Advisory updated:

  • patched = [">= 0.3.0"]
  • aliases = ["GHSA-6m57-8r3p-pqx6"]
  • url = https://github.com/spearman/unbounded-spsc/issues/4
  • one-line maintainer-ack note prepended to the description

Ready for review.

Copy link
Copy Markdown
Member

@djc djc left a comment

Choose a reason for hiding this comment

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

I don't see the maintainer approving submission of a RustSec advisory anywhere.

Comment on lines +36 to +64
## Trigger

```rust
use std::thread;
use unbounded_spsc::channel;

for _ in 0..500 {
let (tx, rx) = channel::<Box<u64>>();
let h = thread::spawn(move || {
for _ in 0..10_000 { let _ = tx.send(Box::new(0xDEAD_BEEF)); }
});
drop(rx);
let _ = h.join();
}
// Release: SIGSEGV within a few iterations.
// ASan: stack-buffer-overflow inside fake-Consumer::try_pop.
```

## Smoking-gun upstream evidence

`src/lib.rs:975` in the project's own test suite carries:

```
// TODO: failures
// - failed with assertion on line 394 in send fn
// assert!(second.is_none())
```

That assertion lives in the same transmute block. The author has observed the symptom — `try_pop()` returning `Some` where logically there should be `None` — as a flaky test rather than recognising it as UB.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's drop the "Trigger" and "Smoking-gun upstream evidence" sections.


# `Sender::send` pointer-as-value transmute causes OOB read and fake-Arc drop under tx/rx race

Maintainer acknowledged on 2026-05-15; v0.3.0 ships the fix plus a regression test (`race_disconnect_does_not_corrupt_sender_or_abort`), and 0.2.0 + all 0.1.x versions have been yanked on crates.io.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's drop this metadata from the advisory contents.

@djc
Copy link
Copy Markdown
Member

djc commented May 18, 2026

accepted GHSA-6m57-8r3p-pqx6,

This has not been published.

@berkant-koc
Copy link
Copy Markdown
Contributor Author

berkant-koc commented May 18, 2026 via email

@berkant-koc
Copy link
Copy Markdown
Contributor Author

berkant-koc commented May 18, 2026 via email

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