From: Olivier Houchard Date: Thu, 6 Aug 2026 07:32:10 +0000 (+0200) Subject: BUG/MEDIUM: sock: bound the recvmsg() length when receiving old sockets X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0fdb91c5c953ddc043f2e788d4f7627e1f8ac09b;p=thirdparty%2Fhaproxy.git BUG/MEDIUM: sock: bound the recvmsg() length when receiving old sockets sock_get_old_sockets() sizes tmpbuf from the number of FDs announced by the old process, but passes a fixed iov_len of MAX_SEND_FD entries to every recvmsg() and loops as long as fewer FDs than announced were received, without ever comparing curoff to the size of the allocation. A peer announcing a single FD (4118 bytes allocated) and then streaming plain data with no SCM_RIGHTS makes the kernel write up to 252*4118 bytes per recvmsg() past the end of the buffer, and the loop never ends. Reproduced with a fake old process: glibc aborts on "free(): invalid next size" after ~320 kB. Only the peer of the -x transfer socket can do this, so it is not reachable from the network, but it happens before privileges are dropped. Let's clamp each recvmsg() to the room really left in the allocation and abort the transfer when the peer sends more. Legitimate transfers are unaffected, they use at most 1+255+1+255+4 bytes per FD. This has been there since commit f73629d23 ("MINOR: global: Add an option to get the old listening sockets.") in 1.8, which already sized tmpbuf on fd_nb and the iovec on MAX_SEND_FD. It may be backported to all stable versions. Reported-by: Claude (ANT-2026-Q363CKEH) --- diff --git a/src/sock.c b/src/sock.c index 0452805cc..a5a6f6e2c 100644 --- a/src/sock.c +++ b/src/sock.c @@ -482,7 +482,7 @@ int sock_get_old_sockets(const char *unixsocket) int fd_nb; int got_fd = 0; int cur_fd = 0; - size_t maxoff = 0, curoff = 0; + size_t maxoff = 0, curoff = 0, tmpbuf_sz; if (strncmp("sockpair@", unixsocket, strlen("sockpair@")) == 0) { /* sockpair for master-worker usage */ @@ -579,11 +579,23 @@ int sock_get_old_sockets(const char *unixsocket) msghdr.msg_control = cmsgbuf; msghdr.msg_controllen = CMSG_SPACE(sizeof(int)) * MAX_SEND_FD; - iov.iov_len = MAX_SEND_FD * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int)); + tmpbuf_sz = (size_t)fd_nb * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int)); do { int ret3; + /* never let the peer write more than what was allocated for the + * announced number of FDs. + */ + iov.iov_len = MAX_SEND_FD * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int)); + if (iov.iov_len > tmpbuf_sz - curoff) + iov.iov_len = tmpbuf_sz - curoff; + + if (!iov.iov_len) { + ha_warning("Received more data than expected while receiving sockets\n"); + goto out; + } + iov.iov_base = tmpbuf + curoff; ret = recvmsg(sock, &msghdr, 0);