From b52fc6c284d88f9f5443e996121821c9afb5e988 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 23 Jun 2026 16:09:07 +0200 Subject: [PATCH] stream-service: Avoid race condition when accepting sockets Even if `poll()` indicates that the socket is ready it might block if it's in blocking mode. This change avoids blocking in such cases (accept will fail with EAGAIN/EWOULDBLOCK and `watch()` will return TRUE). As the non-blocking mode is inherited on Windows (on Linux, the man page documents the non-inheritance as a Linux specialty), we set the mode for the accepted socket explicitly to blocking to match the expectations of `stream_t`. Fixes: daf1880b3947 ("stream: add a stream service class abstracting services using BSD sockets") --- .../networking/streams/stream_service.c | 31 +++++++++++++++++++ .../networking/streams/stream_service.h | 8 +++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/libstrongswan/networking/streams/stream_service.c b/src/libstrongswan/networking/streams/stream_service.c index c85a06643..3ebc51e7c 100644 --- a/src/libstrongswan/networking/streams/stream_service.c +++ b/src/libstrongswan/networking/streams/stream_service.c @@ -25,6 +25,7 @@ #include #include #include +#include typedef struct private_stream_service_t private_stream_service_t; @@ -149,6 +150,22 @@ static void destroy_async_data(async_data_t *data) free(data); } +/** + * Enable/disable non-blocking mode on a socket. + */ +static bool set_blocking(int fd, bool block) +{ +#ifdef WIN32 + u_long on = block ? 0 : 1; + return ioctlsocket(fd, FIONBIO, &on) == 0; +#else + int flags = fcntl(fd, F_GETFL); + return flags != -1 && + fcntl(fd, F_SETFL, block ? (flags & ~O_NONBLOCK) + : (flags | O_NONBLOCK)) != -1; +#endif +} + /** * Reduce running counter */ @@ -180,6 +197,14 @@ static job_requeue_t accept_async(async_data_t *data) this->running++; this->mutex->unlock(this->mutex); + /* accepted socket may inherit non-blocking mode from listening socket + * depending on the implementation, so we normalize this to blocking mode */ + if (!set_blocking(data->fd, TRUE)) + { + reduce_running(data); + return JOB_REQUEUE_NONE; + } + stream = stream_create_from_fd(data->fd); if (stream) { @@ -296,6 +321,12 @@ stream_service_t *stream_service_create_from_fd(int fd) { private_stream_service_t *this; + if (!set_blocking(fd, FALSE)) + { + close(fd); + return NULL; + } + INIT(this, .public = { .on_accept = _on_accept, diff --git a/src/libstrongswan/networking/streams/stream_service.h b/src/libstrongswan/networking/streams/stream_service.h index a974b5f9e..a2c9330a5 100644 --- a/src/libstrongswan/networking/streams/stream_service.h +++ b/src/libstrongswan/networking/streams/stream_service.h @@ -76,10 +76,12 @@ struct stream_service_t { /** * Create a service from a file descriptor. * - * The file descriptor MUST be a socket. + * The file descriptor MUST be a socket. It is set to non-blocking mode to + * safely accept() connections from the watcher thread. For consistency, the + * accepted connection sockets are set to blocking mode on all platforms. * - * @param fd file descriptor to wrap into a stream_service_t - * @return stream_service instance + * @param fd file descriptor to wrap (adopted, closed on failure) + * @return service, NULL if fd can't be switched to non-blocking mode */ stream_service_t *stream_service_create_from_fd(int fd);