-
Notifications
You must be signed in to change notification settings - Fork 291
fix: close inherited file descriptors in child process on Linux #867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Prevents leaking file descriptors (pty master FDs, sockets, etc.) to child processes spawned via forkpty(). Uses close_range() syscall on Linux 5.9+, falling back to /proc/self/fd iteration, then brute force. macOS already handles this via POSIX_SPAWN_CLOEXEC_DEFAULT flag. Fixes #657 Part of microsoft/vscode#58814
| pty_close_inherited_fds() { | ||
| // Try close_range() first (Linux 5.9+, glibc 2.34+) | ||
| #if defined(SYS_close_range) | ||
| if (syscall(SYS_close_range, 3, ~0U, 0) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can let the exec call take care of closing
| if (syscall(SYS_close_range, 3, ~0U, 0) == 0) { | |
| if (syscall(SYS_close_range, 3, ~0U, CLOSE_RANGE_CLOEXEC) == 0) { |
| // Fallback: iterate /proc/self/fd | ||
| int dirfd = open("/proc/self/fd", O_RDONLY | O_DIRECTORY | O_CLOEXEC); | ||
| if (dirfd >= 0) { | ||
| DIR *dir = fdopendir(dirfd); | ||
| if (dir) { | ||
| struct dirent *entry; | ||
| while ((entry = readdir(dir)) != nullptr) { | ||
| if (entry->d_name[0] == '.') continue; | ||
| int fd = atoi(entry->d_name); | ||
| if (fd >= 3 && fd != dirfd) { | ||
| close(fd); | ||
| } | ||
| } | ||
| closedir(dir); // Also closes dirfd | ||
| return; | ||
| } | ||
| close(dirfd); | ||
| } | ||
|
|
||
| // Ultimate fallback: brute force (slow with high ulimit) | ||
| long max_fd = sysconf(_SC_OPEN_MAX); | ||
| if (max_fd < 0) max_fd = 1024; | ||
| for (int fd = 3; fd < max_fd; fd++) { | ||
| close(fd); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can mimic libuv https://github.com/libuv/libuv/blob/f420c2bd57ba79d21e1797324ed3ca18e8d92f89/src/unix/core.c#L800-L809 set cloexec flags on the fds and bail on the first error. The last call here to go over _SC_OPEN_MAX is really horrible for spawn performance.
| // Fallback: iterate /proc/self/fd | |
| int dirfd = open("/proc/self/fd", O_RDONLY | O_DIRECTORY | O_CLOEXEC); | |
| if (dirfd >= 0) { | |
| DIR *dir = fdopendir(dirfd); | |
| if (dir) { | |
| struct dirent *entry; | |
| while ((entry = readdir(dir)) != nullptr) { | |
| if (entry->d_name[0] == '.') continue; | |
| int fd = atoi(entry->d_name); | |
| if (fd >= 3 && fd != dirfd) { | |
| close(fd); | |
| } | |
| } | |
| closedir(dir); // Also closes dirfd | |
| return; | |
| } | |
| close(dirfd); | |
| } | |
| // Ultimate fallback: brute force (slow with high ulimit) | |
| long max_fd = sysconf(_SC_OPEN_MAX); | |
| if (max_fd < 0) max_fd = 1024; | |
| for (int fd = 3; fd < max_fd; fd++) { | |
| close(fd); | |
| } | |
| int fd; | |
| /* Set the CLOEXEC flag on all open descriptors. Unconditionally try the | |
| * first 16 file descriptors. After that, bail out after the first error. | |
| */ | |
| for (fd = 3; ; fd++) | |
| if (SetCloseOnExec(fd) && fd > 15) | |
| break; |
SetCloseOnExec would be as follows,
int SetCloseOnExec(int fd) {
int flags = fcntl(fd, F_GETFD, 0);
if (-1 == flags)
return flags;
if (flags & FD_CLOEXEC)
return 0;
return fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
}
Prevents leaking file descriptors (pty master FDs, sockets, etc.) to child processes spawned via forkpty(). Uses close_range() syscall on Linux 5.9+, falling back to /proc/self/fd iteration, then brute force.
macOS already handles this via POSIX_SPAWN_CLOEXEC_DEFAULT flag.
Fixes #657
Part of microsoft/vscode#58814