From 4fe18e071f84cf8b124390dc9ce4e6ba8806a037 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Mon, 27 Jul 2026 15:23:59 +0000 Subject: [PATCH 9/9] server: replace accept4/SOCK_CLOEXEC/SOCK_NONBLOCK/SO_DOMAIN for Darwin None of accept4(), SOCK_CLOEXEC, SOCK_NONBLOCK, or SO_DOMAIN exist on Darwin (confirmed against Apple's actual socket.h: no CLOEXEC/ NONBLOCK bits reserved in the SOCK_* namespace, no SO_DOMAIN in the SO_* list). Add a shared fd_set_cloexec_nonblock() helper to util.h (the same fcntl(F_SETFD,FD_CLOEXEC)+fcntl(F_SETFL,O_NONBLOCK) pattern prepare_socket() in this file already uses for its passed-in-fd case) and use it after accept()/socket() instead of the atomic flags -- same portable fallback libuv and tmux both use, with the same accepted small race window between fd creation and the fcntl calls. prepare_socket()'s SO_DOMAIN validation check (confirming a passed-in fd's address family) is dropped on Darwin, since there's no getsockopt() equivalent; the SO_TYPE == SOCK_STREAM check already there provides most of the same protection. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01U6fuL1PtRJHhr97gAGyS1h --- server.c | 37 +++++++++++++++++++++++++++++++++++++ util.h | 26 +++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/server.c b/server.c index 25963325..a2239e32 100644 --- a/server.c +++ b/server.c @@ -446,8 +446,17 @@ fdm_server(struct fdm *fdm, int fd, int events, void *data) struct sockaddr_un addr; socklen_t addr_size = sizeof(addr); +#if defined(__APPLE__) + int client_fd = accept( + server->fd, (struct sockaddr *)&addr, &addr_size); + if (client_fd != -1 && !fd_set_cloexec_nonblock(client_fd)) { + close(client_fd); + client_fd = -1; + } +#else int client_fd = accept4( server->fd, (struct sockaddr *)&addr, &addr_size, SOCK_CLOEXEC | SOCK_NONBLOCK); +#endif if (client_fd == -1) { LOG_ERRNO("failed to accept client connection"); @@ -478,7 +487,15 @@ try_connect(const char *sock_path) { enum connect_status ret = CONNECT_ERR; +#if defined(__APPLE__) + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd != -1 && !fd_set_cloexec_nonblock(fd)) { + close(fd); + fd = -1; + } +#else int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); +#endif if (fd == -1) { LOG_ERRNO("failed to create UNIX socket"); goto err; @@ -527,9 +544,21 @@ prepare_socket(int fd) return false; } +#if defined(__APPLE__) + /* + * SO_DOMAIN doesn't exist on Darwin (getsockname() + sa_family + * would be a substitute, but adds little here on top of the + * SO_TYPE == SOCK_STREAM check below), so this one check is + * simply skipped there. + */ + int const socket_options[] = { SO_ACCEPTCONN, SO_TYPE }; + int const socket_options_values[] = { NON_ZERO_OPT, SOCK_STREAM }; + char const * const socket_options_names[] = { "SO_ACCEPTCONN", "SO_TYPE" }; +#else int const socket_options[] = { SO_DOMAIN, SO_ACCEPTCONN, SO_TYPE }; int const socket_options_values[] = { AF_UNIX, NON_ZERO_OPT, SOCK_STREAM}; char const * const socket_options_names[] = { "SO_DOMAIN", "SO_ACCEPTCONN", "SO_TYPE" }; +#endif xassert(ALEN(socket_options) == ALEN(socket_options_values)); xassert(ALEN(socket_options) == ALEN(socket_options_names)); @@ -575,7 +604,15 @@ server_init(struct config *conf, struct fdm *fdm, struct reaper *reaper, } else { LOG_DBG("no suitable pre-existing socket found, creating our own"); +#if defined(__APPLE__) + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd != -1 && !fd_set_cloexec_nonblock(fd)) { + close(fd); + fd = -1; + } +#else fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); +#endif if (fd == -1) { LOG_ERRNO("failed to create UNIX socket"); return NULL; diff --git a/util.h b/util.h index 45722f39..7ca3a9c9 100644 --- a/util.h +++ b/util.h @@ -23,10 +23,30 @@ streq(const char *a, const char *b) #if defined(__APPLE__) /* - * pipe2() is a Linux/glibc extension (later adopted by *BSD), never - * implemented by Darwin on any macOS version. All current call sites - * only ever pass O_CLOEXEC and/or O_NONBLOCK. + * Darwin has neither FD_CLOEXEC/O_NONBLOCK-on-creation flags for + * socket()/accept() (SOCK_CLOEXEC, SOCK_NONBLOCK, accept4()) nor + * pipe2() -- all Linux extensions, later adopted by *BSD, never + * implemented by Darwin on any macOS version. Set both properties + * after the fact instead; this reintroduces the small race window + * between fd creation and the fcntl() calls that the atomic flags + * exist specifically to avoid, which is the accepted tradeoff on + * platforms lacking them. */ +static inline bool +fd_set_cloexec_nonblock(int fd) +{ + int f = fcntl(fd, F_GETFD); + if (f < 0 || fcntl(fd, F_SETFD, f | FD_CLOEXEC) < 0) + return false; + + f = fcntl(fd, F_GETFL); + if (f < 0 || fcntl(fd, F_SETFL, f | O_NONBLOCK) < 0) + return false; + + return true; +} + +/* All current call sites only ever pass O_CLOEXEC and/or O_NONBLOCK. */ static inline int pipe2(int fildes[2], int flags) { -- 2.43.0