Skip to contentSkip to conversation

Fix netlink rcvbuf wraparound

PATCH In review Jason Baron 5 participants 12 messages Jun 18, 2025 – Jun 25, 2025

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

v2 cover Fix netlink rcvbuf wraparound

The sk->sk_rmem_alloc field of a netlink socket can wraparound as a signed int when comparing to sk->sk_rcvbuf, when sk->sk_rcvbuf approaches INT_MAX. This can be reproduced by forcing sk->sk_rcvbuf to INT_MAX and this can exhaust all of memory.

I've added a sock_rcvbuf_has_space() helper function to generalize the fix as a similar approach has already been implemented for udp sockets.

v2:
-add Fixes:
-add sock_rcvbuf_has_space() helper
-use helper functions for udp netlink
-remove excessive parentheses

Jason Baron (3):
net: add sock_rcvbuf_has_space() helper
udp: use __sock_rcvbuf_has_space() helper
netlink: Fix wraparound of sk->sk_rmem_alloc

 include/net/sock.h       | 38 ++++++++++++++++++++++++++++++++++++++
 net/ipv4/udp.c           | 13 ++-----------
 net/netlink/af_netlink.c | 35 +++++++++++++++++++++--------------
 3 files changed, 61 insertions(+), 25 deletions(-)
Signature · 1 line
2.25.1
#1 lore raw

PATCH v2 1/3 net: add sock_rcvbuf_has_space() helper

Let's add helper function that abstract the sk->sk_rcvbuf limits check for wraparound that Kuniyuki Iwashima introduce for udp receive path:

commit 5a465a0da13e ("udp: Fix multiple wraparounds of sk->sk_rmem_alloc.")

Note that I copied Kuniyuki Iwashima's comments into sock_rcvbuf_has_space(). Subsequent patches will use this for udp and netlink sockets.

---
 include/net/sock.h | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)
include/net/sock.h +38 -0
index 70c0b570a21f..5f4fdf96bba6 100644
@@ -2484,6 +2484,44 @@ static inline unsigned long sock_wspace(struct sock *sk)
 	return amt;
 }
 
