Skip to content

fix: address UB audit soundness findings#30924

Open
Dicklesworthstone wants to merge 7 commits into
oven-sh:mainfrom
Dicklesworthstone:claude/ub-fixes-demo-1
Open

fix: address UB audit soundness findings#30924
Dicklesworthstone wants to merge 7 commits into
oven-sh:mainfrom
Dicklesworthstone:claude/ub-fixes-demo-1

Conversation

@Dicklesworthstone
Copy link
Copy Markdown

@Dicklesworthstone Dicklesworthstone commented May 17, 2026

Follow-up to the UB audit artifacts in #30903. This PR contains source fixes only; no audit artifacts, beads, lockfile changes, or generated-output churn.

What this fixes:

  • EXP-094: deletes unused DoublyLinkedList / DoublyLinkedNode code whose in-tree unit test reproduced Stacked Borrows UB under Miri.
  • EXP-002: validates Linux raw syscall errno conversion instead of transmuting the kernel errno range into SystemErrno; impossible raw errnos now fail closed instead of mapping to success.
  • EXP-018: makes GuardedLock !Send/!Sync, matching sibling mutex guard behavior for OS backends that require unlock on the locking thread.
  • EXP-019: bounds StoreSlice<T> Send/Sync impls on T, matching the sister StoreRef<T> discipline.
  • EXP-097: validates safe from_raw errno constructors so invalid discriminants panic instead of constructing invalid repr enums.

Moved out of this PR:

Notes:

Verification run locally after rebasing onto current origin/main:

  • cargo +nightly check -p bun_core -p bun_ast -p bun_errno -p bun_threading
  • cargo +nightly check -p bun_errno --target x86_64-pc-windows-msvc
  • cargo +nightly test -p bun_errno --lib system_errno_from_raw_rejects_invalid_safe_input
  • cargo +nightly test -p bun_errno --lib linux_raw_syscall_errno_rejects_invalid_tag
  • cargo +nightly miri test -p bun_errno --lib

Verification after de-overlapping EXP-098 from this PR:

  • env CARGO_TARGET_DIR=/tmp/bun-pr30924-dedupe-target cargo +nightly check -p bun_core -p bun_ast -p bun_errno -p bun_threading
  • git diff --check origin/main...HEAD

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

PR hardens unsafe code across concurrent and encoding paths: auto-trait impls for generic types enforce stricter T bounds, errno discriminant conversions switch from unchecked transmutes to validated enum construction, GuardedLock adds a non-Send marker field, and a deprecated intrusive list implementation is removed with documentation.

Changes

Safety and Correctness Improvements

Layer / File(s) Summary
Auto-trait bounds tightening for generic types
src/ast/nodes.rs, src/bun_core/atomic_cell.rs
StoreSlice Send/Sync impls now require T: Send/T: Sync bounds; AtomicCell Send/Sync bounds changed from T: Copy to T: Atom with updated SAFETY comments.
Errno discriminant validation across platforms
src/errno/lib.rs, src/errno/linux_errno.rs, src/errno/windows_errno.rs
SystemErrno::from_raw validates discriminants before transmute (non-Windows) or uses from_repr (Windows); Linux and Windows errno conversions similarly validate instead of transmuting; panic tests added for invalid discriminants.
Non-Send PhantomData marker for GuardedLock
src/threading/guarded.rs
GuardedLock struct gains _not_send: PhantomData<*const ()> field; try_lock and lock constructors initialize it to enforce non-Send/non-Sync semantics.
Deprecated doubly-linked list removal
src/bun_core/deprecated.rs
DoublyLinkedList/DoublyLinkedNode types, Default impl, public methods, and unit tests removed; documentation replaces deleted implementation; unused core::ptr import removed; RapidHash re-export preserved.

Possibly related issues

Suggested reviewers

  • dylan-conway
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main purpose of the PR: addressing soundness findings from a UB audit.
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.
Description check ✅ Passed The PR description is detailed and complete, covering all required sections including what the PR does and how changes were verified.

