Skip to content

fix(threading): avoid invalid Channel/FIFO read patterns - #31107

Open
Dicklesworthstone wants to merge 1 commit into
oven-sh:mainfrom
Dicklesworthstone:claude/ub-fix-channel-read-item-uninit
Open

Dicklesworthstone wants to merge 1 commit into
oven-sh:mainfrom
Dicklesworthstone:claude/ub-fix-channel-read-item-uninit

Conversation

@Dicklesworthstone

@Dicklesworthstone Dicklesworthstone commented May 19, 2026 •

Copy link
Copy Markdown

What changed

This fixes the audit EXP-033 Channel single-item read shape in bun_threading.

try_read_item() and read_item() previously allocated [MaybeUninit<T>; 1], cast that storage to &mut [T; 1], and then routed through the slice-based read APIs. That fabricates a typed mutable reference before a valid T exists in the slot. For validity-sensitive payloads such as bool, the audit witness reports Miri UB while reading the uninitialized slot:

Undefined Behavior: reading memory ... but memory is uninitialized

The single-item methods now pop directly from the locked FIFO through a shared helper, avoiding the temporary uninitialized T reference entirely. The helper preserves the existing empty/closed-channel behavior and still avoids holding the UnsafeCell buffer borrow across Condition::wait().

During review, I also found a neighboring channel write bug: write_items() appended the entire input slice on every loop iteration while advancing pushed by one. That could duplicate multi-item writes (or return the wrong partial count for fixed buffers). It now writes exactly items[pushed] per successful iteration, matching the loop's accounting and blocking behavior.

Running the new dynamic-buffer multi-item test under Miri then exposed nearby LinearFifo::realign stacked-borrows issues: overlapping copies derived shared raw pointers and mutable raw pointers from the same slice. Both realign copy paths now derive source and destination from one mutable raw pointer.

Test coverage

Added coverage for:

  • Channel<bool> single-item reads, so the validity-sensitive payload case is exercised directly under Miri
  • dynamic-buffer multi-item writes appending each item exactly once and leaving the channel empty after the expected reads
  • wrapped-buffer LinearFifo::realign, which exercises the second raw-copy branch under Miri

Verification

  • bun run fmt:rust
  • env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly test -p bun_threading channel::tests
  • env CARGO_TARGET_DIR=/tmp/bun-channel-fix-miri-target cargo +nightly miri test -p bun_threading channel::tests
  • env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly check -p bun_threading
  • env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly test -p bun_collections linear_fifo
  • env CARGO_TARGET_DIR=/tmp/bun-channel-fix-miri-target cargo +nightly miri test -p bun_collections linear_fifo_realign_wrapped_buffer
  • env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly check -p bun_collections
  • git diff --check
  • env CARGO_TARGET_DIR=/tmp/bun-channel-clean-target cargo +nightly check --workspace from a clean HEAD snapshot

The workspace check only emitted existing warnings.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a8f261e1-dbcb-4b46-ba4e-19a2c8886680

📥 Commits

Reviewing files that changed from the base of the PR and between 5a63c2b and b471ab4.

📒 Files selected for processing (2)
  • src/collections/linear_fifo.rs
  • src/threading/channel.rs

Walkthrough

Refactors Channel read paths by adding a mutex-guarded helper read_item_locked() used by try_read_item, read_item, and read_items; updates write loop to call per-item write_item; refactors LinearFifo realign pointer copy for wrapped/non-wrapped paths; removes MaybeUninit import and adds tests.

Changes

Channel read path consolidation