+static inline bool __sock_rcvbuf_has_space(unsigned int rmem,
+					   unsigned int rcvbuf,
+					   unsigned int size)
+{
+	/* Immediately drop when the receive queue is full. */
+	if (rmem + size > rcvbuf) {
+		if (rcvbuf > INT_MAX >> 1)
+			return false;
+
+		/* Always allow at least one packet for small buffer. */
+		if (rmem > rcvbuf)
+			return false;
+	}
+	return true;
+}
+
+/**
+ * sock_rcvbuf_has_space - check if sk->sk_rcvbuf has space
+ * @sk: socket
+ * @skb: socket buffer
+ *
+ * Can skb->truesize bytes be added to the socket receive buffer
+ * while respecting the sk->sk_rcvbuf limit. Note that rcvbuf and
+ * rmem are assigned to unsigned int to avoid wraparound.
+ *
+ * Return: true if there is space, false otherwise
+ */
+static inline bool sock_rcvbuf_has_space(struct sock *sk, struct sk_buff *skb)
+{
+	unsigned int rmem, rcvbuf;
+
+	/* Cast to unsigned int performs the boundary check for INT_MAX. */
+	rmem = atomic_read(&sk->sk_rmem_alloc);
+	rcvbuf = READ_ONCE(sk->sk_rcvbuf);
+
+	return __sock_rcvbuf_has_space(rmem, rcvbuf, skb->truesize);
+}
+
 /* Note:
  *  We use sk->sk_wq_raw, from contexts knowing this
  *  pointer is not NULL and cannot disappear/change.
Signature · 1 line
2.25.1
#2 lore raw

PATCH v2 2/3 udp: use __sock_rcvbuf_has_space() helper

In reply to Jason Baron

Make use of __sock_rcvbuf_has_space() in __udp_enqueue_schedule_skb().

---
 net/ipv4/udp.c | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)
net/ipv4/udp.c +2 -11
index dde52b8050b8..64e6c34a8fb8 100644
@@ -1733,17 +1733,8 @@ int __udp_enqueue_schedule_skb(struct sock *sk, struct sk_buff *skb)
 	rcvbuf = READ_ONCE(sk->sk_rcvbuf);
 	size = skb->truesize;
 
-	/* Immediately drop when the receive queue is full.
-	 * Cast to unsigned int performs the boundary check for INT_MAX.
-	 */
-	if (rmem + size > rcvbuf) {
-		if (rcvbuf > INT_MAX >> 1)
-			goto drop;
-
-		/* Always allow at least one packet for small buffer. */
-		if (rmem > rcvbuf)
-			goto drop;
-	}
+	if (!__sock_rcvbuf_has_space(rmem, rcvbuf, size))
+		goto drop;
 
 	/* Under mem pressure, it might be helpful to help udp_recvmsg()
 	 * having linear skbs :
Signature · 1 line
2.25.1
#3 lore raw

PATCH v2 3/3 netlink: Fix wraparound of sk->sk_rmem_alloc

In reply to Jason Baron

For netlink sockets, when comparing allocated rmem memory with the rcvbuf limit, the comparison is done using signed values. This means that if rcvbuf is near INT_MAX, then sk->sk_rmem_alloc may become negative in the comparison with rcvbuf which will yield incorrect results.

This can be reproduced by using the program from SOCK_DIAG(7) with some slight modifications. First, setting sk->sk_rcvbuf to INT_MAX using SO_RCVBUFFORCE and then secondly running the "send_query()" in a loop while not calling "receive_responses()". In this case, the value of sk->sk_rmem_alloc will continuously wrap around and thus more memory is allocated than the sk->sk_rcvbuf limit. This will eventually fill all of memory leading to an out of memory condition with skbs filling up the slab.

Let's fix this in a similar manner to:
commit 5a465a0da13e ("udp: Fix multiple wraparounds of sk->sk_rmem_alloc.")

As noted in that fix, if there are multiple threads writing to a netlink socket it's possible to slightly exceed rcvbuf value. But as noted this avoids an expensive 'atomic_add_return()' for the common case. I've confirmed that with the fix the modified program from SOCK_DIAG(7) can no longer fill memory and the sk->sk_rcvbuf limit is enforced.

Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")
---
 net/netlink/af_netlink.c | 35 +++++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 14 deletions(-)
net/netlink/af_netlink.c +21 -14
index e8972a857e51..a770b90abe7d 100644
@@ -1216,8 +1216,8 @@ int netlink_attachskb(struct sock *sk, struct sk_buff *skb,
 
 	nlk = nlk_sk(sk);
 
-	if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
-	     test_bit(NETLINK_S_CONGESTED, &nlk->state))) {
+	if (!sock_rcvbuf_has_space(sk, skb) ||
+	    test_bit(NETLINK_S_CONGESTED, &nlk->state)) {
 		DECLARE_WAITQUEUE(wait, current);
 		if (!*timeo) {
 			if (!ssk || netlink_is_kernel(ssk))
@@ -1230,7 +1230,7 @@ int netlink_attachskb(struct sock *sk, struct sk_buff *skb,
 		__set_current_state(TASK_INTERRUPTIBLE);
 		add_wait_queue(&nlk->wait, &wait);
 
-		if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
+		if ((!sock_rcvbuf_has_space(sk, skb) ||
 		     test_bit(NETLINK_S_CONGESTED, &nlk->state)) &&
 		    !sock_flag(sk, SOCK_DEAD))
 			*timeo = schedule_timeout(*timeo);
@@ -1383,12 +1383,15 @@ EXPORT_SYMBOL_GPL(netlink_strict_get_check);
 static int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
 {
 	struct netlink_sock *nlk = nlk_sk(sk);
+	unsigned int rmem, rcvbuf;
 
-	if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
+	if (sock_rcvbuf_has_space(sk, skb) &&
 	    !test_bit(NETLINK_S_CONGESTED, &nlk->state)) {
 		netlink_skb_set_owner_r(skb, sk);
 		__netlink_sendskb(sk, skb);
-		return atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1);
+		rmem = atomic_read(&sk->sk_rmem_alloc);
+		rcvbuf = READ_ONCE(sk->sk_rcvbuf);
+		return rmem > (rcvbuf >> 1);
 	}
 	return -1;
 }
@@ -1895,6 +1898,7 @@ static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
 	struct netlink_sock *nlk = nlk_sk(sk);
 	size_t copied, max_recvmsg_len;
 	struct sk_buff *skb, *data_skb;
+	unsigned int rmem, rcvbuf;
 	int err, ret;
 
 	if (flags & MSG_OOB)
@@ -1960,12 +1964,15 @@ static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
 
 	skb_free_datagram(sk, skb);
 
-	if (READ_ONCE(nlk->cb_running) &&
-	    atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2) {
-		ret = netlink_dump(sk, false);
-		if (ret) {
-			WRITE_ONCE(sk->sk_err, -ret);
-			sk_error_report(sk);
+	if (READ_ONCE(nlk->cb_running)) {
+		rmem = atomic_read(&sk->sk_rmem_alloc);
+		rcvbuf = READ_ONCE(sk->sk_rcvbuf);
+		if (rmem <= (rcvbuf >> 1)) {
+			ret = netlink_dump(sk, false);
+			if (ret) {
+				WRITE_ONCE(sk->sk_err, -ret);
+				sk_error_report(sk);
+			}
 		}
 	}
 
@@ -2258,9 +2265,6 @@ static int netlink_dump(struct sock *sk, bool lock_taken)
 		goto errout_skb;
 	}
 
-	if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf)
-		goto errout_skb;
-
 	/* NLMSG_GOODSIZE is small to avoid high order allocations being
 	 * required, but it makes sense to _attempt_ a 32KiB allocation
 	 * to reduce number of system calls on dump operations, if user
@@ -2283,6 +2287,9 @@ static int netlink_dump(struct sock *sk, bool lock_taken)
 	if (!skb)
 		goto errout_skb;
 
+	if (!sock_rcvbuf_has_space(sk, skb))
+		goto errout_skb;
+
 	/* Trim skb to allocated size. User is expected to provide buffer as
 	 * large as max(min_dump_alloc, 32KiB (max_recvmsg_len capped at
 	 * netlink_recvmsg())). dump will pack as many smaller messages as
Signature · 1 line
2.25.1
#4 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

From: Jason Baron <[email protected]>
Date: Wed, 18 Jun 2025 19:13:23 -0400

Quoted 15 lines

For netlink sockets, when comparing allocated rmem memory with the rcvbuf limit, the comparison is done using signed values. This means that if rcvbuf is near INT_MAX, then sk->sk_rmem_alloc may become negative in the comparison with rcvbuf which will yield incorrect results.

This can be reproduced by using the program from SOCK_DIAG(7) with some slight modifications. First, setting sk->sk_rcvbuf to INT_MAX using SO_RCVBUFFORCE and then secondly running the "send_query()" in a loop while not calling "receive_responses()". In this case, the value of sk->sk_rmem_alloc will continuously wrap around and thus more memory is allocated than the sk->sk_rcvbuf limit. This will eventually fill all of memory leading to an out of memory condition with skbs filling up the slab.

Quoted 7 lines

Let's fix this in a similar manner to:
commit 5a465a0da13e ("udp: Fix multiple wraparounds of sk->sk_rmem_alloc.")

As noted in that fix, if there are multiple threads writing to a netlink socket it's possible to slightly exceed rcvbuf value. But as noted this avoids an expensive 'atomic_add_return()' for the common case.

This was because UDP RX path is the fast path, but netlink isn't. Also, it's common for UDP that multiple packets for the same socket are processed concurrently, and 850cbaddb52d dropped lock_sock from the path.

maintainer
#5 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

Quoted 25 lines · On Wed, 18 Jun 2025 23:13:02 -0700 Kuniyuki Iwashima wrote:

From: Jason Baron <[email protected]>
Date: Wed, 18 Jun 2025 19:13:23 -0400

Quoted 15 lines

For netlink sockets, when comparing allocated rmem memory with the rcvbuf limit, the comparison is done using signed values. This means that if rcvbuf is near INT_MAX, then sk->sk_rmem_alloc may become negative in the comparison with rcvbuf which will yield incorrect results.

This can be reproduced by using the program from SOCK_DIAG(7) with some slight modifications. First, setting sk->sk_rcvbuf to INT_MAX using SO_RCVBUFFORCE and then secondly running the "send_query()" in a loop while not calling "receive_responses()". In this case, the value of sk->sk_rmem_alloc will continuously wrap around and thus more memory is allocated than the sk->sk_rcvbuf limit. This will eventually fill all of memory leading to an out of memory condition with skbs filling up the slab.

Quoted 7 lines

Let's fix this in a similar manner to:
commit 5a465a0da13e ("udp: Fix multiple wraparounds of sk->sk_rmem_alloc.")

As noted in that fix, if there are multiple threads writing to a netlink socket it's possible to slightly exceed rcvbuf value. But as noted this avoids an expensive 'atomic_add_return()' for the common case.

Quoted 4 lines

This was because UDP RX path is the fast path, but netlink isn't. Also, it's common for UDP that multiple packets for the same socket are processed concurrently, and 850cbaddb52d dropped lock_sock from the path.

To be clear -- are you saying we should fix this differently? Or perhaps that the problem doesn't exist? The change doesn't seem very intrusive..

maintainer
#6 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

Quoted 26 lines · Jakub Kicinski
Quoted 25 lines · On Wed, 18 Jun 2025 23:13:02 -0700 Kuniyuki Iwashima wrote:

From: Jason Baron <[email protected]>
Date: Wed, 18 Jun 2025 19:13:23 -0400

Quoted 22 lines

For netlink sockets, when comparing allocated rmem memory with the rcvbuf limit, the comparison is done using signed values. This means that if rcvbuf is near INT_MAX, then sk->sk_rmem_alloc may become negative in the comparison with rcvbuf which will yield incorrect results.

This can be reproduced by using the program from SOCK_DIAG(7) with some slight modifications. First, setting sk->sk_rcvbuf to INT_MAX using SO_RCVBUFFORCE and then secondly running the "send_query()" in a loop while not calling "receive_responses()". In this case, the value of sk->sk_rmem_alloc will continuously wrap around and thus more memory is allocated than the sk->sk_rcvbuf limit. This will eventually fill all of memory leading to an out of memory condition with skbs filling up the slab.

Let's fix this in a similar manner to:
commit 5a465a0da13e ("udp: Fix multiple wraparounds of sk->sk_rmem_alloc.")

As noted in that fix, if there are multiple threads writing to a netlink socket it's possible to slightly exceed rcvbuf value. But as noted this avoids an expensive 'atomic_add_return()' for the common case.

Quoted 8 lines
Quoted 4 lines

This was because UDP RX path is the fast path, but netlink isn't. Also, it's common for UDP that multiple packets for the same socket are processed concurrently, and 850cbaddb52d dropped lock_sock from the path.

To be clear -- are you saying we should fix this differently? Or perhaps that the problem doesn't exist? The change doesn't seem very intrusive..

AFAICS the race is possible even with netlink as netlink_unicast() runs without the socket lock, too.

The point is that for UDP the scenario with multiple threads enqueuing a packet into the same socket is a critical path, optimizing for performances and allowing some memory accounting inaccuracy makes sense.

For netlink socket, that scenario looks a patological one and I think we should prefer accuracy instead of optimization.

Thanks,

Paolo

#7 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

Quoted 48 lines · Paolo Abeni

!-------------------------------------------------------------------|
This Message Is From an External Sender
This message came from outside your organization.
|-------------------------------------------------------------------!

Quoted 26 lines · Jakub Kicinski
Quoted 25 lines · On Wed, 18 Jun 2025 23:13:02 -0700 Kuniyuki Iwashima wrote:

From: Jason Baron <[email protected]>
Date: Wed, 18 Jun 2025 19:13:23 -0400

Quoted 22 lines

For netlink sockets, when comparing allocated rmem memory with the rcvbuf limit, the comparison is done using signed values. This means that if rcvbuf is near INT_MAX, then sk->sk_rmem_alloc may become negative in the comparison with rcvbuf which will yield incorrect results.

This can be reproduced by using the program from SOCK_DIAG(7) with some slight modifications. First, setting sk->sk_rcvbuf to INT_MAX using SO_RCVBUFFORCE and then secondly running the "send_query()" in a loop while not calling "receive_responses()". In this case, the value of sk->sk_rmem_alloc will continuously wrap around and thus more memory is allocated than the sk->sk_rcvbuf limit. This will eventually fill all of memory leading to an out of memory condition with skbs filling up the slab.

Let's fix this in a similar manner to:
commit 5a465a0da13e ("udp: Fix multiple wraparounds of sk->sk_rmem_alloc.")

As noted in that fix, if there are multiple threads writing to a netlink socket it's possible to slightly exceed rcvbuf value. But as noted this avoids an expensive 'atomic_add_return()' for the common case.

Quoted 8 lines
Quoted 4 lines

This was because UDP RX path is the fast path, but netlink isn't. Also, it's common for UDP that multiple packets for the same socket are processed concurrently, and 850cbaddb52d dropped lock_sock from the path.

To be clear -- are you saying we should fix this differently? Or perhaps that the problem doesn't exist? The change doesn't seem very intrusive..

AFAICS the race is possible even with netlink as netlink_unicast() runs without the socket lock, too.

The point is that for UDP the scenario with multiple threads enqueuing a packet into the same socket is a critical path, optimizing for performances and allowing some memory accounting inaccuracy makes sense.

Quoted 7 lines

For netlink socket, that scenario looks a patological one and I think we should prefer accuracy instead of optimization.

Thanks,

Paolo

Hi,

Since the current netlink code already allows memory allocation to slightly exceed rcvbuf even when there is no overflow, I didn't think we would want to change this especially for smaller buffers as it may cause a regression. I tried to preserve this behavior if you look at the __sock_rcvbuf_has_space() function in patch 1/3.

For larger buffers, I think accurate accounting could make sense, but I thought it made more sense to try and optimize this common case as Kuniyuki Iwashima did, consolidate code between multiple use-cases and not overly special case handling for large buffers.

Thanks,

-Jason

maintainer
#8 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

In reply to Paolo Abeni
Quoted 7 lines · On Tue, 24 Jun 2025 09:55:15 +0200 Paolo Abeni wrote:
Quoted 3 lines

To be clear -- are you saying we should fix this differently? Or perhaps that the problem doesn't exist? The change doesn't seem very intrusive..

AFAICS the race is possible even with netlink as netlink_unicast() runs without the socket lock, too.

Quoted 6 lines

The point is that for UDP the scenario with multiple threads enqueuing a packet into the same socket is a critical path, optimizing for performances and allowing some memory accounting inaccuracy makes sense.

For netlink socket, that scenario looks a patological one and I think we should prefer accuracy instead of optimization.

Could you ELI5 what you mean? Are you suggesting a lock around every sk_rmem write for netlink sockets?
If we think this is an attack vector the attacker can simply use a UDP socket instead. Or do you think it'd lead to simpler code?

#9 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

Date: Tue, 24 Jun 2025 07:11:57 -0700
From: Jakub Kicinski <[email protected]>

Quoted 12 lines · On Tue, 24 Jun 2025 09:55:15 +0200 Paolo Abeni wrote:
Quoted 11 lines
Quoted 3 lines

To be clear -- are you saying we should fix this differently? Or perhaps that the problem doesn't exist? The change doesn't seem very intrusive..

AFAICS the race is possible even with netlink as netlink_unicast() runs without the socket lock, too.

The point is that for UDP the scenario with multiple threads enqueuing a packet into the same socket is a critical path, optimizing for performances and allowing some memory accounting inaccuracy makes sense.

Quoted 7 lines
Quoted 2 lines

For netlink socket, that scenario looks a patological one and I think we should prefer accuracy instead of optimization.

Could you ELI5 what you mean? Are you suggesting a lock around every sk_rmem write for netlink sockets?
If we think this is an attack vector the attacker can simply use a UDP socket instead. Or do you think it'd lead to simpler code?

I was wondering if atomic_add_return() is expensive for netlink, and if not, we could use it like below. I'm also not sure we want to keep the allow-at-least-one-skb rule for netlink though, which comes from the first condition in __sock_queue_rcv_skb() for UDP in the past, IIRC.

untested:

---8<---

net/netlink/af_netlink.c +45 -33
index e8972a857e51..e1a9ae7ff521 100644
@@ -387,7 +387,6 @@ static void netlink_skb_set_owner_r(struct sk_buff *skb, struct sock *sk)
 	WARN_ON(skb->sk != NULL);
 	skb->sk = sk;
 	skb->destructor = netlink_skb_destructor;
-	atomic_add(skb->truesize, &sk->sk_rmem_alloc);
 	sk_mem_charge(sk, skb->truesize);
 }
 
@@ -1212,41 +1211,45 @@ struct sk_buff *netlink_alloc_large_skb(unsigned int size, int broadcast)
 int netlink_attachskb(struct sock *sk, struct sk_buff *skb,
 		      long *timeo, struct sock *ssk)
 {
+	unsigned long rmem = atomic_add_return(skb->truesize, &sk->sk_rmem_alloc);
+	DECLARE_WAITQUEUE(wait, current);
 	struct netlink_sock *nlk;
 
 	nlk = nlk_sk(sk);
 
-	if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
-	     test_bit(NETLINK_S_CONGESTED, &nlk->state))) {
-		DECLARE_WAITQUEUE(wait, current);
-		if (!*timeo) {
-			if (!ssk || netlink_is_kernel(ssk))
-				netlink_overrun(sk);
-			sock_put(sk);
-			kfree_skb(skb);
-			return -EAGAIN;
-		}
-
-		__set_current_state(TASK_INTERRUPTIBLE);
-		add_wait_queue(&nlk->wait, &wait);
+	if (rmem == skb->truesize ||
+	    (rmem < sk->sk_rcvbuf && !test_bit(NETLINK_S_CONGESTED, &nlk->state))) {
+		netlink_skb_set_owner_r(skb, sk);
+		return 0;
+	}
 
-		if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
-		     test_bit(NETLINK_S_CONGESTED, &nlk->state)) &&
-		    !sock_flag(sk, SOCK_DEAD))
-			*timeo = schedule_timeout(*timeo);
+	atomic_dec(skb->truesize, &sk->sk_rmem_alloc);
 
-		__set_current_state(TASK_RUNNING);
-		remove_wait_queue(&nlk->wait, &wait);
+	if (!*timeo) {
+		if (!ssk || netlink_is_kernel(ssk))
+			netlink_overrun(sk);
 		sock_put(sk);
+		kfree_skb(skb);
+		return -EAGAIN;
+	}
 
-		if (signal_pending(current)) {
-			kfree_skb(skb);
-			return sock_intr_errno(*timeo);
-		}
-		return 1;
+	__set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&nlk->wait, &wait);
+
+	if ((atomic_read(&sk->sk_rmem_alloc) + skb->truesize > sk->sk_rcvbuf ||
+	     test_bit(NETLINK_S_CONGESTED, &nlk->state)) &&
+	    !sock_flag(sk, SOCK_DEAD))
+		*timeo = schedule_timeout(*timeo);
+
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue(&nlk->wait, &wait);
+	sock_put(sk);
+
+	if (signal_pending(current)) {
+		kfree_skb(skb);
+		return sock_intr_errno(*timeo);
 	}
-	netlink_skb_set_owner_r(skb, sk);
-	return 0;
+	return 1;
 }
 
 static int __netlink_sendskb(struct sock *sk, struct sk_buff *skb)
@@ -1307,6 +1310,7 @@ static int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb,
 	ret = -ECONNREFUSED;
 	if (nlk->netlink_rcv != NULL) {
 		ret = skb->len;
+		atomic_add(skb->truesize, &sk->sk_rmem_alloc);
 		netlink_skb_set_owner_r(skb, sk);
 		NETLINK_CB(skb).sk = ssk;
 		netlink_deliver_tap_kernel(sk, ssk, skb);
@@ -1382,14 +1386,18 @@ EXPORT_SYMBOL_GPL(netlink_strict_get_check);
 
 static int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
 {
+	unsigned long rmem = atomic_add_return(skb->truesize, &sk->sk_rmem_alloc);
+	unsigned int rcvbuf = READ_ONCE(sk->sk_rcvbuf);
 	struct netlink_sock *nlk = nlk_sk(sk);
 
-	if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
-	    !test_bit(NETLINK_S_CONGESTED, &nlk->state)) {
+	if (rmem == skb->truesize ||
+	    (size <= rcvbuf && !test_bit(NETLINK_S_CONGESTED, &nlk->state))) {
 		netlink_skb_set_owner_r(skb, sk);
 		__netlink_sendskb(sk, skb);
-		return atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1);
+		return size > (rcvbuf >> 1);
 	}
+
+	atomic_dec(skb->truesize, &sk->sk_rmem_alloc);
 	return -1;
 }
 
@@ -2249,6 +2257,7 @@ static int netlink_dump(struct sock *sk, bool lock_taken)
 	struct module *module;
 	int err = -ENOBUFS;
 	int alloc_min_size;
+	unsigned int rmem;
 	int alloc_size;
 
 	if (!lock_taken)
@@ -2258,9 +2267,6 @@ static int netlink_dump(struct sock *sk, bool lock_taken)
 		goto errout_skb;
 	}
 
-	if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf)
-		goto errout_skb;
-
 	/* NLMSG_GOODSIZE is small to avoid high order allocations being
 	 * required, but it makes sense to _attempt_ a 32KiB allocation
 	 * to reduce number of system calls on dump operations, if user
@@ -2283,6 +2289,12 @@ static int netlink_dump(struct sock *sk, bool lock_taken)
 	if (!skb)
 		goto errout_skb;
 
+	rmem = atomic_add_return(skb->truesize, &sk->sk_rmem_alloc);
+	if (rmem != skb->truesize && rmem >= sk->sk_rcvbuf) {
+		atomic_dec(skb->truesize, &sk->sk_rmem_alloc);
+		goto errout_skb;
+	}
+
 	/* Trim skb to allocated size. User is expected to provide buffer as
 	 * large as max(min_dump_alloc, 32KiB (max_recvmsg_len capped at
 	 * netlink_recvmsg())). dump will pack as many smaller messages as

---8<---

maintainer
#10 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

Quoted 17 lines · On Tue, 24 Jun 2025 10:08:41 -0700 Kuniyuki Iwashima wrote:

Date: Tue, 24 Jun 2025 07:11:57 -0700
From: Jakub Kicinski <[email protected]>

Quoted 15 lines · On Tue, 24 Jun 2025 09:55:15 +0200 Paolo Abeni wrote:
Quoted 13 lines
Quoted 3 lines

To be clear -- are you saying we should fix this differently? Or perhaps that the problem doesn't exist? The change doesn't seem very intrusive..

AFAICS the race is possible even with netlink as netlink_unicast() runs without the socket lock, too.

The point is that for UDP the scenario with multiple threads enqueuing a packet into the same socket is a critical path, optimizing for performances and allowing some memory accounting inaccuracy makes sense.

For netlink socket, that scenario looks a patological one and I think we should prefer accuracy instead of optimization.

Quoted 7 lines
Quoted 4 lines

Could you ELI5 what you mean? Are you suggesting a lock around every sk_rmem write for netlink sockets?
If we think this is an attack vector the attacker can simply use a UDP socket instead. Or do you think it'd lead to simpler code?

I was wondering if atomic_add_return() is expensive for netlink, and if not, we could use it like below.

Ah, got it. That does look simpler.

nit: Please don't hide the atomic_add_return() in local variable init, as it need validation and error handling.

Quoted 3 lines

I'm also not sure we want to keep the allow-at-least-one-skb rule for netlink though, which comes from the first condition in __sock_queue_rcv_skb() for UDP in the past, IIRC.

#11 lore raw

Re: [PATCH net-next v2 3/3] netlink: Fix wraparound of sk->sk_rmem_alloc

Quoted 24 lines · Jakub Kicinski
Quoted 22 lines · On Tue, 24 Jun 2025 10:08:41 -0700 Kuniyuki Iwashima wrote:

Date: Tue, 24 Jun 2025 07:11:57 -0700
From: Jakub Kicinski <[email protected]>

Quoted 19 lines · On Tue, 24 Jun 2025 09:55:15 +0200 Paolo Abeni wrote:
Quoted 7 lines
Quoted 3 lines

To be clear -- are you saying we should fix this differently? Or perhaps that the problem doesn't exist? The change doesn't seem very intrusive..

AFAICS the race is possible even with netlink as netlink_unicast() runs without the socket lock, too.

Quoted 6 lines

The point is that for UDP the scenario with multiple threads enqueuing a packet into the same socket is a critical path, optimizing for performances and allowing some memory accounting inaccuracy makes sense.

For netlink socket, that scenario looks a patological one and I think we should prefer accuracy instead of optimization.

Could you ELI5 what you mean? Are you suggesting a lock around every sk_rmem write for netlink sockets?
If we think this is an attack vector the attacker can simply use a UDP socket instead. Or do you think it'd lead to simpler code?

Quoted 7 lines
Quoted 2 lines

I was wondering if atomic_add_return() is expensive for netlink, and if not, we could use it like below.

Ah, got it. That does look simpler.

nit: Please don't hide the atomic_add_return() in local variable init, as it need validation and error handling.

Makes sense.
Will post the official patch later.