✏️ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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/errno/linux_errno.rs`:
- Around line 191-193: The code currently collapses unknown raw syscall errno
values to SystemErrno::SUCCESS via SystemErrno::init(int as
i64).unwrap_or(SystemErrno::SUCCESS), which converts real kernel errors into
false successes; instead stop treating unknown values as success by returning or
propagating the failure from SystemErrno::init (or mapping to an explicit
failure sentinel). Replace the unwrap_or(SystemErrno::SUCCESS) usage in the
SystemErrno::init call with one of: propagate the Option/Result to the caller so
callers must handle None/Err, map unknown raw errno to an explicit failure
variant (e.g. add and return SystemErrno::UNKNOWN_RAW or
SystemErrno::UNMAPPED_ERROR) rather than SUCCESS, or explicitly fail
(panic/return Err) to "fail closed" so unknown kernel error codes are not
treated as successes; adjust the surrounding function signature accordingly to
return the propagated error variant.
🪄 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: 8632e3fb-66ba-4cb9-aea8-e509451e3040

📥 Commits

Reviewing files that changed from the base of the PR and between 172afa5 and 117db1f.

📒 Files selected for processing (8)
  • src/ast/nodes.rs
  • src/bun_core/atomic_cell.rs
  • src/bun_core/deprecated.rs
  • src/errno/lib.rs
  • src/errno/linux_errno.rs
  • src/errno/windows_errno.rs
  • src/runtime/webcore/encoding.rs
  • src/threading/guarded.rs

Comment thread src/errno/linux_errno.rs Outdated
codex and others added 6 commits May 17, 2026 21:59
`bun_core::deprecated::DoublyLinkedList<T>` and its unit tests failed under Miri's Stacked Borrows model. The test handed the list `&mut node` references, the list stored raw `*mut node` links, and later `&mut node` reborrows invalidated the raw-pointer tags before traversal:

  error: Undefined Behavior: attempting a read access ... tag does not exist in the borrow stack

Reproducer against the pre-fix source:

  MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test \
      -p bun_core --lib basic_doubly_linked_list_test

The type had no production callers; only its own tests and one `TODO(port): std.DoublyLinkedList` comment in `src/jsc/web_worker.rs` referenced it. Because `deprecated.rs` already records removed Zig-port helpers, this commit replaces the unused type, impls, and tests with a short removal note and leaves future intrusive-list work to use pinned/list-owned nodes or an intrusive-list adapter.

Source: `.ub-exorcism/2026-05-15-exhaustive/UNDEFINED_BEHAVIOR_EXPERIMENT_DESIGNS.md` entry `EXP-094`. Full audit context: PR oven-sh#30903.
@Dicklesworthstone Dicklesworthstone force-pushed the claude/ub-fixes-demo-1 branch from 256f657 to a36de0f Compare May 18, 2026 02:01
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/ast/nodes.rs`:
- Around line 339-343: StoreSlice<T> is unsound because it's Copy and exposes a
safe slice_mut(self) that takes self by value, allowing duplicated copies to
each produce &mut [T] to the same backing storage; fix by removing the Copy impl
OR change the slice_mut signature to require &mut self (or alternatively mark
slice_mut as unsafe) so that multiple aliased copies cannot create independent
mutable references; update the StoreSlice<T> type impls (the Copy impl and the
slice_mut method) accordingly and ensure Send/Sync bounds remain appropriate for
the payload type.

In `@src/bun_core/atomic_cell.rs`:
- Around line 59-65: The unsafe auto-trait impls for AtomicCell are missing a T:
Send requirement; update the two unsafe impls for Sync and Send to require T:
Send (i.e., change unsafe impl<T: Atom> Sync for AtomicCell<T> {} and unsafe
impl<T: Atom> Send for AtomicCell<T> {} to include + Send on T) so the container
cannot be auto-marked Send/Sync for types that are !Send; make the same T: Send
change anywhere else you expose auto-trait impls for AtomicCell to keep bounds
consistent.

In `@src/bun_core/deprecated.rs`:
- Around line 101-113: Remove the deleted-code rationale comment block that
begins "The Rust port of `std.DoublyLinkedList` / `DoublyLinkedNode` was
removed..." so the module only contains current surface-level documentation, not
historical justification; delete that entire multi-line explanatory paragraph
(the block about Miri/Stacked Borrows, callers, and future intrusive-list
suggestions) and leave no leftover references to the removed type in
deprecated.rs.
🪄 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: ed0836f0-a8c6-477c-9af6-af3bc776c69d

📥 Commits

Reviewing files that changed from the base of the PR and between 256f657 and a36de0f.

