Skip to content

process: restore blocking stdio before execve - #66985

Open
AdamMagued wants to merge 1 commit into
nodejs:mainfrom
AdamMagued:fix-issue-66824-execve-blocking-stdio
Open

AdamMagued wants to merge 1 commit into
nodejs:mainfrom
AdamMagued:fix-issue-66824-execve-blocking-stdio

Conversation

@AdamMagued

Copy link
Copy Markdown

Description

When Node.js initializes stdio streams (such as evaluating process.stdout or process.stderr), libuv sets O_NONBLOCK on the underlying open file descriptions for standard descriptors 0, 1, and 2.

In src/node_process_methods.cc, Execve() clears FD_CLOEXEC on file descriptors 0-2 so they persist across execve(2). However, Execve() bypasses the normal process exit sequence that invokes ResetStdio(). Consequently, the replacement binary executed via process.execve() inherits file descriptors 0-2 in non-blocking mode. Programs assuming blocking stdio (such as python3, cat, or yes) fail with EAGAIN (Resource temporarily unavailable) once a pipe buffer fills up.

This change:

  1. Clears O_NONBLOCK on standard streams (descriptors 0, 1, and 2) before calling execve(2), matching the blocking stdio behavior provided to child processes in child_process.spawn().
  2. Restores the original F_GETFL file status flags alongside FD_CLOEXEC if execve(2) fails, preserving the invariant that a failed process.execve() leaves no observable side effects before throwing to JavaScript.
  3. Adds automated regression tests in test/parallel/test-process-execve-stdio-blocking.js verifying that a program executed via process.execve() receives blocking stdio across fds 0-2 and completes writes that exceed pipe buffer capacity without EAGAIN.

Fixes: #66824

When Node.js initializes stdio handles (such as process.stdout or
process.stderr), libuv configures O_NONBLOCK on the open file
descriptions for fds 0, 1, and 2. Because process.execve() replaces
the process image without invoking the standard exit cleanup path
(which triggers ResetStdio()), the executed binary inherits fds 0-2
in non-blocking mode. Programs assuming blocking stdio fail with EAGAIN
or BlockingIOError when writes exceed pipe capacity.

Clear O_NONBLOCK on standard descriptors 0, 1, and 2 before invoking
execve(2), matching the behavior of child_process.spawn(). If execve(2)
subsequently fails, restore the previous file status flags alongside
the file descriptor close-on-exec flags so no side effects remain.

Fixes: nodejs#66824
Signed-off-by: AdamMagued <adamismailmageud@gmail.com>
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Oct 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Node.js, and thank you for your first contribution!

Before review, please take a moment to read:

Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal.

Caution

AgentScan found account activity patterns that may be consistent with automation. This is a heuristic, not proof that this pull request was opened by an agent or violates policy. AI-assisted contributions are permitted, but automated tooling must not open pull requests without advance approval, and contributors must personally understand, test, verify, and take responsibility for every submitted change. See the AgentScan analysis, AI use policy, and automation policy for additional context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentscan:mixed-signals c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

process.execve() passes non-blocking stdio to the new program

2 participants