-
Notifications
You must be signed in to change notification settings - Fork 4.6k
spawn: don't forward SIGPWR on Linux #30983
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
+31
−2
Closed
Changes from all commits
Commits
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟣 Pre-existing issue, but same bug class as the SIGPWR fix and in the macro being edited:
SIGIOTis an alias forSIGABRT(everywhere) andSIGPOLLis an alias forSIGIO(Linux), soFOR_EACH_SIGNALexpandsREGISTER_SIGNALtwice for the same signal number. The secondsigaction()call overwritesprevious_actions[N]with the just-installed forwarding handler, soBun__unregisterSignalsForForwarding"restores" the SA_RESETHAND forwarder instead of the original — after everyspawnSync, any user-installedSIGIO/SIGABRThandler is permanently lost. DroppingSIGIOTfromFOR_EACH_POSIX_SIGNALandSIGPOLLfromFOR_EACH_LINUX_ONLY_SIGNALwould fix it.Extended reasoning...
What the bug is
On Linux,
SIGIOTis#defined toSIGABRT(both signal 6) andSIGPOLLis#defined toSIGIO(both signal 29). On Darwin and FreeBSD,SIGIOT == SIGABRTas well. YetFOR_EACH_POSIX_SIGNALlists bothSIGABRTandSIGIOT(andSIGIO), andFOR_EACH_LINUX_ONLY_SIGNAL— the macro this PR edits — still listsSIGPOLL. SoFOR_EACH_SIGNAL(REGISTER_SIGNAL)callssigaction()twice for signal 6, and on Linux twice for signal 29.The code path that triggers it
REGISTER_SIGNAL(SIG)expands tosigaction(SIG, &sa, &previous_actions[SIG]). The third argument is the output slot where the kernel writes the previously-installed handler. When the macro expands twice for the same signum:REGISTER_SIGNAL(SIGABRT)→sigaction(6, &sa, &previous_actions[6])— installs the forwarding handler, saves the original handler intoprevious_actions[6].REGISTER_SIGNAL(SIGIOT)→sigaction(6, &sa, &previous_actions[6])— installs the forwarding handler again (no-op), but now saves the forwarding handler from step 1 intoprevious_actions[6], overwriting the original.The same happens for
SIGIO(inFOR_EACH_POSIX_SIGNAL) followed bySIGPOLL(inFOR_EACH_LINUX_ONLY_SIGNAL) on Linux.Then
Bun__unregisterSignalsForForwardingdoessigaction(SIG, &previous_actions[SIG], NULL)for each signal — for 6 and 29, this "restores" the forwarding handler (withSA_RESETHAND) instead of whatever was there beforespawnSync.Why existing code doesn't prevent it
The npm signal list this was copied from is a JS array of string names iterated with
process.on(name, ...); Node tolerates duplicate names because the secondprocess.onjust adds another listener. But this C++ implementation indexesprevious_actions[]by signal number, so aliased names collide on the same array slot. There's no dedup check — the secondsigaction()for the same number silently clobbers the saved original.Impact
After every
spawnSynccompletes, the process is left with a one-shot (SA_RESETHAND) forwarding handler forSIGABRTand (on Linux)SIGIO, instead of the original disposition. WithBun__currentSyncPID == 0post-spawn, the next delivery of either signal is silently swallowed (the lambda just setsBun__pendingSignalToSendand returns), thenSA_RESETHANDresets it toSIG_DFL. Concretely: a user who installsprocess.on('SIGIO', ...)orprocess.on('SIGABRT', ...)loses that handler permanently after the firstspawnSynccall.Step-by-step proof
process.on('SIGIO', handler)— libuv installs a handler for signal 29.Bun.spawnSync(...)→Bun__registerSignalsForForwarding():M(SIGIO)→sigaction(29, &fwd, &previous_actions[29])—previous_actions[29]= libuv's handler. ✓M(SIGPOLL)→sigaction(29, &fwd, &previous_actions[29])—previous_actions[29]= the forwarding handler. ✗Bun__unregisterSignalsForForwarding():M(SIGIO)→sigaction(29, &previous_actions[29], NULL)— installs the forwarding handler (not libuv's).M(SIGPOLL)→ same again.memset(previous_actions, 0, ...).SIGIOto the process. Forwarding lambda runs withBun__currentSyncPID == 0, setsBun__pendingSignalToSend = 29, returns.SA_RESETHANDresets signal 29 toSIG_DFL. The user's handler never fires.SIGIOhitsSIG_DFL(ignored on Linux). The user's handler is gone for good.The
SIGABRT/SIGIOTpair behaves identically on all three POSIX platforms.How to fix
Drop the aliased duplicates: remove
M(SIGIOT)fromFOR_EACH_POSIX_SIGNAL(it'sSIGABRTeverywhere we build) and removeM(SIGPOLL)fromFOR_EACH_LINUX_ONLY_SIGNAL(it'sSIGIOon Linux). Alternatively, guardREGISTER_SIGNALto skip a signum whoseprevious_actions[SIG]slot has already been populated this round.Relationship to this PR
This is pre-existing — not introduced here. But it's the identical
previous_actions[]corruption class the PR is fixing forSIGPWR, and the PR editsFOR_EACH_LINUX_ONLY_SIGNAL(which contains theSIGPOLLduplicate) directly, so it's worth flagging as a natural follow-up while this code is being touched.