Layer / File(s) Summary
Locked-read helper and single-item API refactor
src/threading/channel.rs
read_item_locked() added to centralize mutex-guarded empty/closed checks and unsafe buffer reads. try_read_item acquires the mutex and returns the helper result directly. read_item loops, calling the helper and waiting on getters until an item or close. Removed MaybeUninit import.
Bulk read and write adjustments
src/threading/channel.rs
read_items loop now calls read_item_locked() to obtain each item instead of duplicating read/closed logic. write_items now calls the single-item write_item per iteration.
LinearFifo realign pointer copy
src/collections/linear_fifo.rs
Non-wrapping realignment now uses a raw *mut T buffer pointer and ptr::copy from buf.add(head) to buf. Wrapped realign path reuses raw buf pointer for scratch ↔ buf byte copies and overlapping shift; added a test exercising wrapped-buffer realign.
Tests for single/multi-item behavior
src/threading/channel.rs, src/collections/linear_fifo.rs
New/updated unit tests: verify Channel<bool> single-item try_read_item/read_item behavior and Closed after close(), validate multi-item writes/reads for a Channel<u8> using a dynamic buffer, and test LinearFifo::realign when data wraps.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(threading): avoid invalid Channel/FIFO read patterns' directly and clearly summarizes the main changes: fixing undefined behavior in Channel read methods and FIFO realignment by avoiding invalid read patterns.
Description check ✅ Passed The description provides comprehensive details under non-template sections, explaining the audit fix, bugs found/fixed, and verification steps performed, though it doesn't use the repository's required template structure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Dicklesworthstone
Dicklesworthstone force-pushed the claude/ub-fix-channel-read-item-uninit branch 2 times, most recently from 620f98d to 5a63c2b Compare May 20, 2026 00:11
@Dicklesworthstone Dicklesworthstone changed the title fix(threading): avoid uninit scratch reference in Channel reads fix(threading): avoid invalid Channel/FIFO read patterns May 20, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/collections/linear_fifo.rs`:
- Around line 283-285: The current DynamicBuffer<T>::as_mut_slice() (via
assume_init_slice_mut()) creates &mut [T] over uninitialized memory which is UB
for non‑"any‑bit‑pattern" types like bool; update the buffer API used by
LinearFifo and Channel::init_dynamic() to avoid creating typed references to
uninitialized memory: either add a trait bound (e.g., require T: Pod) on
DynamicBuffer/LinearFifo/Channel so assume_init_slice_mut() is only used for POD
types, or change the trait to expose &mut [MaybeUninit<T>] / raw pointers to
MaybeUninit<T> and perform unsafe copies/rotations with ptr::copy /
ptr::copy_nonoverlapping on the MaybeUninit storage (e.g., replace calls to
as_mut_slice() and buf.as_mut_slice().as_mut_ptr() with MaybeUninit-aware
operations) so no &mut T is formed for uninitialized slots.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d349eaa-9682-4315-aed3-f72a76321044

📥 Commits

Reviewing files that changed from the base of the PR and between 620f98d and 5a63c2b.

📒 Files selected for processing (2)
  • src/collections/linear_fifo.rs
  • src/threading/channel.rs

Comment thread src/collections/linear_fifo.rs
Audit EXP-033 showed the single-item Channel read path could build a typed &mut [T; 1] over MaybeUninit storage before any T existed. That is invalid for values with Rust validity requirements, and the witness reports Miri UB while reading the uninitialized bool-shaped slot.

Route try_read_item/read_item through the same locked FIFO pop used by read_items instead of fabricating a temporary initialized slice. The shared helper checks emptiness before popping, preserves the closed-channel behavior, and still avoids holding the UnsafeCell buffer borrow across Condition::wait().

Fresh-eyes review also found that write_items was appending the whole input slice on every loop iteration while advancing pushed by one. Write one queued item per successful iteration instead, matching the loop's blocking/partial-write accounting.

Running the multi-item dynamic-buffer test under Miri then exposed neighboring LinearFifo::realign stacked-borrows issues: the overlapping copies derived shared raw pointers and mutable raw pointers from the same slice. Derive all source and destination pointers from one mutable raw pointer instead.

Add tests for the bool payload read path, multi-item writes appending each item exactly once, and the wrapped realign path.

Verification:

- bun run fmt:rust

- env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly test -p bun_threading channel::tests

- env CARGO_TARGET_DIR=/tmp/bun-channel-fix-miri-target cargo +nightly miri test -p bun_threading channel::tests

- env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly check -p bun_threading

- env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly test -p bun_collections linear_fifo

- env CARGO_TARGET_DIR=/tmp/bun-channel-fix-miri-target cargo +nightly miri test -p bun_collections linear_fifo_realign_wrapped_buffer

- env CARGO_TARGET_DIR=/tmp/bun-channel-fix-target cargo +nightly check -p bun_collections

- git diff --check

This branch has not been deployed

No deployments
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.

1 participant