📒 Files selected for processing (7)
  • src/ast/nodes.rs
  • src/bun_core/atomic_cell.rs
  • src/bun_core/deprecated.rs
  • src/errno/lib.rs
  • src/errno/linux_errno.rs
  • src/errno/windows_errno.rs
  • src/threading/guarded.rs

Comment thread src/ast/nodes.rs
Comment on lines +339 to +343
//
// The bound matches sister `StoreRef<T>`: without it, `StoreSlice<Cell<U>>`
// would be `Send + Sync` even though `Cell<U>` is `!Sync`.
unsafe impl<T: Send> Send for StoreSlice<T> {}
unsafe impl<T: Sync> Sync for StoreSlice<T> {}
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.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C2 \
  'impl<T> Copy for StoreSlice<T>|pub fn slice_mut|unsafe impl<T: Send> Send for StoreSlice<T>|unsafe impl<T: Sync> Sync for StoreSlice<T>' \
  src/ast/nodes.rs

Repository: oven-sh/bun

Length of output: 824


StoreSlice<T> is unsound: narrowing bounds on Send/Sync does not prevent aliased mutable access.

Line 328 makes StoreSlice<T> unconditionally Copy, and line 412 exposes slice_mut(self) as a safe method taking self by value. A shared &StoreSlice<T> can be copied freely, and each copy can independently call slice_mut() to produce a distinct &mut [T] to the same backing allocation—violating Rust's mutable aliasing invariant. The unsafe trait bounds on lines 342–343 only constrain the payload type T, not the receiver semantics. Either make StoreSlice<T> non-Copy, require &mut self in slice_mut, or mark slice_mut as unsafe.

🤖 Prompt for 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.

In `@src/ast/nodes.rs` around lines 339 - 343, StoreSlice<T> is unsound because
it's Copy and exposes a safe slice_mut(self) that takes self by value, allowing
duplicated copies to each produce &mut [T] to the same backing storage; fix by
removing the Copy impl OR change the slice_mut signature to require &mut self
(or alternatively mark slice_mut as unsafe) so that multiple aliased copies
cannot create independent mutable references; update the StoreSlice<T> type
impls (the Copy impl and the slice_mut method) accordingly and ensure Send/Sync
bounds remain appropriate for the payload type.

Comment thread src/bun_core/atomic_cell.rs Outdated
Comment on lines +59 to +65
// SAFETY: `T: Atom` means every access routes through a native atomic backing
// (or the `AtomicPtr` specialization for pointers) and `T: Copy` means no drop
// glue can race. Non-atomic `Copy` payloads may still use `new()`/`into_inner()`
// locally, but they must not auto-conjure cross-thread Send/Sync through this
// wrapper.
unsafe impl<T: Atom> Sync for AtomicCell<T> {}
unsafe impl<T: Atom> Send for AtomicCell<T> {}
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C2 \
  'unsafe impl<T: Atom> (Sync|Send) for AtomicCell<T>|pub unsafe trait Atom: Copy|pub fn into_inner|pub fn load\(|pub fn swap\(|pub fn compare_exchange' \
  src/bun_core/atomic_cell.rs

Repository: oven-sh/bun

Length of output: 1303


🏁 Script executed:

sed -n '193,210p' src/bun_core/atomic_cell.rs

Repository: oven-sh/bun

Length of output: 622


🏁 Script executed:

sed -n '78,115p' src/bun_core/atomic_cell.rs

Repository: oven-sh/bun

Length of output: 1193


Require T: Send on AtomicCell's auto-trait bounds.

The Atom trait constrains only atomic representation and size; it does not guarantee thread-safe movement of T. However, this API exposes owned T values across threads via into_inner(), load(), swap(), and compare_exchange() on shared references. An external unsafe impl Atom for !Send CopyType would currently auto-impl Send + Sync for AtomicCell<ThatType>, allowing unsound cross-thread value movement. Both implementations require at least T: Send.

