From 18681dd50ec67652c41ae0c97c65708b4c61da18 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Mon, 27 Jul 2026 17:58:04 +0000 Subject: [PATCH] src: treat zero O_CLOEXEC as always requested in pipe2/kqueue1 compat On pre-10.7 Darwin SDKs there is no O_CLOEXEC (the open(2) flag arrived in Lion); MacPorts legacy-support defines it to 0 so that code compiles, which makes 'flags & O_CLOEXEC' dead code. In compat_pipe2 this leaks the self-pipes into every child process spawned by the application; in compat_kqueue1 it is worse: the Apple else-branch then *always* runs and actively strips FD_CLOEXEC, so every emulated fd (epollfd/timerfd/eventfd/ signalfd is created through kqueue1()) leaks into all children. Callers of both shims invariably pass O_CLOEXEC (or the *_CLOEXEC aliases of it), so treat a zero O_CLOEXEC as always requested. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Kvag8nXEuGz9XJL3WdSxKC --- src/compat_kqueue1.c | 19 ++++++++++++++++++- src/compat_pipe2.c | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/compat_kqueue1.c b/src/compat_kqueue1.c index 40788f6..082e9cd 100644 --- a/src/compat_kqueue1.c +++ b/src/compat_kqueue1.c @@ -14,6 +14,23 @@ #include "errno_return.h" #include "wrap.h" +/* + * On pre-10.7 Darwin SDKs there is no O_CLOEXEC (the open(2) flag + * arrived in Lion); MacPorts legacy-support defines it to 0 so that + * code compiles, which makes "flags & O_CLOEXEC" dead code. Worse, + * the Apple else-branch below would then *always* run and strip + * FD_CLOEXEC. All emulated fds (epollfd/timerfd/eventfd/signalfd) + * are created through kqueue1(), and their callers pass *_CLOEXEC + * (aliases of O_CLOEXEC), so treat a zero O_CLOEXEC as always + * requested -- otherwise every one of those fds leaks into every + * child process spawned by the application. + */ +#if defined(__APPLE__) && O_CLOEXEC == 0 +#define CLOEXEC_REQUESTED(flags) 1 +#else +#define CLOEXEC_REQUESTED(flags) (((flags) & O_CLOEXEC) != 0) +#endif + static errno_t compat_kqueue1_impl(int *fd_out, int flags) { @@ -31,7 +48,7 @@ compat_kqueue1_impl(int *fd_out, int flags) { int r; - if (flags & O_CLOEXEC) { + if (CLOEXEC_REQUESTED(flags)) { if ((r = real_fcntl(fd, F_GETFD)) < 0 || real_fcntl(fd, F_SETFD, r | FD_CLOEXEC) < 0) { ec = errno; diff --git a/src/compat_pipe2.c b/src/compat_pipe2.c index adb56a2..993d1b0 100644 --- a/src/compat_pipe2.c +++ b/src/compat_pipe2.c @@ -7,6 +7,20 @@ #include "wrap.h" +/* + * On pre-10.7 Darwin SDKs there is no O_CLOEXEC (the open(2) flag + * arrived in Lion); MacPorts legacy-support defines it to 0 so that + * code compiles, which makes "flags & O_CLOEXEC" dead code. Every + * caller of this shim passes O_CLOEXEC, so treat a zero O_CLOEXEC + * as always requested -- otherwise the shim's self-pipes leak into + * every child process spawned by the application. + */ +#if defined(__APPLE__) && O_CLOEXEC == 0 +#define CLOEXEC_REQUESTED(flags) 1 +#else +#define CLOEXEC_REQUESTED(flags) (((flags) & O_CLOEXEC) != 0) +#endif + static errno_t compat_pipe2_impl(int pipefd[2], int flags) { @@ -34,7 +48,7 @@ compat_pipe2_impl(int pipefd[2], int flags) } } - if (flags & O_CLOEXEC) { + if (CLOEXEC_REQUESTED(flags)) { if ((r = fcntl(p[0], F_GETFD, 0)) < 0 || fcntl(p[0], F_SETFD, r | FD_CLOEXEC) < 0 || (r = fcntl(p[1], F_GETFD, 0)) < 0 || -- 2.43.0