From 37f36c87ec551afbebecbfaadf1e58e427d143d7 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 21 Jul 2026 15:02:33 -0700 Subject: [PATCH 1/3] Fix recvmsg msghdr outputs recvmsg was writing iovecs after the first at incorrect offsets, and was not updating msg_namelen, msg_controllen or msg_flags in the caller's struct msghdr. --- ChangeLog.md | 3 + src/lib/libsyscall.js | 10 ++- src/struct_info.json | 5 +- src/struct_info_generated.json | 3 + src/struct_info_generated_wasm64.json | 3 + test/sockets/test_udp_recvmsg.c | 104 ++++++++++++++++++++++++++ test/test_sockets.py | 5 ++ 7 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 test/sockets/test_udp_recvmsg.c diff --git a/ChangeLog.md b/ChangeLog.md index 4dc2c6608342d..dade569fcd4dc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,9 @@ See docs/process.md for more on how version tagging works. and `$allocate`. (#27378) - llvm-libc was updated to LLVM 22.1.8. (#27374) - Backport fix for musl's qsort (CVE-2026-40200) (#27029) +- `recvmsg` now updates `msg_namelen`, `msg_controllen` and `msg_flags` in the + caller's `struct msghdr`, and no longer mis-offsets writes into multiple + iovecs. 6.0.3 - 07/13/26 ---------------- diff --git a/src/lib/libsyscall.js b/src/lib/libsyscall.js index 73260ff518879..0fc3be60a0c88 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -530,7 +530,8 @@ var SyscallsLibrary = { // write the source address out var name = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_name, '*') }}}; if (name) { - var errno = writeSockaddr(name, sock.family, DNS.lookup_name(msg.addr), msg.port); + var namelen = message + {{{ C_STRUCTS.msghdr.msg_namelen }}}; + var errno = writeSockaddr(name, sock.family, DNS.lookup_name(msg.addr), msg.port, namelen); #if ASSERTIONS assert(!errno); #endif @@ -546,12 +547,15 @@ var SyscallsLibrary = { } var length = Math.min(iovlen, bytesRemaining); var buf = msg.buffer.subarray(bytesRead, bytesRead + length); - HEAPU8.set(buf, iovbase + bytesRead); + HEAPU8.set(buf, iovbase); bytesRead += length; bytesRemaining -= length; } - // TODO set msghdr.msg_flags + {{{ makeSetValue('message', C_STRUCTS.msghdr.msg_controllen, '0', 'i32') }}}; + {{{ makeSetValue('message', C_STRUCTS.msghdr.msg_flags, '0', 'i32') }}}; + + // TODO report truncation in msghdr.msg_flags // MSG_EOR // End of record was received (if supported by the protocol). // MSG_OOB diff --git a/src/struct_info.json b/src/struct_info.json index 86b32cc0d3aee..9aaa792f8df00 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -232,7 +232,10 @@ "msg_name", "msg_namelen", "msg_iov", - "msg_iovlen" + "msg_iovlen", + "msg_control", + "msg_controllen", + "msg_flags" ] } }, diff --git a/src/struct_info_generated.json b/src/struct_info_generated.json index 89ea8ef20bf49..87b90f581ccf9 100644 --- a/src/struct_info_generated.json +++ b/src/struct_info_generated.json @@ -1030,6 +1030,9 @@ }, "msghdr": { "__size__": 28, + "msg_control": 16, + "msg_controllen": 20, + "msg_flags": 24, "msg_iov": 8, "msg_iovlen": 12, "msg_name": 0, diff --git a/src/struct_info_generated_wasm64.json b/src/struct_info_generated_wasm64.json index 9605a53d336a9..3dd2ad1bcb1a0 100644 --- a/src/struct_info_generated_wasm64.json +++ b/src/struct_info_generated_wasm64.json @@ -1030,6 +1030,9 @@ }, "msghdr": { "__size__": 56, + "msg_control": 32, + "msg_controllen": 40, + "msg_flags": 48, "msg_iov": 16, "msg_iovlen": 24, "msg_name": 0, diff --git a/test/sockets/test_udp_recvmsg.c b/test/sockets/test_udp_recvmsg.c new file mode 100644 index 0000000000000..b54fd1bd45a89 --- /dev/null +++ b/test/sockets/test_udp_recvmsg.c @@ -0,0 +1,104 @@ +/* + * Copyright 2026 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +static int server_fd; +static int client_fd; +static struct sockaddr_in destination; +static bool sent; + +static void succeed(void) { + close(server_fd); + close(client_fd); + puts("done"); +#ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); +#else + _exit(0); +#endif +} + +static void main_loop(void) { + if (!sent) { + assert(sendto(client_fd, "onetwo", 6, 0, (struct sockaddr*)&destination, + sizeof(destination)) == 6); + sent = true; + } + + // Receive the single datagram scattered across two iovecs. + char first[3]; + char second[3]; + char control[16]; + struct sockaddr_in source; + struct iovec iovecs[2] = { + {.iov_base = first, .iov_len = sizeof(first)}, + {.iov_base = second, .iov_len = sizeof(second)}, + }; + struct msghdr message = { + .msg_name = &source, + .msg_namelen = sizeof(source), + .msg_iov = iovecs, + .msg_iovlen = 2, + .msg_control = control, + .msg_controllen = sizeof(control), + }; + + ssize_t len = recvmsg(server_fd, &message, 0); + if (len < 0) { + assert(errno == EAGAIN || errno == EWOULDBLOCK); + return; + } + + assert(len == 6); + assert(memcmp(first, "one", 3) == 0); + assert(memcmp(second, "two", 3) == 0); + assert(message.msg_namelen == sizeof(struct sockaddr_in)); + assert(source.sin_family == AF_INET); + assert(message.msg_controllen == 0); + assert(message.msg_flags == 0); + succeed(); +} + +int main(void) { + server_fd = socket(AF_INET, SOCK_DGRAM, 0); + client_fd = socket(AF_INET, SOCK_DGRAM, 0); + assert(server_fd >= 0 && client_fd >= 0); + + struct sockaddr_in address = { + .sin_family = AF_INET, + .sin_port = htons(0), + }; + inet_pton(AF_INET, "127.0.0.1", &address.sin_addr); + assert(bind(server_fd, (struct sockaddr*)&address, sizeof(address)) == 0); + + socklen_t length = sizeof(destination); + assert(getsockname(server_fd, (struct sockaddr*)&destination, &length) == 0); + assert(fcntl(server_fd, F_SETFL, O_NONBLOCK) == 0); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(main_loop, 0, 0); +#else + while (true) { + main_loop(); + usleep(1000); + } +#endif + return 0; +} diff --git a/test/test_sockets.py b/test/test_sockets.py index adcd6fb285ac3..fa2b10cb34270 100644 --- a/test/test_sockets.py +++ b/test/test_sockets.py @@ -500,6 +500,11 @@ def test_noderawsockets_udp(self): # ephemeral port, the client sends a datagram, the server echoes it back. self.do_runf('sockets/test_udp_echo.c', 'done\n', cflags=['-sNODERAWSOCKETS']) + def test_noderawsockets_udp_recvmsg(self): + # recvmsg scatters a datagram across multiple iovecs at the right offsets + # and updates msg_namelen/msg_controllen/msg_flags in the caller's msghdr. + self.do_runf('sockets/test_udp_recvmsg.c', 'done\n', cflags=['-sNODERAWSOCKETS']) + @also_with_proxy_to_pthread def test_noderawsockets_udp_connect(self): # Connected UDP: sendto() with an address gives EISCONN, send() reaches the From 59f69aa64125cd6f8b7f181a4d5ff053a136fd8a Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 21 Jul 2026 15:21:05 -0700 Subject: [PATCH 2/3] rebaseline --- test/codesize/test_codesize_hello_dylink_all.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 7b20e38dd3ad5..b95b10417ae11 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 268195, + "a.out.js": 268229, "a.out.nodebug.wasm": 588191, - "total": 856386, + "total": 856420, "sent": [ "IMG_Init", "IMG_Load", From ccb89c84fa9152a25563f8a98d1d9dc0029c16f0 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 21 Jul 2026 15:38:25 -0700 Subject: [PATCH 3/3] fixup changelog --- ChangeLog.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index dade569fcd4dc..4dc2c6608342d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,9 +30,6 @@ See docs/process.md for more on how version tagging works. and `$allocate`. (#27378) - llvm-libc was updated to LLVM 22.1.8. (#27374) - Backport fix for musl's qsort (CVE-2026-40200) (#27029) -- `recvmsg` now updates `msg_namelen`, `msg_controllen` and `msg_flags` in the - caller's `struct msghdr`, and no longer mis-offsets writes into multiple - iovecs. 6.0.3 - 07/13/26 ----------------