Suggested fix
-unsafe impl<T: Atom> Sync for AtomicCell<T> {}
-unsafe impl<T: Atom> Send for AtomicCell<T> {}
+unsafe impl<T: Atom + Send> Sync for AtomicCell<T> {}
+unsafe impl<T: Atom + Send> Send for AtomicCell<T> {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// SAFETY: `T: Atom` means every access routes through a native atomic backing
// (or the `AtomicPtr` specialization for pointers) and `T: Copy` means no drop
// glue can race. Non-atomic `Copy` payloads may still use `new()`/`into_inner()`
// locally, but they must not auto-conjure cross-thread Send/Sync through this
// wrapper.
unsafe impl<T: Atom> Sync for AtomicCell<T> {}
unsafe impl<T: Atom> Send for AtomicCell<T> {}
// SAFETY: `T: Atom` means every access routes through a native atomic backing
// (or the `AtomicPtr` specialization for pointers) and `T: Copy` means no drop
// glue can race. Non-atomic `Copy` payloads may still use `new()`/`into_inner()`
// locally, but they must not auto-conjure cross-thread Send/Sync through this
// wrapper.
unsafe impl<T: Atom + Send> Sync for AtomicCell<T> {}
unsafe impl<T: Atom + Send> Send for AtomicCell<T> {}
🤖 Prompt for 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.

In `@src/bun_core/atomic_cell.rs` around lines 59 - 65, The unsafe auto-trait
impls for AtomicCell are missing a T: Send requirement; update the two unsafe
impls for Sync and Send to require T: Send (i.e., change unsafe impl<T: Atom>
Sync for AtomicCell<T> {} and unsafe impl<T: Atom> Send for AtomicCell<T> {} to
include + Send on T) so the container cannot be auto-marked Send/Sync for types
that are !Send; make the same T: Send change anywhere else you expose auto-trait
impls for AtomicCell to keep bounds consistent.

Comment on lines +101 to +113
//
// The Rust port of `std.DoublyLinkedList` / `DoublyLinkedNode` was removed
// after its in-tree unit test failed under Miri (Stacked Borrows): callers
// hand the list `&mut node` references whose tags are then invalidated by
// later `&mut node` re-borrows on the same stack-local, while the list
// still traverses the stale raw `*mut node` links. The struct had no
// callers outside its own unit test, so deletion is the safe fix. The one
// in-tree comment that referenced the type (`src/jsc/web_worker.rs`'s
// `TODO(port): std.DoublyLinkedList` for `WebWorker.live_{next,prev}`)
// remains a TODO. Future intrusive-list needs should pick a design that
// does not interleave `*mut node` and `&mut node` on the same allocation
// (for example pinned/list-owned nodes, an `intrusive-collections` adapter,
// or a `Box`-owning list).
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Drop the deletion-rationale block from source.

This is documenting removed code rather than the current module surface. Please keep that rationale in the PR/commit history and delete the block here.

As per coding guidelines, "Never add comments to deleted code blocks" and "Avoid adding comments where not necessary".

🤖 Prompt for 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.

In `@src/bun_core/deprecated.rs` around lines 101 - 113, Remove the deleted-code
rationale comment block that begins "The Rust port of `std.DoublyLinkedList` /
`DoublyLinkedNode` was removed..." so the module only contains current
surface-level documentation, not historical justification; delete that entire
multi-line explanatory paragraph (the block about Miri/Stacked Borrows, callers,
and future intrusive-list suggestions) and leave no leftover references to the
removed type in deprecated.rs.

@Dicklesworthstone
Copy link
Copy Markdown
Author

Thanks for the careful review @coderabbitai — going through your findings:

Addressed (already in this PR):

  • src/errno/linux_errno.rs:193 — unknown-errno fall-through to SUCCESS removed in dc20be9 (you marked this resolved). ✓

Not yet addressed — tracking for a follow-up PR:

  • src/ast/nodes.rs:343StoreSlice<T> Copy + slice_mut(self) aliasing. You're right that the unsafe impl Send/Sync bound narrowing in this PR doesn't close the aliasing hole — Copy on the handle plus a self-by-value slice_mut lets two copies hand out independent &mut [T]. The fix is to drop Copy or change slice_mut to &mut self, and it touches every StoreSlice consumer; it's substantive enough to warrant its own PR rather than expanding this one.
  • src/bun_core/atomic_cell.rs:65AtomicCell<T> missing T: Send on the auto-trait unsafe impl Send/Sync. Same posture: real soundness bug, real fix, deserves its own focused PR.

Style (deferred):

  • src/bun_core/deprecated.rs:113 — deletion-rationale block per the project's "no comments on deleted code" guideline. Will fold this into the follow-up.

The two critical findings are filed in my audit-followup tracking. Will open separate PRs for each.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Tip

For best results, initiate chat on the files or code changes.

@Dicklesworthstone: Thanks for the thorough triage — the scope reasoning is solid. Keeping the aliasing and T: Send fixes in separate, focused PRs is the right call given their reach.

Quick summary of where things stand:

Finding Status
linux_errno.rs:193 — unknown-errno fall-through ✅ Addressed (dc20be9)
ast/nodes.rs:343StoreSlice Copy + slice_mut aliasing 🔜 Follow-up PR
atomic_cell.rs:65AtomicCell missing T: Send bound 🔜 Follow-up PR
deprecated.rs:113 — deletion-rationale comment style 🔜 Follow-up PR

Would you like me to open GitHub tracking issues for the two soundness findings (StoreSlice aliasing and AtomicCell<T> missing T: Send) so they're formally tracked and linked back to this PR? Just say the word and I'll create them now.

(ᓀ ᓀ) 🐇


✏️ Learnings added
Learnt from: Dicklesworthstone
URL: https://github.com/oven-sh/bun/pull/30924

Timestamp: 2026-05-19T19:09:29.028Z
Learning: In the Bun repository (oven-sh/bun), `StoreSlice<T>` in `src/ast/nodes.rs` has a known soundness hole: `Copy` on the handle combined with a `self`-by-value `slice_mut` allows two copies to independently hand out `&mut [T]`, creating aliased mutable references. The intended fix (either drop `Copy` or change `slice_mut` to `&mut self`) was deferred to a follow-up PR because it touches every `StoreSlice` consumer. The `unsafe impl Send/Sync` bound narrowing in PR `#30924` does NOT close this aliasing hole.

Learnt from: Dicklesworthstone
URL: https://github.com/oven-sh/bun/pull/30924

Timestamp: 2026-05-19T19:09:29.028Z
Learning: In the Bun repository (oven-sh/bun), `AtomicCell<T>` in `src/bun_core/atomic_cell.rs` has a known soundness bug: the `unsafe impl Send/Sync` bounds use `T: Atom` but are missing `T: Send`. A non-`Send` type `T` that implements `Atom` can therefore be made cross-thread via `AtomicCell<T>`. This was acknowledged in PR `#30924` review and deferred to a separate follow-up PR.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@Dicklesworthstone
Copy link
Copy Markdown
Author

Yes please @coderabbitai — open the two tracking issues for the StoreSlice aliasing and AtomicCell T: Send findings. Linking them back to this PR will make sure they don't fall off the audit-followup list. Thanks.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

On it — creating both issues now and linking them back to #30924.


🏁 Script executed:

#!/bin/bash

# Issue 1: StoreSlice<T> aliasing soundness bug
gh issue create \
  --repo oven-sh/bun \
  --title "soundness: StoreSlice<T> Copy + slice_mut aliasing allows &mut [T] aliasing" \
  --assignee Dicklesworthstone \
  --body "## Summary

\`StoreSlice<T>\` in \`src/ast/nodes.rs\` has a soundness hole: because \`StoreSlice<T>\` is \`Copy\` and \`slice_mut\` takes \`self\` by value, two copies of the same handle can independently call \`slice_mut\` and each receive a \`&mut [T]\` over the same backing allocation. This produces aliased mutable references, which is immediate undefined behaviour.

The \`unsafe impl Send/Sync\` bound-narrowing landed in `#30924` (EXP-019) does **not** close this hole.

## Reproduction sketch

\`\`\`rust
let a: StoreSlice<Foo> = …;
let b = a;                    // Copy — both name the same slice
let r1 = a.slice_mut(store);  // &mut [Foo]
let r2 = b.slice_mut(store);  // &mut [Foo] — same memory → UB
\`\`\`

## Proposed fix

Either:
- Remove the \`Copy\` impl on \`StoreSlice<T>\`, or
- Change \`slice_mut\` to take \`&mut self\` instead of \`self\`.

Both options require updating every \`StoreSlice\` consumer, so this warrants a dedicated PR.

## References

- Identified during UB audit — see `#30903`
- Partial mitigation (Send/Sync bounds) landed in `#30924` (EXP-019)
- Raised by `@coderabbitai` code review on `#30924`
- Requested by `@Dicklesworthstone`"

# Issue 2: AtomicCell<T> missing T: Send bound
gh issue create \
  --repo oven-sh/bun \
  --title "soundness: AtomicCell<T> unsafe impl Send/Sync missing T: Send bound" \
  --assignee Dicklesworthstone \
  --body "## Summary

\`AtomicCell<T>\` in \`src/bun_core/atomic_cell.rs\` bounds its \`unsafe impl Send\` and \`unsafe impl Sync\` on \`T: Atom\`, but is missing the required \`T: Send\` constraint. As a result, a non-\`Send\` type \`T\` that implements \`Atom\` can be made cross-thread via \`AtomicCell<T>\`, violating Rust's aliasing and thread-safety guarantees.

The \`T: Atom\` bound narrowing landed in `#30924` (EXP-098) is the correct direction but is insufficient on its own.

## Proposed fix

\`\`\`rust
// current
unsafe impl<T: Atom> Send for AtomicCell<T> {}
unsafe impl<T: Atom> Sync for AtomicCell<T> {}

// correct
unsafe impl<T: Atom + Send> Send for AtomicCell<T> {}
unsafe impl<T: Atom + Send> Sync for AtomicCell<T> {}
\`\`\`

## References

- Identified during UB audit — see `#30903`
- Partial mitigation (T: Atom bounds) landed in `#30924` (EXP-098)
- Raised by `@coderabbitai` code review on `#30924`
- Requested by `@Dicklesworthstone`"

Length of output: 154


The assignee setting failed due to integration permissions — let me check whether the issues themselves were created successfully.


🏁 Script executed:

#!/bin/bash
# Find the two newly created issues
gh issue list \
  --repo oven-sh/bun \
  --search "soundness: StoreSlice" \
  --json number,title,url \
  --limit 5

gh issue list \
  --repo oven-sh/bun \
  --search "soundness: AtomicCell" \
  --json number,title,url \
  --limit 5

Length of output: 459


@Dicklesworthstone: Both issues are live:

Finding Issue
StoreSlice<T> Copy + slice_mut aliasing #31088
AtomicCell<T> missing T: Send bound #31089

Both are linked back to this PR (#30924) and #30903 in their bodies. The integration couldn't set the assignee due to a permissions restriction on oven-sh/bun — you'll need to self-assign each issue manually.

One heads-up: there's a pre-existing issue #30802 ("StoreSlice Send/Sync missing T bounds") that tracks the Send/Sync bound gap specifically. #31088 covers the distinct Copy + slice_mut aliasing hole, so the two are complementary rather than duplicates — worth a quick cross-link between them if you haven't already.

(ᓀ ᓀ) 🐇

PR oven-sh#31090 now owns the AtomicCell Send/Sync soundness follow-up raised from oven-sh#30924 review. Drop the partial EXP-098 change from this mixed audit PR so reviewers do not see two overlapping AtomicCell fixes.
@Dicklesworthstone
Copy link
Copy Markdown
Author

De-overlap update: removed the partial EXP-098 AtomicCell change from this PR in 7f5e55f. The PR diff no longer touches src/bun_core/atomic_cell.rs; the PR body now marks EXP-098 as moved out. The remaining AtomicCell soundness issue is tracked by #31089 / #30840 and handled by the focused follow-up #31090.

@Dicklesworthstone
Copy link
Copy Markdown
Author

Update — both deferred soundness findings are now fixed in dedicated PRs:

  • StoreSlice Copy + slice_mut aliasingfix(ast): close StoreSlice<T> slice_mut aliasing hole #31092

    • Signature change: `slice_mut(self) -> &'a mut [T]` → `slice_mut(&mut self) -> &mut [T]`.
    • Brings it into structural alignment with `StoreRef::deref_mut` (the doc already claimed they "share one safety story" — they didn't, until now).
    • 8 mechanical call-site updates. Decorators + transpiler tests pass.
  • AtomicCell missing T: Send boundfix(bun_core): gate AtomicCell Send/Sync on AtomSend marker trait #31093

    • Introduces `pub unsafe trait AtomSend: Atom {}` marker and gates the Send/Sync auto-traits on it.
    • `unsafe_impl_atom!` macro emits both Atom + AtomSend (in-tree usage unchanged); pointer specializations get hand-rolled AtomSend (mirrors AtomicPtr's unconditional Send + Sync); hand-rolled `unsafe impl Atom for FunkyType` no longer auto-derives — implementor must explicitly opt in to cross-thread carriage.
    • Standalone Miri-adjacent witness at `/tmp/atomicell-witness/` proves a downstream `unsafe impl Atom for NotSend` now fails the `Send` check at compile time. Pre-fix it silently compiled.

(@coderabbitai if you still want to open formal tracking issues, feel free — but these PRs supersede the followup-tracking-issue plan; tracking is via the PRs themselves now.)

The `deprecated.rs:113` style cleanup remains the only deferred item from your review, and I'll fold it into a small follow-up if it's not already covered elsewhere.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Tip

For best results, initiate chat on the files or code changes.

@Dicklesworthstone: Excellent — the audit trail is now complete for the two critical findings. Updated status:

Finding Status
linux_errno.rs:193 — unknown-errno fall-through ✅ Fixed (dc20be9, this PR)
ast/nodes.rs:343StoreSlice Copy + slice_mut aliasing ✅ Fixed (#31092slice_mut(&mut self))
atomic_cell.rs:65AtomicCell missing T: Send bound ✅ Fixed (#31093AtomSend marker trait)
deprecated.rs:113 — deletion-rationale comment style 🔜 Follow-up (deferred)

The AtomSend marker approach in #31093 is particularly clean — it forces explicit opt-in for cross-thread carriage rather than silently inheriting it from Atom, which is exactly the right discipline for an unsafe trait boundary.

Tracking issues #31088 and #31089 are now superseded by the PRs. Would you like me to close them (or add cross-reference comments pointing to #31092/#31093) to keep the issue tracker tidy?

(ᓀ ᓀ) 🐇


✏️ Learnings added
Learnt from: Dicklesworthstone
URL: https://github.com/oven-sh/bun/pull/30924

Timestamp: 2026-05-19T20:00:25.458Z
Learning: In the Bun repository (oven-sh/bun), the `StoreSlice<T>` Copy + `slice_mut` aliasing soundness hole (EXP-019, tracked in PR `#30924` review) was fixed in PR `#31092` by changing the signature from `slice_mut(self) -> &'a mut [T]` to `slice_mut(&mut self) -> &mut [T]`, with 8 mechanical call-site updates. Tracking issue `#31088` was superseded by that PR.

Learnt from: Dicklesworthstone
URL: https://github.com/oven-sh/bun/pull/30924

Timestamp: 2026-05-19T20:00:25.458Z
Learning: In the Bun repository (oven-sh/bun), the `AtomicCell<T>` missing `T: Send` soundness bug (EXP-098, tracked in PR `#30924` review) was fixed in PR `#31093` by introducing a new `pub unsafe trait AtomSend: Atom {}` marker trait and gating `unsafe impl Send/Sync` for `AtomicCell<T>` on `T: AtomSend`. The `unsafe_impl_atom!` macro was updated to emit both `Atom` and `AtomSend`; hand-rolled `unsafe impl Atom for FunkyType` no longer auto-derives `AtomSend` — implementors must explicitly opt in. Tracking issue `#31089` was superseded by that PR.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Dicklesworthstone pushed a commit to Dicklesworthstone/bun that referenced this pull request May 19, 2026
The previous bound `unsafe impl<T: Copy> Send/Sync for AtomicCell<T>` was
too broad: any external `unsafe impl Atom for SomeCopyButNotSendType`
would auto-derive `AtomicCell<ThatType>: Send + Sync`, which would let
`load`/`swap`/`compare_exchange` hand owned `T` values across threads
through a shared `&AtomicCell<T>` reference. Audit witness in the
CodeRabbit review of oven-sh#30924 (src/bun_core/atomic_cell.rs:65).

Introduce a new marker trait:

    pub unsafe trait AtomSend: Atom {}

and tighten the bounds to `<T: AtomSend>`. `AtomSend` separates "OK as
an atomic representation" (which `Atom` already guaranteed) from "OK to
hand owned `T` between threads via atomic load/store" — the latter is
what AtomicCell's auto-traits actually need.

- The `unsafe_impl_atom!` macro now emits both `Atom` and `AtomSend`,
  matching the macro's existing safety contract (caller asserts Self is
  fully POD-shaped and the cross-thread story matches the underlying
  atomic word's semantics). In-tree macro users (`bool`, `char`, the
  integer/float scalars, `Level`, `Winsize`) all keep `AtomicCell<Self>:
  Send + Sync` with no change.
- The pointer specializations (`*mut U`, `*const U`, `Option<NonNull<U>>`)
  manually implement `AtomSend`, matching `AtomicPtr<U>: Send + Sync`
  unconditionally — the atomic op makes the address transfer
  well-defined, and dereferencing remains the caller's hazard.
- Hand-rolled `unsafe impl Atom for FunkyType` (without using the macro)
  no longer auto-derives Send/Sync on AtomicCell. The implementor must
  now explicitly add `unsafe impl AtomSend for FunkyType` to opt in,
  forcing them to consider the cross-thread story separately from the
  atomic-representation story.

Verified:

- `cargo check --workspace` clean
- `cargo check --tests -p bun_core` clean
- Added compile-time test `atomic_cell_atomsend_marker_is_required_for_send`
  asserting the standard usages still pass through (`AtomicCell<u64>`,
  `AtomicCell<*mut u8>`, `AtomicCell<Option<NonNull<u8>>>`)
- Standalone witness at `/tmp/atomicell-witness/`: a downstream
  `unsafe impl Atom for NotSend` (where `NotSend: Copy + !Send`) without
  a paired `AtomSend` impl now FAILS to compile through
  `assert_send::<AtomicCell<NotSend>>()` with
  `the trait bound NotSend: AtomSend is not satisfied`. Pre-fix the same
  code compiled, exposing the hole CodeRabbit flagged.

Refs oven-sh#30903, oven-sh#30924.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Dicklesworthstone
Copy link
Copy Markdown
Author

Correction on my earlier reply (the one starting "Thanks for the careful review @coderabbitai…") — I said `deprecated.rs:113` was still deferred, but I missed that the relevant code was already deleted in `781811cbdd` bun_core: delete unused DoublyLinkedList (audit EXP-094, Miri SB UB)` two days prior. The deletion is a stronger fix than the comment cleanup you flagged: the test was hitting a Miri Stacked Borrows UB (raw-pointer tag invalidation when `&mut node` reborrows occurred between insertion and traversal), so removing the whole unused type closes the audit finding entirely rather than just tidying the rationale comment.

So the actual status on this PR's three CodeRabbit findings:

Finding Status
`linux_errno.rs:193` — unknown-errno fall-through ✅ `dc20be9`
`ast/nodes.rs:343` — `StoreSlice` aliasing ✅ moved to its own PR #31092 (now with `slice_mut` unsafe + tightened doc)
`atomic_cell.rs:65` — `AtomicCell` Send/Sync bound ✅ moved to its own PR #31093 (now with `AtomSend` marker + macro split + negative compile-time test)
`deprecated.rs:113` — deletion-rationale comment ✅ `781811cbdd` — type itself deleted (Miri-witnessed UB in the unit test, audit EXP-094)

Nothing actually deferred — apologies for the earlier mis-call.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Tip

For best results, initiate chat on the files or code changes.

@Dicklesworthstone: Correction noted and appreciated — the deletion is indeed the right outcome. Removing the entire unused type is strictly stronger than a comment cleanup; there's nothing left to tidy once the code is gone, and it closes the Miri Stacked Borrows finding at the source. No apology needed for the earlier mis-call either — that kind of audit-trail housekeeping is exactly what keeps a multi-PR fix sequence coherent.

Updated final status for all CodeRabbit findings on this PR:

Finding Fix Status
linux_errno.rs:193 — unknown-errno fall-through dc20be9
ast/nodes.rs:343StoreSlice Copy + slice_mut aliasing #31092 (slice_mut(&mut self))
atomic_cell.rs:65AtomicCell missing T: Send bound #31093 (AtomSend marker trait)
deprecated.rs:113 — deletion-rationale comment / EXP-094 Miri SB UB 781811cbdd (type deleted entirely)

Everything is resolved. 🐇


✏️ Learnings added
Learnt from: Dicklesworthstone
URL: https://github.com/oven-sh/bun/pull/30924

Timestamp: 2026-05-19T21:03:44.745Z
Learning: In the Bun repository (oven-sh/bun), the `deprecated.rs:113` finding (EXP-094, Miri Stacked Borrows UB) was fully closed in commit `781811cbdd` by deleting the entire unused `DoublyLinkedList<T>` / `DoublyLinkedNode<T>` type from `src/bun_core/deprecated.rs`. The UB stemmed from raw-pointer tag invalidation when `&mut node` reborrows occurred between insertion and traversal. Deletion is the complete fix; no comment-style cleanup follow-up is needed. This was confirmed in PR `#30924`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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