Repository navigation
Bun.password: fix UAF when worker.terminate() lands mid argon2/bcrypt job #35156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
d795b86
Bun.password: gate the pool-thread completion enqueue on worker termi…
robobun 092a1a7
clippy: ManuallyDrop instead of mem::forget for leaked promise handle
robobun 61bab23
test: wire worker error/exit to reject the readiness promise
robobun a23e86f
AnyTask: add dispose slot so the worker-terminate drain reclaims queu…
robobun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| //! A counted guest gate protecting an allocation shared with other threads. | ||
| //! | ||
| //! Guests bracket every access to the protected memory with `enter()`/ | ||
| //! `leave()`. The owner calls `close_and_wait()` before freeing the memory: | ||
| //! it refuses new guests, then blocks until every guest inside has left. | ||
| //! The gate itself must live OUTSIDE the protected allocation (e.g. in an | ||
| //! `Arc` cloned by each guest) so that a late `enter()` after the close is a | ||
| //! safe "gate closed" answer instead of a use-after-free. | ||
|
|
||
| use core::sync::atomic::{AtomicU32, Ordering}; | ||
|
|
||
| use crate::futex; | ||
|
|
||
| /// Bit 0: closed. Remaining bits: number of guests currently inside ×2. | ||
| const CLOSED: u32 = 1; | ||
| const GUEST: u32 = 2; | ||
|
|
||
| #[derive(Default)] | ||
| pub struct ShutdownGate { | ||
| state: AtomicU32, | ||
| } | ||
|
|
||
| impl ShutdownGate { | ||
| pub const fn new() -> Self { | ||
| Self { | ||
| state: AtomicU32::new(0), | ||
| } | ||
| } | ||
|
|
||
| /// Try to enter the gate. Returns `false` if the gate is closed — the | ||
| /// protected memory may already be freed and must not be touched. | ||
| #[must_use] | ||
| pub fn enter(&self) -> bool { | ||
| // Optimistic add, undo on closed: `close_and_wait` tolerates the | ||
| // transient count because it re-reads state until it settles. | ||
| let prev = self.state.fetch_add(GUEST, Ordering::Acquire); | ||
| if prev & CLOSED == 0 { | ||
| return true; | ||
| } | ||
| self.leave(); | ||
| false | ||
| } | ||
|
|
||
| /// Leave the gate. Must pair with an `enter()` that returned `true` (or | ||
| /// the internal undo above). After this call the guest must not touch the | ||
| /// protected memory again. | ||
| pub fn leave(&self) { | ||
| let prev = self.state.fetch_sub(GUEST, Ordering::Release); | ||
| if prev == CLOSED | GUEST { | ||
| // Last guest out of a closed gate: wake `close_and_wait` (all | ||
| // waiters — `close_and_wait` is callable from several owners). | ||
| futex::wake(&self.state, u32::MAX); | ||
| } | ||
| } | ||
|
|
||
| /// Set the CLOSED bit without waiting for guests. Only sound when the | ||
| /// protected memory is never actually freed (the main VM's box lives for | ||
| /// the process) — new guests are refused, in-flight ones finish on their | ||
| /// own time. | ||
| pub fn close_without_waiting(&self) { | ||
| self.state.fetch_or(CLOSED, Ordering::AcqRel); | ||
| } | ||
|
|
||
| /// Close the gate and block until every guest has left. After this | ||
| /// returns, no guest is inside and none can enter; the protected memory | ||
| /// may be freed. Idempotent; must never be called from a guest section. | ||
| pub fn close_and_wait(&self) { | ||
| let mut state = self.state.fetch_or(CLOSED, Ordering::AcqRel) | CLOSED; | ||
| while state != CLOSED { | ||
| let _ = futex::wait(&self.state, state, None); | ||
| state = self.state.load(Ordering::Acquire); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use std::sync::Arc; | ||
| use std::sync::atomic::AtomicUsize; | ||
|
|
||
| #[test] | ||
| fn close_and_wait_drains_racing_guests() { | ||
| for _ in 0..64 { | ||
| let gate = Arc::new(ShutdownGate::new()); | ||
| let inside = Arc::new(AtomicUsize::new(0)); | ||
| let guests: Vec<_> = (0..8) | ||
| .map(|_| { | ||
| let (gate, inside) = (Arc::clone(&gate), Arc::clone(&inside)); | ||
| std::thread::spawn(move || { | ||
| for _ in 0..500 { | ||
| if !gate.enter() { | ||
| return; | ||
| } | ||
| inside.fetch_add(1, Ordering::SeqCst); | ||
| std::hint::spin_loop(); | ||
| inside.fetch_sub(1, Ordering::SeqCst); | ||
| gate.leave(); | ||
| } | ||
| }) | ||
| }) | ||
| .collect(); | ||
| // A second concurrent closer: close_and_wait must be callable | ||
| // from several owners and both must drain. | ||
| let second_closer = { | ||
| let gate = Arc::clone(&gate); | ||
| std::thread::spawn(move || gate.close_and_wait()) | ||
| }; | ||
| gate.close_and_wait(); | ||
| // After close_and_wait returns, no guest is inside and none can enter. | ||
| assert_eq!(inside.load(Ordering::SeqCst), 0); | ||
| assert!(!gate.enter()); | ||
| gate.close_and_wait(); // idempotent | ||
| second_closer.join().unwrap(); | ||
| for g in guests { | ||
| g.join().unwrap(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn enter_is_rejected_while_a_guest_holds_the_gate_closed() { | ||
| let gate = Arc::new(ShutdownGate::new()); | ||
| assert!(gate.enter()); | ||
| let closer = { | ||
| let gate = Arc::clone(&gate); | ||
| std::thread::spawn(move || gate.close_and_wait()) | ||
| }; | ||
| // The closer sets CLOSED immediately; wait until new entries bounce. | ||
| while gate.enter() { | ||
| gate.leave(); | ||
| std::hint::spin_loop(); | ||
| } | ||
| // Release the in-flight guest so the blocked closer drains. | ||
| gate.leave(); | ||
| closer.join().unwrap(); | ||
| assert!(!gate.enter()); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.