What changed, and why it matters
This commit updates a bundled helper library (ccan) and fixes a small but meaningful networking bug in its event loop. Previously, when a socket reported an error, the code always assumed the generic error 'bad file descriptor' (EBADF). Now it asks the operating system for the specific socket error. This is particularly important on macOS, where a failed outgoing connection can be reported differently than on Linux, and the real reason (for example, 'connection refused') was being lost. The change improves cross-platform correctness and debugging, but it is a bug-fix/correctness improvement rather than a clear-cut security vulnerability.
Treat as a routine correctness/portability fix. Review downstream consumers of io_close() to confirm they handle ECONNREFUSED and other socket errors safely, and ensure no code path relied on the previous EBADF behavior. No urgent security patch is indicated by the diff alone.
Security signals we found
Error-handling path changed from a hardcoded generic errno to a real socket error retrieved via getsockopt(SO_ERROR)
Cross-platform macOS/Linux behavior divergence in poll(2) error reporting for connect() failures
Potential for downstream logic to behave differently now that ECONNREFUSED (or other real errors) is propagated instead of EBADF
Evidence from the diff
The patch modifies ccan/ccan/io/poll.c’s io_loop(). In the branch handling POLLHUP/POLLNVAL/POLLERR, the code previously unconditionally set errno = EBADF before calling io_close(c). The new code calls getsockopt(fd, SOL_SOCKET, SO_ERROR, &errno, &errno_len) to retrieve the actual pending socket error. If getsockopt itself fails, it falls back to EBADF. The commit message notes this matters for connect() failures on macOS, where POLLHUP|POLLERR or POLLOUT|POLLHUP may be delivered and SO_ERROR holds ECONNREFUSED. This is a portability/correctness fix; it does not by itself prevent a memory corruption, remote code execution, or authentication bypass, but misreported errno could theoretically mask or alter error-handling paths in downstream callers.
Changed components
ccan/ccan/io/poll.cio_loop() event loopSocket error handling for POLLHUP/POLLNVAL/POLLERRInspect captured patch +14 / −3
diff --git a/ccan/README b/ccan/README
index 6dd49e64..8d518c24 100644
--- a/ccan/README
+++ b/ccan/README
@@ -1,3 +1,3 @@
-CCAN imported from http://ccodearchive.net.
+CCAN imported from https://github.com/rustyrussell/ccan.
-CCAN version: init-2612-ge242779f
+CCAN version: efd48386
diff --git a/ccan/ccan/io/poll.c b/ccan/ccan/io/poll.c
index 656cc0e3..c4cbaee8 100644
--- a/ccan/ccan/io/poll.c
+++ b/ccan/ccan/io/poll.c
@@ -433,6 +433,7 @@ void *io_loop(struct timers *timers, struct timer **expired)
fairness_counter++;
for (size_t rotation = 0; rotation < num_fds && !io_loop_return; rotation++) {
+ socklen_t errno_len = sizeof(errno);
struct io_conn *c;
int events;
@@ -469,8 +470,18 @@ void *io_loop(struct timers *timers, struct timer **expired)
r--;
io_ready(c, events);
} else if (events & (POLLHUP|POLLNVAL|POLLERR)) {
+ /* On `connect` failure, Linux typically
+ * returns POLLIN|POLLERR. MacOS returns either
+ * POLLHUP|POLLERR or POLLOUT|POLLHUP depending
+ * on version, setting the socket error to
+ * ECONNREFUSED. */
r--;
- errno = EBADF;
+ /* Get fd's specific error to find Mac's
+ * ECONNREFUSED, among others */
+ if(getsockopt(fds[i]->fd, SOL_SOCKET, SO_ERROR,
+ &errno, &errno_len) == -1) {
+ errno = EBADF;
+ }
io_close(c);
}
}
Why this scored 23/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.