Skip to content

Fix file descriptor leaks and unsafe exec in Async::Exec - #799

Open
MarkRose wants to merge 1 commit into
sm0svx:masterfrom
MarkRose:fix-asyncexec-fd-and-exec-safety
Open

Fix file descriptor leaks and unsafe exec in Async::Exec#799
MarkRose wants to merge 1 commit into
sm0svx:masterfrom
MarkRose:fix-asyncexec-fd-and-exec-safety

Conversation

@MarkRose

Copy link
Copy Markdown
Contributor
  • Exec::closeStdin() closed stdin_fd but never reset it to -1, so the
    destructor would close it a second time, potentially closing an
    unrelated fd that had since been recycled by the OS. closeStdin() now
    sets stdin_fd to -1 after closing (and is a no-op if already closed).
  • Exec::run() leaked the stdin/stdout/stderr pipe file descriptors it
    had already opened whenever a later pipe() or fork() call failed
    (2, 4, or 6 leaked fds depending on which call failed). Added a small
    helper that closes a set of fds and call it on each failure path so
    already-opened pipes are released before returning.
  • Exec::run() did not validate the command line before use. With an
    empty command line, the child process would call execv() with a NULL
    path and later code would read args[0] on an empty vector (undefined
    behavior). run() now rejects an empty command line up front and
    returns false instead of proceeding.

Co-Authored-By: Claude Opus 4.8 [email protected]


This PR also adds a unit test (AsyncExecTest.cpp). It is auto-discovered and executed by the CTest suite proposed in #762 once that is merged; without that suite present the test file is inert and does not affect the build.

- Exec::closeStdin() closed stdin_fd but never reset it to -1, so the
  destructor would close it a second time, potentially closing an
  unrelated fd that had since been recycled by the OS. closeStdin() now
  sets stdin_fd to -1 after closing (and is a no-op if already closed).
- Exec::run() leaked the stdin/stdout/stderr pipe file descriptors it
  had already opened whenever a later pipe() or fork() call failed
  (2, 4, or 6 leaked fds depending on which call failed). Added a small
  helper that closes a set of fds and call it on each failure path so
  already-opened pipes are released before returning.
- Exec::run() did not validate the command line before use. With an
  empty command line, the child process would call execv() with a NULL
  path and later code would read args[0] on an empty vector (undefined
  behavior). run() now rejects an empty command line up front and
  returns false instead of proceeding.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@MarkRose MarkRose closed this Jul 12, 2026
@MarkRose MarkRose reopened this Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant