Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings

Commit 1e0c7ff

Browse filesBrowse the repository at this point in the historyBrowse files
Sjorsclaude
andcommitted
util: Clear FD_CLOEXEC in child instead of parent before fork
SpawnProcess cleared FD_CLOEXEC on the child's socket in the parent before forking. Between that fcntl() and fork(), a concurrent fork+exec on another thread would snapshot the descriptor with the close-on-exec flag already cleared, leaking it into an unrelated child process where it survives exec. A leaked duplicate of the socket also prevents the parent from seeing EOF when the spawned process exits. Instead, keep both descriptors close-on-exec in the parent for their entire lifetime and have the intended child clear the flag on its own copy of the descriptor between fork() and exec(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8550ee6 commit 1e0c7ff
Copy full SHA for 1e0c7ff

1 file changed

+10-5Lines changed: 10 additions & 5 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/mp/util.cpp‎

Copy file name to clipboardExpand all lines: src/mp/util.cpp
+10-5Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@ std::tuple<ProcessId, SocketId> SpawnProcess(SpawnConnectInfoToArgsFn&& connect_
142142
const std::vector<std::string> args{connect_info_to_args(std::to_string(fds[0]))};
143143
const std::vector<char*> argv{MakeArgv(args)};
144144

145-
// Clear FD_CLOEXEC on fds[0] before forking so it survives exec in the child.
146-
int fds0_flags;
147-
KJ_SYSCALL(fds0_flags = fcntl(fds[0], F_GETFD));
148-
KJ_SYSCALL(fcntl(fds[0], F_SETFD, fds0_flags & ~FD_CLOEXEC));
149-
150145
ProcessId pid = fork();
151146
if (pid == -1) {
152147
throw std::system_error(errno, std::system_category(), "fork");
@@ -172,6 +167,16 @@ std::tuple<ProcessId, SocketId> SpawnProcess(SpawnConnectInfoToArgsFn&& connect_
172167
}
173168
}
174169

170+
// Clear FD_CLOEXEC on socket 0 so it survives exec. Doing this here
171+
// rather than in the parent before forking avoids a window where a
172+
// concurrent fork in another thread would leak the descriptor into an
173+
// unrelated child process (which would not clear it).
174+
// fcntl is async-signal-safe.
175+
const int fds0_flags = fcntl(fds[0], F_GETFD);
176+
if (fds0_flags == -1 || fcntl(fds[0], F_SETFD, fds0_flags & ~FD_CLOEXEC) == -1) {
177+
ChildFail("SpawnProcess(child): clearing FD_CLOEXEC failed\n");
178+
}
179+
175180
execvp(argv[0], argv.data());
176181
// NOTE: perror() is not async-signal-safe; calling it here in a
177182
// post-fork child may deadlock in multithreaded parents.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.