* [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds
@ 2026-09-25 13:52 Eric Dumazet
2026-09-25 13:52 ` [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value Eric Dumazet
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Eric Dumazet @ 2026-09-25 13:52 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Wei Wang, Simon Horman, Cai Xinchen, netdev, edumazet,
Eric Dumazet
This series fixes two issues with SO_RESERVE_MEM and adds a kselftest suite:
- Patch 1 restricts sock_reserve_memory() to TCP sockets (sk_is_tcp(sk))
instead of sk_has_account(sk), and caps the requested reservation to
SZ_1G in sk_setsockopt(SO_RESERVE_MEM).
Currently, sk_has_account(sk) allows SO_RESERVE_MEM on UDP sockets,
where sk->sk_forward_alloc is protected by sk->sk_receive_queue.lock
rather than the socket lock, and udp_rmem_release() does not account
for sk_unused_reserved_mem(sk). Concurrent UDP packet processing and
setsockopt(SO_RESERVE_MEM) corrupt sk_forward_alloc and memcg
accounting. In addition, values near INT_MAX overflow 32-bit signed
int in sk_mem_pages(delta) and (pages << PAGE_SHIFT).
- Patch 2 adds a kselftest (tools/testing/selftests/net/so_reserve_mem.c)
covering non-TCP rejection (UDP, AF_UNIX, SOCK_RAW, MPTCP), bounds
validation, reserve grow/shrink/rounding and SK_MEMINFO_FWD_ALLOC
reporting, cgroup v2 memory.max enforcement, accept() child zero-reserve
inheritance, and reserve retention across TCP traffic.
v2:
- Patch 1: use page-aligned SZ_1G instead of INT_MAX >> 1 so values
returned by getsockopt(SO_RESERVE_MEM) always round-trip through
setsockopt(SO_RESERVE_MEM) (Sashiko).
- Patch 2: enable CONFIG_CGROUPS=y and CONFIG_MEMCG=y in
tools/testing/selftests/net/config, skip when memcg socket accounting
is unavailable, restore cgroup.subtree_control on teardown, accept
ENOPROTOOPT when MPTCP is disabled via sysctl, move the test process
out of cg_child before lowering memory.max, wait for
SK_MEMINFO_WMEM_QUEUED to drain before checking cfd forward alloc,
and drop the arch-specific SO_RESERVE_MEM fallback define (Sashiko).
- Link to v1: https://lore.kernel.org/netdev/20260924123602.1979090-1-edumazet@google.com/
Eric Dumazet (2):
net: restrict SO_RESERVE_MEM to TCP sockets and cap max value
selftests/net: add SO_RESERVE_MEM test
net/core/sock.c | 4 +-
tools/testing/selftests/net/.gitignore | 1 +
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/config | 2 +
tools/testing/selftests/net/so_reserve_mem.c | 404 +++++++++++++++++++
5 files changed, 410 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/net/so_reserve_mem.c
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value
2026-09-25 13:52 [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds Eric Dumazet
@ 2026-09-25 13:52 ` Eric Dumazet
2026-09-28 17:23 ` Wei Wang
2026-09-25 13:52 ` [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test Eric Dumazet
2026-09-29 2:40 ` [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds patchwork-bot+netdevbpf
2 siblings, 1 reply; 10+ messages in thread
From: Eric Dumazet @ 2026-09-25 13:52 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Wei Wang, Simon Horman, Cai Xinchen, netdev, edumazet,
Eric Dumazet
Commit 2bb2f5fb21b0 ("net: add new socket option SO_RESERVE_MEM") and
commit d00c8ee31729 ("net: fix possible NULL deref in sock_reserve_memory")
only checked sk_has_account(sk), which is true for both TCP and UDP
sockets.
However, SO_RESERVE_MEM and sk_unused_reserved_mem() are currently only
supported by TCP:
- On UDP sockets, sk->sk_forward_alloc is protected by
sk->sk_receive_queue.lock, whereas sock_reserve_memory() and
sock_release_reserved_memory() only acquire lock_sock(sk). Concurrent
UDP packet reception/release and setsockopt(SO_RESERVE_MEM) corrupt
sk_forward_alloc and memcg accounting.
- udp_rmem_release() reclaims excess sk_forward_alloc without
accounting for sk_unused_reserved_mem(sk).
Restrict sock_reserve_memory() to TCP sockets (sk_is_tcp(sk)) for now.
Supporting SO_RESERVE_MEM for UDP (acquiring sk_receive_queue.lock and
honoring sk_unused_reserved_mem() in udp_rmem_release()) can be done in
a future net-next series if needed.
In addition, reject val > SZ_1G with -EINVAL in
sk_setsockopt(SO_RESERVE_MEM). Without an upper bound, values near
INT_MAX cause sk_mem_pages(delta) and (pages << PAGE_SHIFT) to overflow
32-bit signed int, corrupting sk->sk_forward_alloc and
sk->sk_reserved_mem. Using a page-aligned cap (SZ_1G) ensures that the
page-rounded sk->sk_reserved_mem reported by getsockopt(SO_RESERVE_MEM)
can always be passed back to setsockopt(SO_RESERVE_MEM).
Fixes: 2bb2f5fb21b0 ("net: add new socket option SO_RESERVE_MEM")
Reported-by: Cai Xinchen <caixinchen1@huawei.com>
Closes: https://lore.kernel.org/netdev/5a88421d-10ef-4fca-9acb-85a27a3c1173@huawei.com/
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Wei Wang <weiwan@google.com>
---
net/core/sock.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/core/sock.c b/net/core/sock.c
index d23333bb4f3fafa19f58095522ef10d918d92496..e8551df8330ff4b1d662b0d0133e20d1f74eb728 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1034,7 +1034,7 @@ static int sock_reserve_memory(struct sock *sk, int bytes)
bool charged;
int pages;
- if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
+ if (!mem_cgroup_sk_enabled(sk) || !sk_is_tcp(sk))
return -EOPNOTSUPP;
if (!bytes)
@@ -1661,7 +1661,7 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
{
int delta;
- if (val < 0) {
+ if (val < 0 || val > SZ_1G) {
ret = -EINVAL;
break;
}
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test
2026-09-25 13:52 [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds Eric Dumazet
2026-09-25 13:52 ` [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value Eric Dumazet
@ 2026-09-25 13:52 ` Eric Dumazet
2026-09-28 17:22 ` Wei Wang
2026-09-28 19:54 ` netdev-bot+sashiko
2026-09-29 2:40 ` [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds patchwork-bot+netdevbpf
2 siblings, 2 replies; 10+ messages in thread
From: Eric Dumazet @ 2026-09-25 13:52 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Wei Wang, Simon Horman, Cai Xinchen, netdev, edumazet,
Eric Dumazet
Add a kselftest suite covering SO_RESERVE_MEM behavior:
- Reject unsupported socket types (UDP/SOCK_DGRAM, UNIX/SOCK_STREAM,
SOCK_RAW, MPTCP) with EOPNOTSUPP.
- Validate bounds (negative values and values > SZ_1G return EINVAL).
- Verify reserve adjustments (increase, decrease, page rounding, and
reset to 0) and corresponding sk_meminfo[SK_MEMINFO_FWD_ALLOC] changes
via getsockopt(SO_MEMINFO).
- Exercise cgroup v2 memory.max enforcement returning ENOMEM when the
requested reservation exceeds the cgroup limit.
- Verify that a child socket returned by accept() starts with a 0
reserve even when its parent listener configured a non-zero
SO_RESERVE_MEM.
- Verify reserve retention across TCP send/receive/ACK cycles.
Also enable CONFIG_CGROUPS=y and CONFIG_MEMCG=y in
tools/testing/selftests/net/config.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Assisted-by: LLM
---
tools/testing/selftests/net/.gitignore | 1 +
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/config | 2 +
tools/testing/selftests/net/so_reserve_mem.c | 404 +++++++++++++++++++
4 files changed, 408 insertions(+)
create mode 100644 tools/testing/selftests/net/so_reserve_mem.c
diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index c9f46031ac73b20850d068125bb804420d236ad8..a15d7cc4fdcb22b36fdb35b9eaf47e511caa8673 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -41,6 +41,7 @@ skf_net_off
socket
so_incoming_cpu
so_netns_cookie
+so_reserve_mem
so_rcv_listener
stress_reuseport_listen
tap
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 3ee3378f8b26eff9d99a834491ad5ef594dcd782..300207a840c1bcb73a41151c8f94e8f78d01a29b 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -196,6 +196,7 @@ TEST_GEN_PROGS := \
sk_connect_zero_addr \
sk_so_peek_off \
so_incoming_cpu \
+ so_reserve_mem \
tap \
tcp_port_share \
tls \
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 737e7e6327b3793984718eb518d3fb79b7870e4d..d355cf98059770bbcdcda1a33d8e84bc390eb26a 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -7,6 +7,7 @@ CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_CAN=m
CONFIG_CAN_DEV=m
CONFIG_CAN_VXCAN=m
+CONFIG_CGROUPS=y
CONFIG_CRYPTO_ARIA=y
CONFIG_CRYPTO_CHACHA20POLY1305=m
CONFIG_CRYPTO_SHA1=y
@@ -61,6 +62,7 @@ CONFIG_L2TP_V3=y
CONFIG_MACSEC=m
CONFIG_MACVLAN=y
CONFIG_MACVTAP=y
+CONFIG_MEMCG=y
CONFIG_MPLS=y
CONFIG_MPLS_IPTUNNEL=m
CONFIG_MPLS_ROUTING=m
diff --git a/tools/testing/selftests/net/so_reserve_mem.c b/tools/testing/selftests/net/so_reserve_mem.c
new file mode 100644
index 0000000000000000000000000000000000000000..920aeccc0ddf780e1b7ffd2172cf8bc2a01320f1
--- /dev/null
+++ b/tools/testing/selftests/net/so_reserve_mem.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/sock_diag.h>
+#include <netinet/in.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+
+#ifndef IPPROTO_MPTCP
+#define IPPROTO_MPTCP 262
+#endif
+
+#define SO_RESERVE_MEM_MAX (1 << 30)
+
+static int get_reserve_mem(struct __test_metadata *_metadata, int fd)
+{
+ int val = -1;
+ socklen_t len = sizeof(val);
+
+ EXPECT_EQ(getsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, &len), 0);
+ return val;
+}
+
+static __u32 get_fwd_alloc(struct __test_metadata *_metadata, int fd)
+{
+ __u32 meminfo[SK_MEMINFO_VARS] = {};
+ socklen_t len = sizeof(meminfo);
+
+ EXPECT_EQ(getsockopt(fd, SOL_SOCKET, SO_MEMINFO, meminfo, &len), 0);
+ return meminfo[SK_MEMINFO_FWD_ALLOC];
+}
+
+static void wait_wmem_drained(struct __test_metadata *_metadata, int fd)
+{
+ __u32 meminfo[SK_MEMINFO_VARS] = {};
+ socklen_t len;
+ int i;
+
+ for (i = 0; i < 1000; i++) {
+ len = sizeof(meminfo);
+ ASSERT_EQ(getsockopt(fd, SOL_SOCKET, SO_MEMINFO, meminfo, &len), 0);
+ if (meminfo[SK_MEMINFO_WMEM_QUEUED] == 0)
+ return;
+ usleep(1000);
+ }
+ EXPECT_EQ(meminfo[SK_MEMINFO_WMEM_QUEUED], 0U);
+}
+
+FIXTURE(so_reserve_mem)
+{
+ char cg_root[64];
+ char cg_child[128];
+ long page_size;
+ bool cg_mounted;
+ bool restore_subtree_ctrl;
+};
+
+FIXTURE_TEARDOWN(so_reserve_mem)
+{
+ char path[160];
+ int fd;
+
+ if (!self->cg_mounted)
+ return;
+
+ snprintf(path, sizeof(path), "%s/cgroup.procs", self->cg_root);
+ fd = open(path, O_WRONLY);
+ if (fd >= 0) {
+ dprintf(fd, "%d\n", getpid());
+ close(fd);
+ }
+ if (self->cg_child[0]) {
+ rmdir(self->cg_child);
+ self->cg_child[0] = '\0';
+ }
+ if (self->restore_subtree_ctrl) {
+ snprintf(path, sizeof(path), "%s/cgroup.subtree_control",
+ self->cg_root);
+ fd = open(path, O_WRONLY);
+ if (fd >= 0) {
+ if (write(fd, "-memory", 7) < 0)
+ ;
+ close(fd);
+ }
+ self->restore_subtree_ctrl = false;
+ }
+ umount2(self->cg_root, MNT_DETACH);
+ rmdir(self->cg_root);
+ self->cg_mounted = false;
+}
+
+FIXTURE_SETUP(so_reserve_mem)
+{
+ char procs_path[160], ctrl_path[160], ctrl_buf[256] = {};
+ int fd, ret, val = 0;
+
+ self->page_size = sysconf(_SC_PAGESIZE);
+ ASSERT_GT(self->page_size, 0);
+
+ if (unshare(CLONE_NEWNS))
+ SKIP(return, "Failed to unshare mount namespace (need root)");
+
+ mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+
+ snprintf(self->cg_root, sizeof(self->cg_root),
+ "/tmp/ksft_so_reserve_XXXXXX");
+ ASSERT_NE(mkdtemp(self->cg_root), NULL);
+
+ if (mount("none", self->cg_root, "cgroup2", 0, NULL)) {
+ rmdir(self->cg_root);
+ SKIP(return, "Failed to mount cgroup2 (need root)");
+ }
+ self->cg_mounted = true;
+
+ snprintf(ctrl_path, sizeof(ctrl_path), "%s/cgroup.subtree_control",
+ self->cg_root);
+ fd = open(ctrl_path, O_RDWR);
+ if (fd < 0) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "Failed to open cgroup.subtree_control");
+ }
+ if (read(fd, ctrl_buf, sizeof(ctrl_buf) - 1) < 0) {
+ close(fd);
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "Failed to read cgroup.subtree_control");
+ }
+ if (!strstr(ctrl_buf, "memory")) {
+ if (write(fd, "+memory", 7) != 7) {
+ close(fd);
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "cgroup2 memory controller not available");
+ }
+ self->restore_subtree_ctrl = true;
+ }
+ close(fd);
+
+ snprintf(self->cg_child, sizeof(self->cg_child), "%s/test_%d",
+ self->cg_root, getpid());
+ if (mkdir(self->cg_child, 0755)) {
+ self->cg_child[0] = '\0';
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_TRUE(false);
+ }
+
+ snprintf(procs_path, sizeof(procs_path), "%s/cgroup.procs",
+ self->cg_child);
+ fd = open(procs_path, O_WRONLY);
+ if (fd < 0 || dprintf(fd, "%d\n", getpid()) <= 0) {
+ if (fd >= 0)
+ close(fd);
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_TRUE(false);
+ }
+ close(fd);
+
+ /* Verify memcg socket accounting is enabled on this kernel */
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_GE(fd, 0);
+ }
+ ret = setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val));
+ close(fd);
+ if (ret && errno == EOPNOTSUPP) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ SKIP(return, "memcg socket accounting not enabled");
+ }
+ if (ret) {
+ so_reserve_mem_teardown(_metadata, self, variant);
+ ASSERT_EQ(ret, 0);
+ }
+}
+
+static void check_non_tcp_rejected(struct __test_metadata *_metadata,
+ int domain, int type, int protocol,
+ int val)
+{
+ int fd = socket(domain, type, protocol);
+
+ if (fd < 0) {
+ EXPECT_TRUE(errno == EAFNOSUPPORT ||
+ errno == EPROTONOSUPPORT ||
+ errno == ENOPROTOOPT);
+ return;
+ }
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EOPNOTSUPP);
+ close(fd);
+}
+
+TEST_F(so_reserve_mem, non_tcp_rejected)
+{
+ int val = self->page_size * 4;
+
+ check_non_tcp_rejected(_metadata, AF_INET, SOCK_DGRAM, 0, val);
+ check_non_tcp_rejected(_metadata, AF_UNIX, SOCK_STREAM, 0, val);
+ check_non_tcp_rejected(_metadata, AF_INET, SOCK_RAW, IPPROTO_ICMP, val);
+ check_non_tcp_rejected(_metadata, AF_INET, SOCK_STREAM, IPPROTO_MPTCP, val);
+}
+
+TEST_F(so_reserve_mem, grow_shrink_and_rounding)
+{
+ int ps = self->page_size;
+ int fd, val;
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(fd, 0);
+
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), 0U);
+
+ /* Negative or > SO_RESERVE_MEM_MAX value -> EINVAL */
+ val = -1;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ val = SO_RESERVE_MEM_MAX + 1;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ val = INT_MAX;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /* 1 byte rounds up to 1 page */
+ val = 1;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)ps);
+
+ /* Grow to 16 pages */
+ val = 16 * ps;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 16 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(16 * ps));
+
+ /* Shrink by 1 byte (rounds delta down to 0 -> stays 16 pages) */
+ val = 16 * ps - 1;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 16 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(16 * ps));
+
+ /* Shrink to 4 pages */
+ val = 4 * ps;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 4 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(4 * ps));
+
+ /* Release all */
+ val = 0;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
+ EXPECT_EQ(get_fwd_alloc(_metadata, fd), 0U);
+
+ close(fd);
+}
+
+TEST_F(so_reserve_mem, cgroup_memory_max)
+{
+ char max_path[160], procs_path[160];
+ int fd, max_fd, procs_fd, val;
+ int ps = self->page_size;
+
+ snprintf(max_path, sizeof(max_path), "%s/memory.max", self->cg_child);
+ max_fd = open(max_path, O_WRONLY | O_NONBLOCK);
+ if (max_fd < 0)
+ SKIP(return, "cgroup memory controller not delegated");
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(fd, 0);
+
+ /* Move test process back to root cgroup before lowering cg_child's
+ * memory.max so the limit only governs the socket's memcg charges
+ * and cannot trigger OOM on the test process itself.
+ */
+ snprintf(procs_path, sizeof(procs_path), "%s/cgroup.procs",
+ self->cg_root);
+ procs_fd = open(procs_path, O_WRONLY);
+ ASSERT_GE(procs_fd, 0);
+ ASSERT_GT(dprintf(procs_fd, "%d\n", getpid()), 0);
+ close(procs_fd);
+
+ /* Limit cg_child memory to 8 pages and try to reserve 128 pages */
+ ASSERT_GT(dprintf(max_fd, "%ld\n", 8L * ps), 0);
+
+ val = 128 * ps;
+ EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
+ EXPECT_EQ(errno, ENOMEM);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
+
+ /* Restore unlimited memory.max */
+ ASSERT_GT(dprintf(max_fd, "max\n"), 0);
+ close(max_fd);
+
+ val = 4 * ps;
+ ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, fd), 4 * ps);
+
+ close(fd);
+}
+
+TEST_F(so_reserve_mem, accept_child_zero_reserve)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ socklen_t alen = sizeof(addr);
+ int ps = self->page_size;
+ int lfd, cfd, sfd, val;
+
+ lfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(lfd, 0);
+
+ /* Set SO_RESERVE_MEM on listener before listen() */
+ val = 4 * ps;
+ ASSERT_EQ(setsockopt(lfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ ASSERT_EQ(bind(lfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+ ASSERT_EQ(listen(lfd, 2), 0);
+ ASSERT_EQ(getsockname(lfd, (struct sockaddr *)&addr, &alen), 0);
+
+ /* Grow SO_RESERVE_MEM on listener after listen() */
+ val = 8 * ps;
+ ASSERT_EQ(setsockopt(lfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, lfd), 8 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, lfd), (__u32)(8 * ps));
+
+ cfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(cfd, 0);
+ ASSERT_EQ(connect(cfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+
+ sfd = accept(lfd, NULL, NULL);
+ ASSERT_GE(sfd, 0);
+
+ /* Child after accept() must have 0 reserve while listener keeps 8 pages */
+ EXPECT_EQ(get_reserve_mem(_metadata, sfd), 0);
+ EXPECT_EQ(get_fwd_alloc(_metadata, sfd), 0U);
+ EXPECT_EQ(get_reserve_mem(_metadata, lfd), 8 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, lfd), (__u32)(8 * ps));
+
+ /* Child can still independently set its own SO_RESERVE_MEM */
+ val = 6 * ps;
+ ASSERT_EQ(setsockopt(sfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ EXPECT_EQ(get_reserve_mem(_metadata, sfd), 6 * ps);
+ EXPECT_EQ(get_fwd_alloc(_metadata, sfd), (__u32)(6 * ps));
+
+ close(sfd);
+ close(cfd);
+ close(lfd);
+}
+
+TEST_F(so_reserve_mem, preserved_after_traffic)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ socklen_t alen = sizeof(addr);
+ int ps = self->page_size;
+ int lfd, cfd, sfd, val;
+ char buf[8192] = {};
+
+ lfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(lfd, 0);
+ ASSERT_EQ(bind(lfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+ ASSERT_EQ(listen(lfd, 1), 0);
+ ASSERT_EQ(getsockname(lfd, (struct sockaddr *)&addr, &alen), 0);
+
+ cfd = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(cfd, 0);
+ val = 16 * ps;
+ ASSERT_EQ(setsockopt(cfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
+ ASSERT_EQ(connect(cfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+
+ sfd = accept(lfd, NULL, NULL);
+ ASSERT_GE(sfd, 0);
+
+ /* Send & drain traffic; cfd must retain its 16-page forward alloc,
+ * while sfd (0 reserve) reclaims its forward alloc back to 0.
+ */
+ ASSERT_EQ(send(cfd, buf, sizeof(buf), 0), (ssize_t)sizeof(buf));
+ ASSERT_EQ(recv(sfd, buf, sizeof(buf), MSG_WAITALL), (ssize_t)sizeof(buf));
+ wait_wmem_drained(_metadata, cfd);
+
+ EXPECT_GE(get_fwd_alloc(_metadata, cfd), (__u32)(16 * ps));
+ EXPECT_EQ(get_fwd_alloc(_metadata, sfd), 0U);
+
+ close(sfd);
+ close(cfd);
+ close(lfd);
+}
+
+TEST_HARNESS_MAIN
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test
2026-09-25 13:52 ` [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test Eric Dumazet
@ 2026-09-28 17:22 ` Wei Wang
2026-09-28 18:27 ` Eric Dumazet
2026-09-28 19:54 ` netdev-bot+sashiko
1 sibling, 1 reply; 10+ messages in thread
From: Wei Wang @ 2026-09-28 17:22 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Cai Xinchen, netdev, edumazet
On Fri, Sep 25, 2026 at 6:52 AM Eric Dumazet <edumazet@google.com> wrote:
>
> Add a kselftest suite covering SO_RESERVE_MEM behavior:
> - Reject unsupported socket types (UDP/SOCK_DGRAM, UNIX/SOCK_STREAM,
> SOCK_RAW, MPTCP) with EOPNOTSUPP.
> - Validate bounds (negative values and values > SZ_1G return EINVAL).
> - Verify reserve adjustments (increase, decrease, page rounding, and
> reset to 0) and corresponding sk_meminfo[SK_MEMINFO_FWD_ALLOC] changes
> via getsockopt(SO_MEMINFO).
> - Exercise cgroup v2 memory.max enforcement returning ENOMEM when the
> requested reservation exceeds the cgroup limit.
> - Verify that a child socket returned by accept() starts with a 0
> reserve even when its parent listener configured a non-zero
> SO_RESERVE_MEM.
> - Verify reserve retention across TCP send/receive/ACK cycles.
>
> Also enable CONFIG_CGROUPS=y and CONFIG_MEMCG=y in
> tools/testing/selftests/net/config.
>
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> Assisted-by: LLM
> ---
> tools/testing/selftests/net/.gitignore | 1 +
> tools/testing/selftests/net/Makefile | 1 +
> tools/testing/selftests/net/config | 2 +
> tools/testing/selftests/net/so_reserve_mem.c | 404 +++++++++++++++++++
> 4 files changed, 408 insertions(+)
> create mode 100644 tools/testing/selftests/net/so_reserve_mem.c
>
> diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
> index c9f46031ac73b20850d068125bb804420d236ad8..a15d7cc4fdcb22b36fdb35b9eaf47e511caa8673 100644
> --- a/tools/testing/selftests/net/.gitignore
> +++ b/tools/testing/selftests/net/.gitignore
> @@ -41,6 +41,7 @@ skf_net_off
> socket
> so_incoming_cpu
> so_netns_cookie
> +so_reserve_mem
> so_rcv_listener
> stress_reuseport_listen
> tap
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index 3ee3378f8b26eff9d99a834491ad5ef594dcd782..300207a840c1bcb73a41151c8f94e8f78d01a29b 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -196,6 +196,7 @@ TEST_GEN_PROGS := \
> sk_connect_zero_addr \
> sk_so_peek_off \
> so_incoming_cpu \
> + so_reserve_mem \
> tap \
> tcp_port_share \
> tls \
> diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
> index 737e7e6327b3793984718eb518d3fb79b7870e4d..d355cf98059770bbcdcda1a33d8e84bc390eb26a 100644
> --- a/tools/testing/selftests/net/config
> +++ b/tools/testing/selftests/net/config
> @@ -7,6 +7,7 @@ CONFIG_BRIDGE_VLAN_FILTERING=y
> CONFIG_CAN=m
> CONFIG_CAN_DEV=m
> CONFIG_CAN_VXCAN=m
> +CONFIG_CGROUPS=y
> CONFIG_CRYPTO_ARIA=y
> CONFIG_CRYPTO_CHACHA20POLY1305=m
> CONFIG_CRYPTO_SHA1=y
> @@ -61,6 +62,7 @@ CONFIG_L2TP_V3=y
> CONFIG_MACSEC=m
> CONFIG_MACVLAN=y
> CONFIG_MACVTAP=y
> +CONFIG_MEMCG=y
> CONFIG_MPLS=y
> CONFIG_MPLS_IPTUNNEL=m
> CONFIG_MPLS_ROUTING=m
> diff --git a/tools/testing/selftests/net/so_reserve_mem.c b/tools/testing/selftests/net/so_reserve_mem.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..920aeccc0ddf780e1b7ffd2172cf8bc2a01320f1
> --- /dev/null
> +++ b/tools/testing/selftests/net/so_reserve_mem.c
> @@ -0,0 +1,404 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#define _GNU_SOURCE
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <limits.h>
> +#include <linux/sock_diag.h>
> +#include <netinet/in.h>
> +#include <sched.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/mount.h>
> +#include <sys/socket.h>
> +#include <sys/stat.h>
> +#include <unistd.h>
> +
> +#include "kselftest_harness.h"
> +
> +#ifndef IPPROTO_MPTCP
> +#define IPPROTO_MPTCP 262
> +#endif
> +
Should we add
#ifndef SO_RESERVE_MEM
#define SO_RESERVE_MEM 73
#endif
for systems which don't have SO_RESERVE_MEM defined?
> +#define SO_RESERVE_MEM_MAX (1 << 30)
> +
> +static int get_reserve_mem(struct __test_metadata *_metadata, int fd)
> +{
> + int val = -1;
> + socklen_t len = sizeof(val);
> +
> + EXPECT_EQ(getsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, &len), 0);
> + return val;
> +}
> +
> +static __u32 get_fwd_alloc(struct __test_metadata *_metadata, int fd)
> +{
> + __u32 meminfo[SK_MEMINFO_VARS] = {};
> + socklen_t len = sizeof(meminfo);
> +
> + EXPECT_EQ(getsockopt(fd, SOL_SOCKET, SO_MEMINFO, meminfo, &len), 0);
> + return meminfo[SK_MEMINFO_FWD_ALLOC];
> +}
> +
> +static void wait_wmem_drained(struct __test_metadata *_metadata, int fd)
> +{
> + __u32 meminfo[SK_MEMINFO_VARS] = {};
> + socklen_t len;
> + int i;
> +
> + for (i = 0; i < 1000; i++) {
> + len = sizeof(meminfo);
> + ASSERT_EQ(getsockopt(fd, SOL_SOCKET, SO_MEMINFO, meminfo, &len), 0);
> + if (meminfo[SK_MEMINFO_WMEM_QUEUED] == 0)
> + return;
> + usleep(1000);
> + }
> + EXPECT_EQ(meminfo[SK_MEMINFO_WMEM_QUEUED], 0U);
> +}
> +
> +FIXTURE(so_reserve_mem)
> +{
> + char cg_root[64];
> + char cg_child[128];
> + long page_size;
> + bool cg_mounted;
> + bool restore_subtree_ctrl;
> +};
> +
> +FIXTURE_TEARDOWN(so_reserve_mem)
> +{
> + char path[160];
> + int fd;
> +
> + if (!self->cg_mounted)
> + return;
> +
> + snprintf(path, sizeof(path), "%s/cgroup.procs", self->cg_root);
> + fd = open(path, O_WRONLY);
> + if (fd >= 0) {
> + dprintf(fd, "%d\n", getpid());
> + close(fd);
> + }
> + if (self->cg_child[0]) {
> + rmdir(self->cg_child);
> + self->cg_child[0] = '\0';
> + }
> + if (self->restore_subtree_ctrl) {
> + snprintf(path, sizeof(path), "%s/cgroup.subtree_control",
> + self->cg_root);
> + fd = open(path, O_WRONLY);
> + if (fd >= 0) {
> + if (write(fd, "-memory", 7) < 0)
> + ;
> + close(fd);
> + }
> + self->restore_subtree_ctrl = false;
> + }
> + umount2(self->cg_root, MNT_DETACH);
> + rmdir(self->cg_root);
> + self->cg_mounted = false;
> +}
> +
> +FIXTURE_SETUP(so_reserve_mem)
> +{
> + char procs_path[160], ctrl_path[160], ctrl_buf[256] = {};
> + int fd, ret, val = 0;
> +
> + self->page_size = sysconf(_SC_PAGESIZE);
> + ASSERT_GT(self->page_size, 0);
> +
> + if (unshare(CLONE_NEWNS))
> + SKIP(return, "Failed to unshare mount namespace (need root)");
> +
> + mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL);
> +
> + snprintf(self->cg_root, sizeof(self->cg_root),
> + "/tmp/ksft_so_reserve_XXXXXX");
> + ASSERT_NE(mkdtemp(self->cg_root), NULL);
> +
> + if (mount("none", self->cg_root, "cgroup2", 0, NULL)) {
> + rmdir(self->cg_root);
> + SKIP(return, "Failed to mount cgroup2 (need root)");
> + }
> + self->cg_mounted = true;
> +
> + snprintf(ctrl_path, sizeof(ctrl_path), "%s/cgroup.subtree_control",
> + self->cg_root);
> + fd = open(ctrl_path, O_RDWR);
> + if (fd < 0) {
> + so_reserve_mem_teardown(_metadata, self, variant);
> + SKIP(return, "Failed to open cgroup.subtree_control");
> + }
> + if (read(fd, ctrl_buf, sizeof(ctrl_buf) - 1) < 0) {
> + close(fd);
> + so_reserve_mem_teardown(_metadata, self, variant);
> + SKIP(return, "Failed to read cgroup.subtree_control");
> + }
> + if (!strstr(ctrl_buf, "memory")) {
> + if (write(fd, "+memory", 7) != 7) {
> + close(fd);
> + so_reserve_mem_teardown(_metadata, self, variant);
> + SKIP(return, "cgroup2 memory controller not available");
> + }
> + self->restore_subtree_ctrl = true;
> + }
> + close(fd);
> +
> + snprintf(self->cg_child, sizeof(self->cg_child), "%s/test_%d",
> + self->cg_root, getpid());
> + if (mkdir(self->cg_child, 0755)) {
> + self->cg_child[0] = '\0';
> + so_reserve_mem_teardown(_metadata, self, variant);
> + ASSERT_TRUE(false);
> + }
> +
> + snprintf(procs_path, sizeof(procs_path), "%s/cgroup.procs",
> + self->cg_child);
> + fd = open(procs_path, O_WRONLY);
> + if (fd < 0 || dprintf(fd, "%d\n", getpid()) <= 0) {
> + if (fd >= 0)
> + close(fd);
> + so_reserve_mem_teardown(_metadata, self, variant);
> + ASSERT_TRUE(false);
> + }
> + close(fd);
> +
> + /* Verify memcg socket accounting is enabled on this kernel */
> + fd = socket(AF_INET, SOCK_STREAM, 0);
> + if (fd < 0) {
> + so_reserve_mem_teardown(_metadata, self, variant);
> + ASSERT_GE(fd, 0);
> + }
> + ret = setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val));
> + close(fd);
> + if (ret && errno == EOPNOTSUPP) {
> + so_reserve_mem_teardown(_metadata, self, variant);
> + SKIP(return, "memcg socket accounting not enabled");
> + }
> + if (ret) {
> + so_reserve_mem_teardown(_metadata, self, variant);
> + ASSERT_EQ(ret, 0);
> + }
> +}
> +
> +static void check_non_tcp_rejected(struct __test_metadata *_metadata,
> + int domain, int type, int protocol,
> + int val)
> +{
> + int fd = socket(domain, type, protocol);
> +
> + if (fd < 0) {
> + EXPECT_TRUE(errno == EAFNOSUPPORT ||
> + errno == EPROTONOSUPPORT ||
> + errno == ENOPROTOOPT);
> + return;
> + }
> + EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
> + EXPECT_EQ(errno, EOPNOTSUPP);
> + close(fd);
> +}
> +
> +TEST_F(so_reserve_mem, non_tcp_rejected)
> +{
> + int val = self->page_size * 4;
> +
> + check_non_tcp_rejected(_metadata, AF_INET, SOCK_DGRAM, 0, val);
> + check_non_tcp_rejected(_metadata, AF_UNIX, SOCK_STREAM, 0, val);
> + check_non_tcp_rejected(_metadata, AF_INET, SOCK_RAW, IPPROTO_ICMP, val);
> + check_non_tcp_rejected(_metadata, AF_INET, SOCK_STREAM, IPPROTO_MPTCP, val);
Add a test for check_non_tcp_rejected(_metadata, AF_INET, SOCK_DGRAM,
0, 0) to validate that non-TCP socket with val = 0 also returns
-EOPNOTSUPP rather than 0?
> +}
> +
> +TEST_F(so_reserve_mem, grow_shrink_and_rounding)
> +{
> + int ps = self->page_size;
> + int fd, val;
> +
> + fd = socket(AF_INET, SOCK_STREAM, 0);
> + ASSERT_GE(fd, 0);
> +
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
> + EXPECT_EQ(get_fwd_alloc(_metadata, fd), 0U);
> +
> + /* Negative or > SO_RESERVE_MEM_MAX value -> EINVAL */
> + val = -1;
> + EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
> + EXPECT_EQ(errno, EINVAL);
> +
> + val = SO_RESERVE_MEM_MAX + 1;
> + EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
> + EXPECT_EQ(errno, EINVAL);
> +
> + val = INT_MAX;
> + EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
> + EXPECT_EQ(errno, EINVAL);
> +
> + /* 1 byte rounds up to 1 page */
> + val = 1;
> + ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), ps);
> + EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)ps);
> +
> + /* Grow to 16 pages */
> + val = 16 * ps;
> + ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), 16 * ps);
> + EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(16 * ps));
> +
> + /* Shrink by 1 byte (rounds delta down to 0 -> stays 16 pages) */
> + val = 16 * ps - 1;
> + ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), 16 * ps);
> + EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(16 * ps));
> +
> + /* Shrink to 4 pages */
> + val = 4 * ps;
> + ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), 4 * ps);
> + EXPECT_EQ(get_fwd_alloc(_metadata, fd), (__u32)(4 * ps));
> +
> + /* Release all */
> + val = 0;
> + ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
> + EXPECT_EQ(get_fwd_alloc(_metadata, fd), 0U);
> +
> + close(fd);
> +}
> +
> +TEST_F(so_reserve_mem, cgroup_memory_max)
> +{
> + char max_path[160], procs_path[160];
> + int fd, max_fd, procs_fd, val;
> + int ps = self->page_size;
> +
> + snprintf(max_path, sizeof(max_path), "%s/memory.max", self->cg_child);
> + max_fd = open(max_path, O_WRONLY | O_NONBLOCK);
> + if (max_fd < 0)
> + SKIP(return, "cgroup memory controller not delegated");
> +
> + fd = socket(AF_INET, SOCK_STREAM, 0);
> + ASSERT_GE(fd, 0);
> +
> + /* Move test process back to root cgroup before lowering cg_child's
> + * memory.max so the limit only governs the socket's memcg charges
> + * and cannot trigger OOM on the test process itself.
> + */
> + snprintf(procs_path, sizeof(procs_path), "%s/cgroup.procs",
> + self->cg_root);
> + procs_fd = open(procs_path, O_WRONLY);
> + ASSERT_GE(procs_fd, 0);
> + ASSERT_GT(dprintf(procs_fd, "%d\n", getpid()), 0);
> + close(procs_fd);
> +
> + /* Limit cg_child memory to 8 pages and try to reserve 128 pages */
> + ASSERT_GT(dprintf(max_fd, "%ld\n", 8L * ps), 0);
> +
> + val = 128 * ps;
> + EXPECT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), -1);
> + EXPECT_EQ(errno, ENOMEM);
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), 0);
> +
> + /* Restore unlimited memory.max */
> + ASSERT_GT(dprintf(max_fd, "max\n"), 0);
> + close(max_fd);
> +
> + val = 4 * ps;
> + ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, fd), 4 * ps);
> +
> + close(fd);
> +}
> +
> +TEST_F(so_reserve_mem, accept_child_zero_reserve)
> +{
> + struct sockaddr_in addr = {
> + .sin_family = AF_INET,
> + .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
> + };
> + socklen_t alen = sizeof(addr);
> + int ps = self->page_size;
> + int lfd, cfd, sfd, val;
> +
> + lfd = socket(AF_INET, SOCK_STREAM, 0);
> + ASSERT_GE(lfd, 0);
> +
> + /* Set SO_RESERVE_MEM on listener before listen() */
> + val = 4 * ps;
> + ASSERT_EQ(setsockopt(lfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + ASSERT_EQ(bind(lfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
> + ASSERT_EQ(listen(lfd, 2), 0);
> + ASSERT_EQ(getsockname(lfd, (struct sockaddr *)&addr, &alen), 0);
> +
> + /* Grow SO_RESERVE_MEM on listener after listen() */
> + val = 8 * ps;
> + ASSERT_EQ(setsockopt(lfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, lfd), 8 * ps);
> + EXPECT_EQ(get_fwd_alloc(_metadata, lfd), (__u32)(8 * ps));
> +
> + cfd = socket(AF_INET, SOCK_STREAM, 0);
> + ASSERT_GE(cfd, 0);
> + ASSERT_EQ(connect(cfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
> +
> + sfd = accept(lfd, NULL, NULL);
> + ASSERT_GE(sfd, 0);
> +
> + /* Child after accept() must have 0 reserve while listener keeps 8 pages */
> + EXPECT_EQ(get_reserve_mem(_metadata, sfd), 0);
> + EXPECT_EQ(get_fwd_alloc(_metadata, sfd), 0U);
> + EXPECT_EQ(get_reserve_mem(_metadata, lfd), 8 * ps);
> + EXPECT_EQ(get_fwd_alloc(_metadata, lfd), (__u32)(8 * ps));
> +
> + /* Child can still independently set its own SO_RESERVE_MEM */
> + val = 6 * ps;
> + ASSERT_EQ(setsockopt(sfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + EXPECT_EQ(get_reserve_mem(_metadata, sfd), 6 * ps);
> + EXPECT_EQ(get_fwd_alloc(_metadata, sfd), (__u32)(6 * ps));
> +
> + close(sfd);
> + close(cfd);
> + close(lfd);
> +}
> +
> +TEST_F(so_reserve_mem, preserved_after_traffic)
> +{
> + struct sockaddr_in addr = {
> + .sin_family = AF_INET,
> + .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
> + };
> + socklen_t alen = sizeof(addr);
> + int ps = self->page_size;
> + int lfd, cfd, sfd, val;
> + char buf[8192] = {};
> +
> + lfd = socket(AF_INET, SOCK_STREAM, 0);
> + ASSERT_GE(lfd, 0);
> + ASSERT_EQ(bind(lfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
> + ASSERT_EQ(listen(lfd, 1), 0);
> + ASSERT_EQ(getsockname(lfd, (struct sockaddr *)&addr, &alen), 0);
> +
> + cfd = socket(AF_INET, SOCK_STREAM, 0);
> + ASSERT_GE(cfd, 0);
> + val = 16 * ps;
> + ASSERT_EQ(setsockopt(cfd, SOL_SOCKET, SO_RESERVE_MEM, &val, sizeof(val)), 0);
> + ASSERT_EQ(connect(cfd, (struct sockaddr *)&addr, sizeof(addr)), 0);
> +
> + sfd = accept(lfd, NULL, NULL);
> + ASSERT_GE(sfd, 0);
> +
> + /* Send & drain traffic; cfd must retain its 16-page forward alloc,
> + * while sfd (0 reserve) reclaims its forward alloc back to 0.
> + */
> + ASSERT_EQ(send(cfd, buf, sizeof(buf), 0), (ssize_t)sizeof(buf));
> + ASSERT_EQ(recv(sfd, buf, sizeof(buf), MSG_WAITALL), (ssize_t)sizeof(buf));
> + wait_wmem_drained(_metadata, cfd);
> +
> + EXPECT_GE(get_fwd_alloc(_metadata, cfd), (__u32)(16 * ps));
> + EXPECT_EQ(get_fwd_alloc(_metadata, sfd), 0U);
> +
> + close(sfd);
> + close(cfd);
> + close(lfd);
> +}
> +
> +TEST_HARNESS_MAIN
> --
> 2.56.0.rc1.315.gc6ed9934b7-goog
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value
2026-09-25 13:52 ` [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value Eric Dumazet
@ 2026-09-28 17:23 ` Wei Wang
0 siblings, 0 replies; 10+ messages in thread
From: Wei Wang @ 2026-09-28 17:23 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Cai Xinchen, netdev, edumazet
On Fri, Sep 25, 2026 at 6:52 AM Eric Dumazet <edumazet@google.com> wrote:
>
> Commit 2bb2f5fb21b0 ("net: add new socket option SO_RESERVE_MEM") and
> commit d00c8ee31729 ("net: fix possible NULL deref in sock_reserve_memory")
> only checked sk_has_account(sk), which is true for both TCP and UDP
> sockets.
>
> However, SO_RESERVE_MEM and sk_unused_reserved_mem() are currently only
> supported by TCP:
> - On UDP sockets, sk->sk_forward_alloc is protected by
> sk->sk_receive_queue.lock, whereas sock_reserve_memory() and
> sock_release_reserved_memory() only acquire lock_sock(sk). Concurrent
> UDP packet reception/release and setsockopt(SO_RESERVE_MEM) corrupt
> sk_forward_alloc and memcg accounting.
> - udp_rmem_release() reclaims excess sk_forward_alloc without
> accounting for sk_unused_reserved_mem(sk).
>
> Restrict sock_reserve_memory() to TCP sockets (sk_is_tcp(sk)) for now.
> Supporting SO_RESERVE_MEM for UDP (acquiring sk_receive_queue.lock and
> honoring sk_unused_reserved_mem() in udp_rmem_release()) can be done in
> a future net-next series if needed.
>
> In addition, reject val > SZ_1G with -EINVAL in
> sk_setsockopt(SO_RESERVE_MEM). Without an upper bound, values near
> INT_MAX cause sk_mem_pages(delta) and (pages << PAGE_SHIFT) to overflow
> 32-bit signed int, corrupting sk->sk_forward_alloc and
> sk->sk_reserved_mem. Using a page-aligned cap (SZ_1G) ensures that the
> page-rounded sk->sk_reserved_mem reported by getsockopt(SO_RESERVE_MEM)
> can always be passed back to setsockopt(SO_RESERVE_MEM).
>
> Fixes: 2bb2f5fb21b0 ("net: add new socket option SO_RESERVE_MEM")
> Reported-by: Cai Xinchen <caixinchen1@huawei.com>
> Closes: https://lore.kernel.org/netdev/5a88421d-10ef-4fca-9acb-85a27a3c1173@huawei.com/
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> Reviewed-by: Wei Wang <weiwan@google.com>
> ---
Reviewed-by: Wei Wang <weiwan@google.com> for v2. :)
> net/core/sock.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/net/core/sock.c b/net/core/sock.c
> index d23333bb4f3fafa19f58095522ef10d918d92496..e8551df8330ff4b1d662b0d0133e20d1f74eb728 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -1034,7 +1034,7 @@ static int sock_reserve_memory(struct sock *sk, int bytes)
> bool charged;
> int pages;
>
> - if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
> + if (!mem_cgroup_sk_enabled(sk) || !sk_is_tcp(sk))
> return -EOPNOTSUPP;
>
> if (!bytes)
> @@ -1661,7 +1661,7 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
> {
> int delta;
>
> - if (val < 0) {
> + if (val < 0 || val > SZ_1G) {
> ret = -EINVAL;
> break;
> }
> --
> 2.56.0.rc1.315.gc6ed9934b7-goog
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test
2026-09-28 17:22 ` Wei Wang
@ 2026-09-28 18:27 ` Eric Dumazet
0 siblings, 0 replies; 10+ messages in thread
From: Eric Dumazet @ 2026-09-28 18:27 UTC (permalink / raw)
To: Wei Wang
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Cai Xinchen, netdev, edumazet
On Mon, Sep 28, 2026 at 7:22 PM Wei Wang <weiwan@google.com> wrote:
>
> Should we add
> #ifndef SO_RESERVE_MEM
> #define SO_RESERVE_MEM 73
> #endif
> for systems which don't have SO_RESERVE_MEM defined?
I removed it after sashiko rightfully complained.
https://lore.kernel.org/netdev/179034054266.2160803.16187930067723490774@kernel.org/
> Add a test for check_non_tcp_rejected(_metadata, AF_INET, SOCK_DGRAM,
> 0, 0) to validate that non-TCP socket with val = 0 also returns
> -EOPNOTSUPP rather than 0?
Hmm... I feel the test is already quite lengthy :)
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test
2026-09-25 13:52 ` [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test Eric Dumazet
2026-09-28 17:22 ` Wei Wang
@ 2026-09-28 19:54 ` netdev-bot+sashiko
2026-09-28 20:03 ` Eric Dumazet
1 sibling, 1 reply; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 19:54 UTC (permalink / raw)
To: edumazet
Cc: davem, kuba, pabeni, weiwan, horms, caixinchen1, netdev, edumazet
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 4
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The test assumes the freshly mounted cgroup2 root (cg_root) is
the real hierarchy root, which is exempt from the no-internal-process…
- [Low] check_non_tcp_rejected()
(tools/testing/selftests/net/so_reserve_mem.c, lines 191-195) accepts…
- [Low] The fixture changes the host's shared cgroup v2 hierarchy without
restoring its original state symmetrically.
- [Low] preserved_after_traffic assumes that once SO_MEMINFO shows
SK_MEMINFO_WMEM_QUEUED == 0 in wait_wmem_drained() (line 53), a later,…
- [Low] The fixture checks and changes the root cgroup subtree_control
without any coordination, then undoes the change unconditionally.
--
selftests/net: add SO_RESERVE_MEM test
This adds a kselftest for SO_RESERVE_MEM. It covers rejection of
unsupported socket types, bounds checking, reserve grow/shrink and page
rounding, cgroup v2 memory.max enforcement, the reserve of an accept()
child, and reserve retention across TCP traffic. It also enables
CONFIG_CGROUPS and CONFIG_MEMCG in the net selftests config.
> diff --git a/tools/testing/selftests/net/so_reserve_mem.c b/tools/testing/selftests/net/so_reserve_mem.c
> new file mode 100644
> index 0000000000000..920aeccc0ddf7
> --- /dev/null
> +++ b/tools/testing/selftests/net/so_reserve_mem.c
> @@ -0,0 +1,404 @@
[ ... ]
> +FIXTURE_TEARDOWN(so_reserve_mem)
> +{
> + char path[160];
> + int fd;
> +
> + if (!self->cg_mounted)
> + return;
> +
> + snprintf(path, sizeof(path), "%s/cgroup.procs", self->cg_root);
> + fd = open(path, O_WRONLY);
> + if (fd >= 0) {
> + dprintf(fd, "%d\n", getpid());
> + close(fd);
> + }
> + if (self->cg_child[0]) {
> + rmdir(self->cg_child);
> + self->cg_child[0] = '\0';
> + }
> + if (self->restore_subtree_ctrl) {
> + snprintf(path, sizeof(path), "%s/cgroup.subtree_control",
> + self->cg_root);
> + fd = open(path, O_WRONLY);
> + if (fd >= 0) {
> + if (write(fd, "-memory", 7) < 0)
> + ;
> + close(fd);
> + }
[Severity: Low]
Can this leave the host cgroup hierarchy modified?
FIXTURE_SETUP() only calls unshare(CLONE_NEWNS), so the cgroup2 mount on
/tmp/ksft_so_reserve_XXXXXX is the same hierarchy the host uses. Setup can
write "+memory" to the root cgroup.subtree_control. It then creates
test_<pid> and moves the process there without recording its original
cgroup.
This teardown moves the process to cg_root, not back to where it started.
The "-memory" write can fail, for example with EBUSY because another child
has since enabled memory in its own subtree_control. In that case the
global change stays and nothing reports it.
The teardown also only runs in the test grandchild. On a harness timeout,
__wait_for_test() in kselftest_harness.h does:
kill(-(t->pid), SIGKILL);
so the teardown never runs. The same happens on an external SIGKILL or
SIGINT. In those cases three things are left behind:
- the empty test_<pid> cgroup
- the /tmp/ksft_so_reserve_XXXXXX directory on the real /tmp
- "+memory" in the root subtree_control
[ ... ]
> +FIXTURE_SETUP(so_reserve_mem)
> +{
> + char procs_path[160], ctrl_path[160], ctrl_buf[256] = {};
> + int fd, ret, val = 0;
> +
> + self->page_size = sysconf(_SC_PAGESIZE);
> + ASSERT_GT(self->page_size, 0);
> +
> + if (unshare(CLONE_NEWNS))
> + SKIP(return, "Failed to unshare mount namespace (need root)");
[ ... ]
> + if (!strstr(ctrl_buf, "memory")) {
> + if (write(fd, "+memory", 7) != 7) {
> + close(fd);
> + so_reserve_mem_teardown(_metadata, self, variant);
> + SKIP(return, "cgroup2 memory controller not available");
> + }
> + self->restore_subtree_ctrl = true;
> + }
> + close(fd);
[Severity: Low]
Only the mount namespace is unshared, so this reads and modifies the
system-wide root cgroup.subtree_control. What happens if another memcg
user starts relying on the memory controller after this "+memory" write?
For example, a second instance of this test, or a cgroup selftest running
in parallel, would see memory already enabled. It would create its own
child cgroup and rely on memory.max.
The unconditional "-memory" write in FIXTURE_TEARDOWN() would still
succeed. cgroup_subtree_control_write() only returns EBUSY when a live
child has memory in its own subtree_control:
kernel/cgroup/cgroup.c:cgroup_subtree_control_write() {
...
/* a child has it enabled? */
cgroup_for_each_live_child(child, cgrp) {
if (child->subtree_control & (1 << ssid)) {
ret = -EBUSY;
goto out_unlock;
}
}
...
}
Children that only inherit the memory css don't block the disable.
Wouldn't the other user's memory css then be killed while it is still in
use? memory.max would disappear, and new sockets would lose sk_memcg, so
SO_RESERVE_MEM would start returning EOPNOTSUPP.
The return value of the "-memory" write is also ignored.
[ ... ]
> +static void check_non_tcp_rejected(struct __test_metadata *_metadata,
> + int domain, int type, int protocol,
> + int val)
> +{
> + int fd = socket(domain, type, protocol);
> +
> + if (fd < 0) {
> + EXPECT_TRUE(errno == EAFNOSUPPORT ||
> + errno == EPROTONOSUPPORT ||
> + errno == ENOPROTOOPT);
> + return;
> + }
[Severity: Low]
Should EPERM be accepted here too, ideally by skipping this subcase?
The non_tcp_rejected test calls this helper with
AF_INET/SOCK_RAW/IPPROTO_ICMP. inet_create() rejects raw sockets when the
caller lacks CAP_NET_RAW:
net/ipv4/af_inet.c:inet_create() {
...
err = -EPERM;
if (sock->type == SOCK_RAW && !kern &&
!ns_capable(net->user_ns, CAP_NET_RAW))
goto out_rcu_unlock;
...
}
The fixture setup needs only CAP_SYS_ADMIN and filesystem permissions: it
calls unshare(CLONE_NEWNS), mounts cgroup2 and writes cgroup files. So a
runner with CAP_SYS_ADMIN but without CAP_NET_RAW gets through setup.
Examples are root under capsh --drop=cap_net_raw, or a container that
drops NET_RAW.
That runner then fails this EXPECT_TRUE() on EPERM, and SO_RESERVE_MEM is
never tested on a raw socket.
[ ... ]
> +TEST_F(so_reserve_mem, cgroup_memory_max)
> +{
[ ... ]
> + /* Move test process back to root cgroup before lowering cg_child's
> + * memory.max so the limit only governs the socket's memcg charges
> + * and cannot trigger OOM on the test process itself.
> + */
> + snprintf(procs_path, sizeof(procs_path), "%s/cgroup.procs",
> + self->cg_root);
> + procs_fd = open(procs_path, O_WRONLY);
> + ASSERT_GE(procs_fd, 0);
> + ASSERT_GT(dprintf(procs_fd, "%d\n", getpid()), 0);
[Severity: Medium]
Does this assume that cg_root is the real hierarchy root?
In a non-init cgroup namespace, cgroup_do_get_tree() makes the namespace
root cgroup the root of the cgroup2 mount. This is the default for Docker
on cgroup v2, systemd-nspawn and similar. That cgroup has a parent, so it
is not exempt from the no-internal-process rule.
Consider a namespace root that already has memory in its subtree_control
and populated domain children, as in a systemd-managed container. Setup
succeeds without even writing "+memory", but the move back to cg_root
then fails in cgroup_migrate_vet_dst():
kernel/cgroup/cgroup.c:cgroup_migrate_vet_dst() {
...
if (cgroup_can_be_thread_root(dst_cgrp) || cgroup_is_threaded(dst_cgrp))
return 0;
/* apply no-internal-process constraint */
if (dst_cgrp->subtree_control)
return -EBUSY;
...
}
Wouldn't this ASSERT_GT() then report a failure rather than a skip?
The same move in FIXTURE_TEARDOWN() fails silently in every test. Then:
- rmdir(self->cg_child) fails with EBUSY because the process is still
inside
- cg_child[0] is cleared anyway
- an empty test_<pid> cgroup stays in the real hierarchy after the
process exits
[ ... ]
> + /* Send & drain traffic; cfd must retain its 16-page forward alloc,
> + * while sfd (0 reserve) reclaims its forward alloc back to 0.
> + */
> + ASSERT_EQ(send(cfd, buf, sizeof(buf), 0), (ssize_t)sizeof(buf));
> + ASSERT_EQ(recv(sfd, buf, sizeof(buf), MSG_WAITALL), (ssize_t)sizeof(buf));
> + wait_wmem_drained(_metadata, cfd);
> +
> + EXPECT_GE(get_fwd_alloc(_metadata, cfd), (__u32)(16 * ps));
[Severity: Low]
Can this check fail intermittently?
wait_wmem_drained() returns as soon as SO_MEMINFO shows
SK_MEMINFO_WMEM_QUEUED == 0. SK_MEMINFO_FWD_ALLOC is then read by a
separate getsockopt(). sk_get_meminfo() reads each field with its own
READ_ONCE() and takes no socket lock.
When an ACK frees a skb from the rtx queue of cfd, the counters are
updated in this order:
include/net/tcp.h:tcp_wmem_free_skb() {
sk_wmem_queued_add(sk, -skb->truesize);
if (!skb_zcopy_pure(skb))
sk_mem_uncharge(sk, skb->truesize);
...
}
That ACK processing can run on another CPU. It can come from the
delayed-ACK timer, from ksoftirqd after the test thread has migrated, or
from a preemptible softirq thread on PREEMPT_RT.
The test can then see WMEM_QUEUED == 0 while FWD_ALLOC is still
16 * ps - truesize. FWD_ALLOC is not polled again after that.
Would polling until a single SO_MEMINFO snapshot shows both
WMEM_QUEUED == 0 and FWD_ALLOC >= 16 * ps close this window?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260925135244.3715196-1-edumazet%40google.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test
2026-09-28 19:54 ` netdev-bot+sashiko
@ 2026-09-28 20:03 ` Eric Dumazet
2026-09-28 20:18 ` Wei Wang
0 siblings, 1 reply; 10+ messages in thread
From: Eric Dumazet @ 2026-09-28 20:03 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: davem, kuba, pabeni, weiwan, horms, caixinchen1, netdev, edumazet
On Mon, Sep 28, 2026 at 9:54 PM <netdev-bot+sashiko@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 5 potential
> issue(s) to consider.
>
> Critical: 0 · High: 0 · Medium: 1 · Low: 4
This is a test. Lets remove it if we have to work days on it.
I will not send a V3.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test
2026-09-28 20:03 ` Eric Dumazet
@ 2026-09-28 20:18 ` Wei Wang
0 siblings, 0 replies; 10+ messages in thread
From: Wei Wang @ 2026-09-28 20:18 UTC (permalink / raw)
To: Eric Dumazet
Cc: netdev-bot+sashiko, davem, kuba, pabeni, horms, caixinchen1,
netdev, edumazet
On Mon, Sep 28, 2026 at 1:03 PM Eric Dumazet <edumazet@google.com> wrote:
>
> On Mon, Sep 28, 2026 at 9:54 PM <netdev-bot+sashiko@kernel.org> wrote:
> >
> > Thank you for your contribution! Sashiko AI review found 5 potential
> > issue(s) to consider.
> >
> > Critical: 0 · High: 0 · Medium: 1 · Low: 4
>
> This is a test. Lets remove it if we have to work days on it.
>
> I will not send a V3.
Reviewed-by: Wei Wang <weiwan@google.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds
2026-09-25 13:52 [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds Eric Dumazet
2026-09-25 13:52 ` [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value Eric Dumazet
2026-09-25 13:52 ` [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test Eric Dumazet
@ 2026-09-29 2:40 ` patchwork-bot+netdevbpf
2 siblings, 0 replies; 10+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-29 2:40 UTC (permalink / raw)
To: Eric Dumazet
Cc: davem, kuba, pabeni, weiwan, horms, caixinchen1, netdev, edumazet
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Fri, 25 Sep 2026 13:52:42 +0000 you wrote:
> This series fixes two issues with SO_RESERVE_MEM and adds a kselftest suite:
>
> - Patch 1 restricts sock_reserve_memory() to TCP sockets (sk_is_tcp(sk))
> instead of sk_has_account(sk), and caps the requested reservation to
> SZ_1G in sk_setsockopt(SO_RESERVE_MEM).
>
> Currently, sk_has_account(sk) allows SO_RESERVE_MEM on UDP sockets,
> where sk->sk_forward_alloc is protected by sk->sk_receive_queue.lock
> rather than the socket lock, and udp_rmem_release() does not account
> for sk_unused_reserved_mem(sk). Concurrent UDP packet processing and
> setsockopt(SO_RESERVE_MEM) corrupt sk_forward_alloc and memcg
> accounting. In addition, values near INT_MAX overflow 32-bit signed
> int in sk_mem_pages(delta) and (pages << PAGE_SHIFT).
>
> [...]
Here is the summary with links:
- [v2,net,1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value
https://git.kernel.org/netdev/net/c/37e02c42a00b
- [v2,net,2/2] selftests/net: add SO_RESERVE_MEM test
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-29 2:40 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-25 13:52 [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds Eric Dumazet
2026-09-25 13:52 ` [PATCH v2 net 1/2] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value Eric Dumazet
2026-09-28 17:23 ` Wei Wang
2026-09-25 13:52 ` [PATCH v2 net 2/2] selftests/net: add SO_RESERVE_MEM test Eric Dumazet
2026-09-28 17:22 ` Wei Wang
2026-09-28 18:27 ` Eric Dumazet
2026-09-28 19:54 ` netdev-bot+sashiko
2026-09-28 20:03 ` Eric Dumazet
2026-09-28 20:18 ` Wei Wang
2026-09-29 2:40 ` [PATCH v2 net 0/2] net: fix SO_RESERVE_MEM socket type restriction and value bounds patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox