Use O_CLOEXEC to avoid race conditions#10162
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Hardens file-descriptor creation against FD leaks across fork()+exec() in multithreaded programs by using close-on-exec flags (e.g., O_CLOEXEC, SOCK_CLOEXEC, EFD_CLOEXEC) at creation time where possible.
Changes:
- Introduces portable
WC_*_CLOEXECmacros that fall back to no-ops on unsupported platforms. - Applies
WC_CLOEXEC/WC_SOCK_CLOEXEC/WC_EFD_CLOEXECto variousopen(),socket(), andeventfd()call sites. - Uses
accept4(..., SOCK_CLOEXEC)for AF_ALG where available and passes close-on-exec flag toperf_event_open.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| wolfssl/wolfcrypt/wc_port.h | Adds portable close-on-exec flag macros for FD-creating syscalls. |
| wolfcrypt/src/random.c | Opens RNG-related device FDs with O_CLOEXEC where available. |
| wolfcrypt/src/port/intel/quickassist_mem.c | Opens QAT memory device with O_CLOEXEC. |
| wolfcrypt/src/port/devcrypto/wc_devcrypto.c | Opens /dev/crypto with O_CLOEXEC. |
| wolfcrypt/src/port/caam/wolfcaam_qnx.c | Opens CAAM device with O_CLOEXEC. |
| wolfcrypt/src/port/af_alg/wc_afalg.c | Uses accept4(..., SOCK_CLOEXEC) when available; creates AF_ALG sockets with SOCK_CLOEXEC. |
| wolfcrypt/src/port/af_alg/afalg_hash.c | Uses accept4(..., SOCK_CLOEXEC) when available for duplicated AF_ALG FDs. |
| wolfcrypt/benchmark/benchmark.c | Adds PERF_FLAG_FD_CLOEXEC to perf_event_open flags. |
| src/wolfio.c | Creates TCP sockets with SOCK_CLOEXEC; sets FD_CLOEXEC after accept(). |
| src/ssl.c | Creates EGD AF_UNIX socket with SOCK_CLOEXEC. |
| src/crl.c | Uses CLOEXEC flags for eventfd/inotify/open() in CRL monitor paths. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
dgarske
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: REQUEST_CHANGES
Findings: 5 total — 4 posted, 1 skipped
Posted findings
- [Low] crl.c kqueue path: F_SETFD without read-modify-write —
src/crl.c:1719 - [Low] wolfio.c: defined(SOCK_CLOEXEC) guard in accept4 path is tautologically true —
src/wolfio.c:1642-1643 - [Medium] afalg_hash.c: accept4 used without _GNU_SOURCE but guarded by system SOCK_CLOEXEC —
wolfcrypt/src/port/af_alg/afalg_hash.c:226-232 - [High] wolfio.c: _GNU_SOURCE placement may conflict if libwolfssl_sources.h pulls in system headers —
src/wolfio.c:31-36
Skipped findings
- [Medium] wc_afalg.c: SOCK_CLOEXEC fallback defeats accept4 guard — missing _GNU_SOURCE
Review generated by Skoll via openclaw
src/crl.c
Outdated
| return NULL; | ||
| } | ||
| #ifdef FD_CLOEXEC | ||
| (void)fcntl(crl->mfd, F_SETFD, FD_CLOEXEC); |
There was a problem hiding this comment.
🔵 [Low] crl.c kqueue path: F_SETFD without read-modify-write
🔧 NIT convention
The kqueue code path sets FD_CLOEXEC directly:
(void)fcntl(crl->mfd, F_SETFD, FD_CLOEXEC);This overwrites any existing fd flags rather than ORing FD_CLOEXEC into the current flags. Elsewhere in this same PR (inotify path at line 1866-1868, benchmark.c line 1599-1601, wolfio.c line 1649-1651), the read-modify-write pattern is correctly used:
int fdFlags = fcntl(fd, F_GETFD);
if (fdFlags >= 0)
(void)fcntl(fd, F_SETFD, fdFlags | FD_CLOEXEC);In practice, kqueue fds have no other fd flags, so this is harmless. But the inconsistency could mislead future developers who copy the pattern.
Suggestion:
| (void)fcntl(crl->mfd, F_SETFD, FD_CLOEXEC); | |
| ```c | |
| #ifdef FD_CLOEXEC | |
| { | |
| int fdFlags = fcntl(crl->mfd, F_GETFD); | |
| if (fdFlags >= 0) | |
| (void)fcntl(crl->mfd, F_SETFD, fdFlags | FD_CLOEXEC); | |
| } | |
| #endif |
| { | ||
| return (int)accept(sockfd, peer_addr, peer_len); | ||
| int fd; | ||
| #if !defined(USE_WINDOWS_API) && !defined(WOLFSSL_ZEPHYR) && \ |
There was a problem hiding this comment.
🔵 [Low] wolfio.c: defined(SOCK_CLOEXEC) guard in accept4 path is tautologically true
🔧 NIT style
The fallback #define SOCK_CLOEXEC 0 at line 56-57 makes defined(SOCK_CLOEXEC) in the accept4 guard at line 1643 always true. The code works correctly because _GNU_SOURCE is defined at the top of the file ensuring accept4 is declared, and SOCK_CLOEXEC=0 is a no-op flag. However, the defined(SOCK_CLOEXEC) check is misleading — it appears to test for platform support but can never be false. A reader might incorrectly think removing the fallback define would enable the else branch.
Suggestion:
| #if !defined(USE_WINDOWS_API) && !defined(WOLFSSL_ZEPHYR) && \ | |
| Consider removing `defined(SOCK_CLOEXEC)` from the guard since it's always true, or add a comment noting the fallback makes it tautological: | |
| ```c | |
| #if !defined(USE_WINDOWS_API) && !defined(WOLFSSL_ZEPHYR) && \ | |
| (defined(__linux__) || defined(__ANDROID__)) | |
| fd = (int)accept4(sockfd, peer_addr, peer_len, SOCK_CLOEXEC); |
|
|
||
| dst->rdFd = accept(src->rdFd, NULL, 0); | ||
| dst->alFd = accept(src->alFd, NULL, 0); | ||
| #if defined(__linux__) && defined(SOCK_CLOEXEC) |
There was a problem hiding this comment.
🟡 [Medium] afalg_hash.c: accept4 used without _GNU_SOURCE but guarded by system SOCK_CLOEXEC
💡 SUGGEST bug
afalg_hash.c uses accept4 guarded by #if defined(__linux__) && defined(SOCK_CLOEXEC). Unlike wc_afalg.c, it does NOT define a SOCK_CLOEXEC fallback, so the guard correctly tests system support (if the system defines SOCK_CLOEXEC, accept4 should also be declared). This is the correct pattern.
However, afalg_hash.c doesn't define _GNU_SOURCE and doesn't include <sys/socket.h> directly — it gets it through wc_afalg.h. On glibc 2.14–2.18 without _GNU_SOURCE, the system SOCK_CLOEXEC is not defined in the headers, so the code correctly falls through to accept(). This works, but adding _GNU_SOURCE (like wolfio.c does) would allow accept4 to work on those older systems too.
Recommendation: Consider adding _GNU_SOURCE before system headers in afalg_hash.c (and transitively wc_afalg.h), matching the wolfio.c pattern, so accept4 is available on all glibc-based Linux systems.
| * before any system headers are included. Excluded for Zephyr and other | ||
| * embedded RTOSes whose libc layers conflict with glibc-style definitions | ||
| * (e.g., Zephyr's socket_select.h vs. glibc's fd_set). */ | ||
| #if (defined(__linux__) || defined(__ANDROID__)) && \ |
There was a problem hiding this comment.
🟠 [High] wolfio.c: _GNU_SOURCE placement may conflict if libwolfssl_sources.h pulls in system headers
🚫 BLOCK bug
_GNU_SOURCE must be defined before any system headers are included. In wolfio.c, it's defined at line 33, then libwolfssl_sources.h is included at line 36. libwolfssl_sources.h includes config.h, then types.h, error-crypt.h, and logging.h. The types.h header includes <stdint.h> and potentially other system headers. On glibc, once any system header is included, defining _GNU_SOURCE after the fact has no effect — feature test macros are evaluated on the first inclusion of <features.h>, which is triggered by the first system header.
If types.h (via <stdint.h> → <features.h>) is processed before _GNU_SOURCE is checked, the accept4 declaration won't be exposed when <sys/socket.h> is later included, even though _GNU_SOURCE is now defined.
Note: in practice, config.h from autotools may define _GNU_SOURCE itself (when --enable-asyncthreads is used), and modern glibc >= 2.19 exposes accept4 by default. But for correctness on all supported configurations, _GNU_SOURCE should be defined before libwolfssl_sources.h.
Suggestion:
| #if (defined(__linux__) || defined(__ANDROID__)) && \ | |
| The `_GNU_SOURCE` define is already before `libwolfssl_sources.h`. The concern is whether `_GNU_SOURCE` itself can be tested before `config.h` is included. It should work because the `#if !defined(_GNU_SOURCE)` check only tests whether `_GNU_SOURCE` was defined by the build system (e.g., `-D_GNU_SOURCE` on the command line). If autotools already set it, we skip the define; if not, we set it before any headers. **However**, verify that no header included by `libwolfssl_sources.h` pulls in `<features.h>` before our `_GNU_SOURCE` takes effect. On glibc, `<stdint.h>` does include `<features.h>`, but since `_GNU_SOURCE` is defined before that include, it should be visible. This is correct order. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 12 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Description
Use the "close-on-exec" flag at file creation time to harden multithread use cases against race conditions.
Fixes #9753
Testing
build testing
Checklist