GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||
GeezerVison is showing github.com — process: throw on execve(2) failure instead of aborting by bengl · Pull Request #62878 · nodejs/node Conversation
Member
When the underlying execve(2) system call fails, process.execve() previously printed an error to stderr and called ABORT(), preventing JS code from detecting or recovering from common failures such as a missing binary. Throw an ErrnoException instead, carrying the usual code, errno, syscall, and path properties. To leave the process in a clean state when execve(2) fails, no longer run native AtExit callbacks before the call (their in-memory effects are discarded on success anyway), and snapshot and restore the FD_CLOEXEC flags on stdio so a failed call has no observable side effects. Rename and update test-process-execve-abort.js accordingly and document the new behaviour.
Sorry, something went wrong. Uh oh!
There was an error while loading. Please reload this page. All reactions
|
There was an error while loading. Please reload this page.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@ ## main #62878 +/- ## ========================================== - Coverage 89.62% 89.62% -0.01% ========================================== Files 706 706 Lines 219165 219170 +5 Branches 41989 42000 +11 ========================================== + Hits 196435 196439 +4 + Misses 14621 14615 -6 - Partials 8109 8116 +7
🚀 New features to boost your workflow:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
When the underlying execve(2) system call fails, process.execve() previously printed an error to stderr and called ABORT(), preventing JS code from detecting or recovering from common failures such as a missing binary. Throw an ErrnoException instead, carrying the standard code, errno, syscall, and path properties. To leave the process in a clean state when execve(2) fails, no longer run native AtExit callbacks before the call (their in-memory effects are discarded on success anyway), and snapshot and restore the FD_CLOEXEC flags on stdio so a failed call has no observable side effects. Rename and update test-process-execve-abort.js accordingly and document the new behavior. Signed-off-by: Bryan English <[email protected]>
55e9403 to
55e4b8f
Compare
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@jasnell adding you as a reviewer since you had the initial suggestion to just abort when execve fails. The approach I took here is a bit of a departure, since RunAtExit is no longer called. I think this approach makes sense because the process isn't so much "exiting" as being replaced entirely, as is the nature of execve. The benefit of being able to recover from |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
lgtm
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Landed in 0a9b67e |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
When the underlying execve(2) system call fails, process.execve() previously printed an error to stderr and called ABORT(), preventing JS code from detecting or recovering from common failures such as a missing binary. Throw an ErrnoException instead, carrying the standard code, errno, syscall, and path properties. To leave the process in a clean state when execve(2) fails, no longer run native AtExit callbacks before the call (their in-memory effects are discarded on success anyway), and snapshot and restore the FD_CLOEXEC flags on stdio so a failed call has no observable side effects. Rename and update test-process-execve-abort.js accordingly and document the new behavior. Signed-off-by: Bryan English <[email protected]> PR-URL: #62878 Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
Notable changes: buffer: * (SEMVER-MINOR) add `end` parameter (Robert Nagy) #62390 crypto: * (SEMVER-MINOR) accept key data in `crypto.diffieHellman()` and cleanup DH jobs (Filip Skokan) #62527 * (SEMVER-MINOR) implement `randomUUIDv7()` (nabeel378) #62553 debugger: * (SEMVER-MINOR) add edit-free runtime expression probes to `node inspect` (Joyee Cheung) #62713 fs: * (SEMVER-MINOR) add `signal` option to `fs.stat()` (Mert Can Altin) #57775 * (SEMVER-MINOR) expose frsize field in statfs (Jinho Jang) #62277 http: * (SEMVER-MINOR) harden `ClientRequest` options merge (Matteo Collina) #63082 * (SEMVER-MINOR) add `req.signal` to `IncomingMessage` (Akshat) #62541 lib,src,test,doc: * (SEMVER-MINOR) add `node:ffi` module (Colin Ihrig) #62072 process: * (SEMVER-MINOR) throw on `execve(2)` failure instead of aborting (Bryan English) #62878 src: * (SEMVER-MINOR) allow empty `--experimental-config-file` (Marco Ippolito) #61610 stream: * (SEMVER-MINOR) propagate destruction in `duplexPair` (Ahmed Elhor) #61098 test_runner: * (SEMVER-MINOR) align mock timeout api (sangwook) #62820 * (SEMVER-MINOR) add mock-timers support for `AbortSignal.timeout` (DeveloperViraj) #60751 * (SEMVER-MINOR) support test order randomization (Pietro Marchini) #61747 util: * (SEMVER-MINOR) colorize text with hex colors (Guilherme Araújo) #61556 PR-URL: #63137
Notable changes: buffer: * (SEMVER-MINOR) add `end` parameter (Robert Nagy) #62390 crypto: * (SEMVER-MINOR) accept key data in `crypto.diffieHellman()` and cleanup DH jobs (Filip Skokan) #62527 * (SEMVER-MINOR) implement `randomUUIDv7()` (nabeel378) #62553 debugger: * (SEMVER-MINOR) add edit-free runtime expression probes to `node inspect` (Joyee Cheung) #62713 fs: * (SEMVER-MINOR) add `signal` option to `fs.stat()` (Mert Can Altin) #57775 * (SEMVER-MINOR) expose frsize field in statfs (Jinho Jang) #62277 http: * (SEMVER-MINOR) harden `ClientRequest` options merge (Matteo Collina) #63082 * (SEMVER-MINOR) add `req.signal` to `IncomingMessage` (Akshat) #62541 lib,src,test,doc: * (SEMVER-MINOR) add `node:ffi` module (Colin Ihrig) #62072 process: * (SEMVER-MINOR) throw on `execve(2)` failure instead of aborting (Bryan English) #62878 src: * (SEMVER-MINOR) allow empty `--experimental-config-file` (Marco Ippolito) #61610 stream: * (SEMVER-MINOR) propagate destruction in `duplexPair` (Ahmed Elhor) #61098 test_runner: * (SEMVER-MINOR) align mock timeout api (sangwook) #62820 * (SEMVER-MINOR) add mock-timers support for `AbortSignal.timeout` (DeveloperViraj) #60751 * (SEMVER-MINOR) support test order randomization (Pietro Marchini) #61747 util: * (SEMVER-MINOR) colorize text with hex colors (Guilherme Araújo) #61556 PR-URL: #63137
Notable changes: buffer: * (SEMVER-MINOR) add `end` parameter (Robert Nagy) #62390 crypto: * (SEMVER-MINOR) accept key data in `crypto.diffieHellman()` and cleanup DH jobs (Filip Skokan) #62527 * (SEMVER-MINOR) implement `randomUUIDv7()` (nabeel378) #62553 debugger: * (SEMVER-MINOR) add edit-free runtime expression probes to `node inspect` (Joyee Cheung) #62713 fs: * (SEMVER-MINOR) add `signal` option to `fs.stat()` (Mert Can Altin) #57775 * (SEMVER-MINOR) expose frsize field in statfs (Jinho Jang) #62277 http: * (SEMVER-MINOR) harden `ClientRequest` options merge (Matteo Collina) #63082 * (SEMVER-MINOR) add `req.signal` to `IncomingMessage` (Akshat) #62541 lib,src,test,doc: * (SEMVER-MINOR) add `node:ffi` module (Colin Ihrig) #62072 process: * (SEMVER-MINOR) throw on `execve(2)` failure instead of aborting (Bryan English) #62878 src: * (SEMVER-MINOR) allow empty `--experimental-config-file` (Marco Ippolito) #61610 stream: * (SEMVER-MINOR) propagate destruction in `duplexPair` (Ahmed Elhor) #61098 test_runner: * (SEMVER-MINOR) align mock timeout api (sangwook) #62820 * (SEMVER-MINOR) add mock-timers support for `AbortSignal.timeout` (DeveloperViraj) #60751 * (SEMVER-MINOR) support test order randomization (Pietro Marchini) #61747 util: * (SEMVER-MINOR) colorize text with hex colors (Guilherme Araújo) #61556 PR-URL: #63137
### Problem
When the underlying `execve(2)` syscall fails (missing binary,
permission denied, ...), `process.execve()` prints an error to stderr
and calls `abort()`, so JavaScript has no way to observe or recover from
the failure:
```js
try {
process.execve("/does/not/exist", ["/does/not/exist"]);
} catch {
// never reached; the process dies with SIGABRT
}
```
```
process.execve failed with error code ENOENT
SystemError [process.execve]: execve No such file or directory: /does/not/exist
at execve (node:internal/process/per_thread:0:0)
Aborted
```
Node changed this in nodejs/node#62878 (shipped in v26): a failed
`execve(2)` now throws an `ErrnoException` and leaves the process in a
clean, recoverable state.
### Fix
`src/jsc/bindings/BunProcess.cpp` (`Process_functionExecve`):
- Throw Node's `ErrnoException` shape instead of printing and calling
`abort()`:
```
Error: ENOENT, No such file or directory '/does/not/exist'
code: 'ENOENT', errno: 2, syscall: 'execve', path: '/does/not/exist'
```
It is built at the call site rather than through `createSystemError`
because Node's error here is a plain `Error`, not a `SystemError`, and
it carries a `path`.
- `persistStandardStream` now returns the previous `F_GETFD` flags, and
a failed `execve(2)` restores the `FD_CLOEXEC` bits it cleared on fds
0-2, matching Node. A partial failure of the fcntl loop also restores
the fds already changed before throwing.
- The Linux path restores the signal mask it cleared before the call.
The Darwin `posix_spawn(POSIX_SPAWN_SETEXEC)` path has no such mutation.
- The Linux `close_range(CLOSE_RANGE_CLOEXEC)` on fds >= 3 is
Bun-specific hardening Node does not have, and it is intentionally not
undone on failure: `close_range` reports nothing per fd, and
`FD_CLOEXEC` only takes effect at the next `exec`, so the still-running
image is unaffected.
No behavior change on success or on Windows (where `process.execve` is
unavailable).
### Tests
- `test/js/node/test/parallel/test-process-execve-throws.js`: vendored
verbatim from Node (added in the same Node PR), replacing
`test-process-execve-abort.js`, which Node deleted.
- `test/js/node/process/process-execve.test.ts`:
- the old "aborts with ENOENT" test becomes "throws ENOENT", asserting
the full error shape and that the process exits cleanly afterwards
- new test: a caught failure leaves the process able to `execve` a
different, valid path, proving no state was left behind
- `test/js/bun/util/fuzzilli-reprl.test.ts`: dropped
`expect(stderr).not.toContain("process.execve failed")`; that string no
longer exists in the codebase so the assertion can never fail. The
load-bearing assertion (the REPRL loop survives the payload) is
unchanged.
On current `main` both new tests fail because the child aborts (exit
134, empty stdout); with this change all pass, including under
`BUN_JSC_validateExceptionChecks=1`.
Note: #31831 includes the same behavior change as part of a larger Node
v26.3.0 process-compat port. This is the focused standalone version;
happy to close either in favor of the other.
---------
Co-authored-by: Jarred Sumner <[email protected]>
### Problem
When the underlying `execve(2)` syscall fails (missing binary,
permission denied, ...), `process.execve()` prints an error to stderr
and calls `abort()`, so JavaScript has no way to observe or recover from
the failure:
```js
try {
process.execve("/does/not/exist", ["/does/not/exist"]);
} catch {
// never reached; the process dies with SIGABRT
}
```
```
process.execve failed with error code ENOENT
SystemError [process.execve]: execve No such file or directory: /does/not/exist
at execve (node:internal/process/per_thread:0:0)
Aborted
```
Node changed this in nodejs/node#62878 (shipped in v26): a failed
`execve(2)` now throws an `ErrnoException` and leaves the process in a
clean, recoverable state.
### Fix
`src/jsc/bindings/BunProcess.cpp` (`Process_functionExecve`):
- Throw Node's `ErrnoException` shape instead of printing and calling
`abort()`:
```
Error: ENOENT, No such file or directory '/does/not/exist'
code: 'ENOENT', errno: 2, syscall: 'execve', path: '/does/not/exist'
```
It is built at the call site rather than through `createSystemError`
because Node's error here is a plain `Error`, not a `SystemError`, and
it carries a `path`.
- `persistStandardStream` now returns the previous `F_GETFD` flags, and
a failed `execve(2)` restores the `FD_CLOEXEC` bits it cleared on fds
0-2, matching Node. A partial failure of the fcntl loop also restores
the fds already changed before throwing.
- The Linux path restores the signal mask it cleared before the call.
The Darwin `posix_spawn(POSIX_SPAWN_SETEXEC)` path has no such mutation.
- The Linux `close_range(CLOSE_RANGE_CLOEXEC)` on fds >= 3 is
Bun-specific hardening Node does not have, and it is intentionally not
undone on failure: `close_range` reports nothing per fd, and
`FD_CLOEXEC` only takes effect at the next `exec`, so the still-running
image is unaffected.
No behavior change on success or on Windows (where `process.execve` is
unavailable).
### Tests
- `test/js/node/test/parallel/test-process-execve-throws.js`: vendored
verbatim from Node (added in the same Node PR), replacing
`test-process-execve-abort.js`, which Node deleted.
- `test/js/node/process/process-execve.test.ts`:
- the old "aborts with ENOENT" test becomes "throws ENOENT", asserting
the full error shape and that the process exits cleanly afterwards
- new test: a caught failure leaves the process able to `execve` a
different, valid path, proving no state was left behind
- `test/js/bun/util/fuzzilli-reprl.test.ts`: dropped
`expect(stderr).not.toContain("process.execve failed")`; that string no
longer exists in the codebase so the assertion can never fail. The
load-bearing assertion (the REPRL loop survives the payload) is
unchanged.
On current `main` both new tests fail because the child aborts (exit
134, empty stdout); with this change all pass, including under
`BUN_JSC_validateExceptionChecks=1`.
Note: #31831 includes the same behavior change as part of a larger Node
v26.3.0 process-compat port. This is the focused standalone version;
happy to close either in favor of the other.
---------
Co-authored-by: Jarred Sumner <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.