From f574e9e52be821bf92545884a9a3bef8bee64d04 Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Sat, 8 Aug 2026 17:14:18 +0800 Subject: [PATCH] process: handle closed stdio in execve Treat EBADF while preparing standard file descriptors as an indication that the descriptor is already closed instead of failing execve. Skip closed descriptors during flag restoration, check restoration errors Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> --- src/node_process_methods.cc | 14 ++++++-- .../test-process-execve-closed-stdio.js | 36 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-process-execve-closed-stdio.js diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 1c695be2d5fa..af54f2af53aa 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -584,11 +584,15 @@ static void Execve(const FunctionCallbackInfo& args) { int saved_stdio_flags[3] = {-1, -1, -1}; for (int fd = 0; fd < 3; fd++) { int prev = persist_standard_stream(fd); - if (prev < 0) { + if (prev < 0 && errno != EBADF) { int fcntl_errno = errno; // Undo changes already applied to earlier fds before throwing. for (int j = 0; j < fd; j++) { - fcntl(j, F_SETFD, saved_stdio_flags[j]); + if (saved_stdio_flags[j] >= 0 && + fcntl(j, F_SETFD, saved_stdio_flags[j]) < 0) { + fcntl_errno = errno; + break; + } } env->ThrowErrnoException(fcntl_errno, "fcntl"); return; @@ -610,7 +614,11 @@ static void Execve(const FunctionCallbackInfo& args) { // throw an ErrnoException so JS can catch it. int execve_errno = errno; for (int fd = 0; fd < 3; fd++) { - fcntl(fd, F_SETFD, saved_stdio_flags[fd]); + if (saved_stdio_flags[fd] >= 0 && + fcntl(fd, F_SETFD, saved_stdio_flags[fd]) < 0) { + env->ThrowErrnoException(errno, "fcntl"); + return; + } } env->ThrowErrnoException(execve_errno, "execve", nullptr, *executable); } diff --git a/test/parallel/test-process-execve-closed-stdio.js b/test/parallel/test-process-execve-closed-stdio.js new file mode 100644 index 000000000000..69c128de6625 --- /dev/null +++ b/test/parallel/test-process-execve-closed-stdio.js @@ -0,0 +1,36 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const { closeSync } = require('fs'); +const { isMainThread } = require('worker_threads'); + +if (!isMainThread) { + common.skip('process.execve is not available in Workers'); +} else if (common.isWindows || common.isIBMi) { + common.skip('process.execve is not available in Windows or IBM i'); +} + +if (process.argv[2] === 'child') { + closeSync(Number(process.argv[3])); + process.execve( + process.execPath, + [process.execPath, '-e', ''], + process.env, + ); + assert.fail('process.execve failed'); +} + +for (let fd = 0; fd < 3; fd++) { + const child = spawnSync( + process.execPath, + [__filename, 'child', String(fd)], + ); + assert.ifError(child.error); + assert.strictEqual( + child.status, + 0, + `process.execve failed with fd ${fd} closed: ${child.stderr}`, + ); +}