unix: enforce recvmmsg buffer size requirements (#5095)

The documentation already stated that the receive buffer should be
a multiple of 64 KiB when the UV_UDP_RECVMMSG is used, but make that
more prominent in the documentation and enforce it in the code.

Refs: https://github.com/libuv/libuv/security/advisories/GHSA-r846-fxvr-f3rx
This commit is contained in:
Ben Noordhuis 2026-03-23 21:25:33 +01:00 committed by GitHub
parent 9f0101dcb8
commit 8877568581
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 70 additions and 7 deletions

View File

@ -76,7 +76,8 @@ Data types
*/
UV_UDP_REUSEPORT = 64,
/*
* Indicates that recvmmsg should be used, if available.
* Indicates that recvmmsg should be used, if available. The uv_alloc_cb
* for this handle should create buffers that are multiples of 64 KiB.
*/
UV_UDP_RECVMMSG = 256
};
@ -168,7 +169,8 @@ API
The remaining bits can be used to set one of these flags:
* `UV_UDP_RECVMMSG`: if set, and the platform supports it, :man:`recvmmsg(2)` will
be used.
be used. The :c:type:`uv_alloc_cb` for this handle should create
buffers that are multiples of 64 KiB.
.. versionadded:: 1.7.0
.. versionchanged:: 1.37.0 added the `UV_UDP_RECVMMSG` flag.
@ -481,8 +483,8 @@ API
`suggested_size` in `alloc_cb` for udp_recv is always set to the size of 1 max size dgram.
.. versionchanged:: 1.35.0 added support for :man:`recvmmsg(2)` on supported platforms).
The use of this feature requires a buffer larger than
2 * 64KB to be passed to `alloc_cb`.
The :c:type:`uv_alloc_cb` for this handle should create
buffers that are multiples of 64 KiB.
.. versionchanged:: 1.37.0 :man:`recvmmsg(2)` support is no longer enabled implicitly,
it must be explicitly requested by passing the `UV_UDP_RECVMMSG` flag to
:c:func:`uv_udp_init_ex`.

View File

@ -705,7 +705,8 @@ enum uv_udp_flags {
*/
UV_UDP_REUSEPORT = 64,
/*
* Indicates that recvmmsg should be used, if available.
* Indicates that recvmmsg should be used, if available. The uv_alloc_cb
* for this handle should create buffers that are multiples of 64 KiB.
*/
UV_UDP_RECVMMSG = 256
};

View File

@ -217,6 +217,8 @@ static int uv__udp_recvmmsg(uv_udp_t* handle, uv_buf_t* buf, int flag) {
/* prepare structures for recvmmsg */
chunks = buf->len / UV__UDP_DGRAM_MAXSIZE;
if (chunks == 0)
return UV_EINVAL;
if (chunks > ARRAY_SIZE(iov))
chunks = ARRAY_SIZE(iov);
for (k = 0; k < chunks; ++k) {
@ -316,8 +318,11 @@ static void uv__udp_recvmsg(uv_udp_t* handle, int flag) {
if (uv_udp_using_recvmmsg(handle)) {
nread = uv__udp_recvmmsg(handle, &buf, flag);
if (nread > 0)
count -= nread;
if (nread <= 0) {
handle->recv_cb(handle, nread, &buf, NULL, 0);
return;
}
count -= nread;
continue;
}

View File

@ -181,6 +181,7 @@ TEST_DECLARE (udp_recvmsg_unreachable_error)
TEST_DECLARE (udp_recvmsg_unreachable_error6)
TEST_DECLARE (udp_send_pollerr_no_recv)
TEST_DECLARE (udp_mmsg)
TEST_DECLARE (udp_mmsg_small_buf)
TEST_DECLARE (udp_multicast_join)
TEST_DECLARE (udp_multicast_join6)
TEST_DECLARE (udp_multicast_ttl)
@ -826,6 +827,7 @@ TASK_LIST_START
TEST_ENTRY (udp_options6)
TEST_ENTRY (udp_no_autobind)
TEST_ENTRY (udp_mmsg)
TEST_ENTRY (udp_mmsg_small_buf)
TEST_ENTRY (udp_multicast_interface)
TEST_ENTRY (udp_multicast_interface6)
TEST_ENTRY (udp_multicast_join)

View File

@ -106,6 +106,31 @@ static void recv_cb(uv_udp_t* handle,
}
static void small_alloc_cb(uv_handle_t* handle,
size_t suggested_size,
uv_buf_t* buf) {
CHECK_HANDLE(handle);
buf->len = 64;
buf->base = malloc(buf->len);
ASSERT_NOT_NULL(buf->base);
alloc_cb_called++;
}
static void small_recv_cb(uv_udp_t* handle,
ssize_t nread,
const uv_buf_t* rcvbuf,
const struct sockaddr* addr,
unsigned flags) {
CHECK_HANDLE(handle);
ASSERT_EQ(UV_EINVAL, nread);
uv_close((uv_handle_t*) &recver, close_cb);
uv_close((uv_handle_t*) &sender, close_cb);
free(rcvbuf->base);
recv_cb_called++;
}
TEST_IMPL(udp_mmsg) {
struct sockaddr_in addr;
uv_buf_t buf;
@ -148,3 +173,31 @@ TEST_IMPL(udp_mmsg) {
MAKE_VALGRIND_HAPPY(uv_default_loop());
return 0;
}
TEST_IMPL(udp_mmsg_small_buf) {
struct sockaddr_in addr;
uv_loop_t* loop;
uv_buf_t buf;
loop = uv_default_loop();
ASSERT_OK(uv_udp_init_ex(loop, &recver, AF_UNSPEC | UV_UDP_RECVMMSG));
if (uv_udp_using_recvmmsg(&recver)) {
ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr));
ASSERT_OK(uv_udp_bind(&recver, (const struct sockaddr*) &addr, 0));
ASSERT_OK(uv_udp_recv_start(&recver, small_alloc_cb, small_recv_cb));
ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr));
ASSERT_OK(uv_udp_init(loop, &sender));
buf = uv_buf_init("PING", 4);
ASSERT_EQ(4, uv_udp_try_send(&sender, &buf, 1, (const struct sockaddr*) &addr));
ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT));
ASSERT_EQ(1, recv_cb_called);
ASSERT_EQ(2, close_cb_called);
} else {
uv_close((uv_handle_t*) &recver, close_cb);
ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT));
ASSERT_EQ(1, close_cb_called);
}
MAKE_VALGRIND_HAPPY(loop);
return 0;
}