mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb()
@ 2026-09-19 22:17 Hui Peng
  2026-09-20 19:27 ` Kuniyuki Iwashima
  2026-09-20 23:19 ` [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb() netdev-bot+sashiko
  0 siblings, 2 replies; 5+ messages in thread
From: Hui Peng @ 2026-09-19 22:17 UTC (permalink / raw)
  To: kuniyu, davem, edumazet, kuba, pabeni, horms; +Cc: netdev, linux-kernel

When an old OOB skb is replaced and discarded in manage_oob(), or when a
fully consumed OOB skb is skipped in unix_stream_read_skb(), the
discarded byte count is not subtracted from u->inq_len (or is subtracted
inconsistently), causing SIOCINQ / FIONREAD to report stale positive
byte counts on an empty AF_UNIX stream socket. Properly account
u->inq_len when dropping or skipping consumed OOB skbs.

Fixes: 314001f0bf92 ("af_unix: Add OOB support")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 42cffeafc8c1..1770af3c2684 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -2849,6 +2849,7 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sk_buff **last,
 		WRITE_ONCE(u->oob_skb, NULL);
 
 		if (!sock_flag(sk, SOCK_URGINLINE)) {
+			WRITE_ONCE(u->inq_len, u->inq_len - 1);
 			__skb_unlink(skb, &sk->sk_receive_queue);
 			unread_skb = skb;
 			skb = skb_peek(&sk->sk_receive_queue);
@@ -2884,6 +2885,7 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
 		return err;
 
 	mutex_lock(&u->iolock);
+again:
 	spin_lock(&queue->lock);
 
 	skb = __skb_dequeue(queue);
@@ -2893,6 +2895,12 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
 		return -EAGAIN;
 	}
 
+	if (!unix_skb_len(skb)) {
+		spin_unlock(&queue->lock);
+		consume_skb(skb);
+		goto again;
+	}
+
 	WRITE_ONCE(u->inq_len, u->inq_len - unix_skb_len(skb));
 
 #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
@@ -2908,6 +2916,11 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
 
 	spin_unlock(&queue->lock);
 
+	if (UNIXCB(skb).consumed) {
+		skb_pull(skb, UNIXCB(skb).consumed);
+		UNIXCB(skb).consumed = 0;
+	}
+
 	unix_orphan_scm(sk, skb);
 
 	mutex_unlock(&u->iolock);

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb()
  2026-09-19 22:17 [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb() Hui Peng
@ 2026-09-20 19:27 ` Kuniyuki Iwashima
  2026-09-21  5:48   ` [PATCH net v2 1/2] af_unix: decrement u->inq_len when skipping unread OOB skb in manage_oob() Hui Peng
  2026-09-20 23:19 ` [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb() netdev-bot+sashiko
  1 sibling, 1 reply; 5+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-20 19:27 UTC (permalink / raw)
  To: Hui Peng; +Cc: davem, edumazet, kuba, pabeni, horms, netdev, linux-kernel

On Sat, Sep 19, 2026 at 3:17 PM Hui Peng <benquike@gmail.com> wrote:
>
> When an old OOB skb is replaced and discarded in manage_oob(), or when a
> fully consumed OOB skb is skipped in unix_stream_read_skb(), the
> discarded byte count is not subtracted from u->inq_len (or is subtracted
> inconsistently), causing SIOCINQ / FIONREAD to report stale positive
> byte counts on an empty AF_UNIX stream socket. Properly account
> u->inq_len when dropping or skipping consumed OOB skbs.
>
> Fixes: 314001f0bf92 ("af_unix: Add OOB support")
> Assisted-by: LLM

Please tell your LLM not to mix different fixes into one.

btw I'm tempted to remove OOB support entirely.

---
pw-bot: cr

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb()
  2026-09-19 22:17 [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb() Hui Peng
  2026-09-20 19:27 ` Kuniyuki Iwashima
@ 2026-09-20 23:19 ` netdev-bot+sashiko
  1 sibling, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-09-20 23:19 UTC (permalink / raw)
  To: benquike
  Cc: kuniyu, davem, edumazet, kuba, pabeni, horms, netdev, linux-kernel

Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.

Critical: 1 · High: 0 · Medium: 3 · Low: 0

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).

- [Critical] Kernel BUG() (oops/panic) in the sockmap read path: the
  newly added `skb_pull(skb, UNIXCB(skb).consumed)` in…
- [Medium] Commit message does not match the code, and two of the three
  hunks are unrelated to inq_len accounting (scope creep).
- [Medium] Incorrect Fixes tag / wrong bug attribution.
- [Medium] Unbalanced SCM_RIGHTS accounting on the new early-drop path in
  `unix_stream_read_skb()`: the added block frees the dequeued skb with…

--

af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb()

This adds a u->inq_len decrement when manage_oob() discards a replaced OOB
skb, makes unix_stream_read_skb() skip and free fully consumed skbs, and
pulls the already consumed prefix out of the skb before handing it to the
recv_actor.  No Link: tags are present in the commit header.

A few questions on the changelog and on the two new blocks in
unix_stream_read_skb().

> When an old OOB skb is replaced and discarded in manage_oob(), or when a
> fully consumed OOB skb is skipped in unix_stream_read_skb(), the
> discarded byte count is not subtracted from u->inq_len (or is subtracted
> inconsistently), causing SIOCINQ / FIONREAD to report stale positive
> byte counts on an empty AF_UNIX stream socket.

[Severity: Medium]
Is the unix_stream_read_skb() half of this description accurate?

unix_skb_len() is:

    static unsigned int unix_skb_len(const struct sk_buff *skb)
    {
    	return skb->len - UNIXCB(skb).consumed;
    }

so a fully consumed skb contributes 0 bytes, and the pre-existing
subtraction already in that function:

    WRITE_ONCE(u->inq_len, u->inq_len - unix_skb_len(skb));

subtracts exactly 0 for it.  The bytes were already removed either by the
partial read in unix_stream_read_generic():

    WRITE_ONCE(u->inq_len, u->inq_len - chunk);

or by unix_stream_recv_urg():

    WRITE_ONCE(u->inq_len, u->inq_len - 1);

Can a fully consumed skb still hold un-accounted bytes in this path?

The new !unix_skb_len(skb) block contains no inq_len update at all, and the
new skb_pull() block is not mentioned in the changelog.  Both change what
the ->read_skb() (BPF sockmap) consumer sees: one stops delivery of a
zero-payload skb, the other stops re-delivering already consumed bytes.
Could those two changes be split out with their own justification, so that
this patch only carries the manage_oob() accounting fix?

> Fixes: 314001f0bf92 ("af_unix: Add OOB support")

[Severity: Medium]
Is this the right Fixes target?  u->inq_len does not exist at that commit;
blame on include/net/af_unix.h attributes

    int			inq_len;

to f4e1fb04c1238 ("af_unix: Use cached value for SOCK_STREAM in
unix_inq_len().").  Before that, unix_inq_len() walked the receive queue:

    skb_queue_walk(&sk->sk_receive_queue, skb)
    	amount += unix_skb_len(skb);

so a discarded OOB skb could not leave a stale count behind.  f4e1fb04c1238
added the inq_len increment in queue_oob() and the decrement in
unix_stream_recv_urg(), but not the manage_oob() discard decrement this
patch adds.  Should the tag point at f4e1fb04c1238 instead, and should the
patch carry Cc: stable@vger.kernel.org given it claims a user-visible
SIOCINQ/FIONREAD fix?

> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 42cffeafc8c1..1770af3c2684 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -2849,6 +2849,7 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sk_buff **last,
>  		WRITE_ONCE(u->oob_skb, NULL);
>  
>  		if (!sock_flag(sk, SOCK_URGINLINE)) {
> +			WRITE_ONCE(u->inq_len, u->inq_len - 1);
>  			__skb_unlink(skb, &sk->sk_receive_queue);
>  			unread_skb = skb;
>  			skb = skb_peek(&sk->sk_receive_queue);
> @@ -2884,6 +2885,7 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
>  		return err;
>  
>  	mutex_lock(&u->iolock);
> +again:
>  	spin_lock(&queue->lock);
>  
>  	skb = __skb_dequeue(queue);
> @@ -2893,6 +2895,12 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
>  		return -EAGAIN;
>  	}
>  
> +	if (!unix_skb_len(skb)) {
> +		spin_unlock(&queue->lock);
> +		consume_skb(skb);
> +		goto again;
> +	}
> +

[Severity: Medium]
Does this new discard path leave u->scm_stat.nr_fds inflated?

The normal exit of unix_stream_read_skb() goes through:

    static void unix_orphan_scm(struct sock *sk, struct sk_buff *skb)
    {
    	scm_stat_del(sk, skb);
    	unix_destruct_scm(skb);
    	skb->destructor = sock_wfree;
    }

and scm_stat_del() is the only place that decrements nr_fds:

    if (unlikely(fp && fp->count)) {
    	atomic_sub(fp->count, &u->scm_stat.nr_fds);
    	unix_del_edges(fp);
    }

The bare consume_skb() here goes through the destructor chain
unix_wfree() -> unix_destruct_scm() -> unix_detach_fds(), which releases the
files but never touches scm_stat.nr_fds.

A reachable input looks like: sendmsg(MSG_OOB) with SCM_RIGHTS, where
queue_oob() attaches the fds and calls scm_stat_add(other, skb); then a
recvmsg(MSG_OOB) without MSG_PEEK in unix_stream_recv_urg() does

    if (!(state->flags & MSG_PEEK))
    	UNIXCB(oob_skb).consumed += 1;

leaving a queued zero-payload skb that still carries UNIXCB(skb).fp.  When
that skb reaches the head of the queue on a sockmap'd socket, the new branch
frees it without scm_stat_del(), so unix_show_fdinfo() keeps reporting an
inflated scm_fds for the life of the socket.  Would calling
unix_orphan_scm(sk, skb) (or at least scm_stat_del()) before freeing keep
the accounting balanced?

>  	WRITE_ONCE(u->inq_len, u->inq_len - unix_skb_len(skb));
>  
>  #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
> @@ -2908,6 +2916,11 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
>  
>  	spin_unlock(&queue->lock);
>  
> +	if (UNIXCB(skb).consumed) {
> +		skb_pull(skb, UNIXCB(skb).consumed);
> +		UNIXCB(skb).consumed = 0;
> +	}
> +

[Severity: Critical]
Can this skb_pull() hit the BUG() in __skb_pull() on a paged skb?

UNIXCB(skb).consumed is a logical stream offset, not bounded by
skb_headlen(skb).  unix_stream_sendmsg() routinely builds non-linear skbs:

    data_len = max_t(int, 0, size - SKB_MAX_HEAD(0));
    ...
    skb = sock_alloc_send_pskb(sk, size - data_len, data_len, ...);

and on the MSG_SPLICE_PAGES branch the head length is 0.

unix_stream_read_generic() then advances consumed and deliberately leaves
the skb queued while any payload remains:

    UNIXCB(skb).consumed += chunk;
    ...
    if (unix_skb_len(skb)) {
    	spin_unlock(&sk->sk_receive_queue.lock);
    	break;
    }

skb_pull() only rejects len > skb->len; __skb_pull() then does:

    skb->len -= len;
    if (unlikely(skb->len < skb->data_len)) {
    	...
    	BUG();
    }

So a peer writing more than SKB_MAX_HEAD(0) bytes (or splicing pages, where
a single consumed byte is enough), a reader consuming a prefix past the
linear head with a normal recvmsg(), and then further data arriving on a
socket with a sockmap verdict program gives
sk_psock_verdict_data_ready() -> ops->read_skb() -> unix_stream_read_skb() ->
skb_pull() -> BUG().

The BUG() fires after spin_unlock(&queue->lock) but before
mutex_unlock(&u->iolock), so the task dies holding u->iolock and further
readers and MSG_OOB users of that socket block forever; the skb is already
dequeued and u->inq_len already decremented.

If already consumed bytes need to be hidden from the recv_actor, would a
paged-safe form such as pskb_pull()/__pskb_pull_tail() with failure handling,
or passing the offset to the actor, be more appropriate here?

>  	unix_orphan_scm(sk, skb);
>  
>  	mutex_unlock(&u->iolock);

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919221735.3708237-1-benquike%40gmail.com

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net v2 1/2] af_unix: decrement u->inq_len when skipping unread OOB skb in manage_oob()
  2026-09-20 19:27 ` Kuniyuki Iwashima
@ 2026-09-21  5:48   ` Hui Peng
  2026-09-21  5:48     ` [PATCH net v2 2/2] af_unix: skip consumed OOB skb and pull consumed bytes in unix_stream_read_skb() Hui Peng
  0 siblings, 1 reply; 5+ messages in thread
From: Hui Peng @ 2026-09-21  5:48 UTC (permalink / raw)
  To: kuniyu, davem, edumazet, kuba, pabeni
  Cc: horms, willemb, mhal, jakub, netdev, linux-kernel, stable, benquike

When a normal in-band read on an AF_UNIX stream socket (without MSG_PEEK
and without SO_OOBINLINE) encounters an unread u->oob_skb in manage_oob(),
manage_oob() clears u->oob_skb, unlinks the 1-byte skb from
sk->sk_receive_queue, and drops it with SKB_DROP_REASON_UNIX_SKIP_OOB
without decrementing u->inq_len. Because queue_oob() incremented
u->inq_len by 1 when queuing the OOB skb, u->inq_len remains permanently
inflated by 1 byte for each skipped unread OOB skb, causing SIOCINQ /
FIONREAD to report a stale positive byte count on an empty socket.

Decrement u->inq_len by 1 when unlinking the unread OOB skb in
manage_oob().

Tested in QEMU against Linux 7.3.0-rc3 by sending three 1-byte MSG_OOB
packets interleaved with normal stream data on an AF_UNIX SOCK_STREAM
socketpair and draining all in-band data via recv(): on the unfixed
kernel, ioctl(SIOCINQ) reports 3 on the empty socket; with this patch
applied, ioctl(SIOCINQ) reports 0.

Fixes: f4e1fb04c123 ("af_unix: Use cached value for SOCK_STREAM in unix_inq_len().")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split the manage_oob() and unix_stream_read_skb() fixes into a 2-patch
  series as requested by Kuniyuki Iwashima.
- Update Fixes: tag to f4e1fb04c123 ("af_unix: Use cached value for
  SOCK_STREAM in unix_inq_len().") and clarify the commit message as
  noted by Sashiko.

 net/unix/af_unix.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 42cffeafc8c1..1770af3c2684 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -2849,6 +2849,7 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sk_buff **last,
 		WRITE_ONCE(u->oob_skb, NULL);
 
 		if (!sock_flag(sk, SOCK_URGINLINE)) {
+			WRITE_ONCE(u->inq_len, u->inq_len - 1);
 			__skb_unlink(skb, &sk->sk_receive_queue);
 			unread_skb = skb;
 			skb = skb_peek(&sk->sk_receive_queue);
-- 
2.49.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net v2 2/2] af_unix: skip consumed OOB skb and pull consumed bytes in unix_stream_read_skb()
  2026-09-21  5:48   ` [PATCH net v2 1/2] af_unix: decrement u->inq_len when skipping unread OOB skb in manage_oob() Hui Peng
@ 2026-09-21  5:48     ` Hui Peng
  0 siblings, 0 replies; 5+ messages in thread
From: Hui Peng @ 2026-09-21  5:48 UTC (permalink / raw)
  To: kuniyu, davem, edumazet, kuba, pabeni
  Cc: horms, willemb, mhal, jakub, netdev, linux-kernel, stable, benquike

When an OOB byte is consumed via recv(MSG_OOB) in unix_stream_recv_urg(),
u->oob_skb is cleared to NULL and UNIXCB(oob_skb).consumed is incremented
to 1, but oob_skb remains on sk->sk_receive_queue (with unix_skb_len(skb)
== 0) to preserve the OOB mark until normal reads advance past it.
Similarly, a partial recv() in unix_stream_read_generic() advances
UNIXCB(skb).consumed without pulling the skb header and leaves the
partially consumed skb at the head of sk->sk_receive_queue.

If the socket is subsequently read via unix_stream_read_skb() (used by
BPF sockmap), unix_stream_read_skb() only checks skb == u->oob_skb
(which is only true for an unconsumed OOB skb) and ignores
UNIXCB(skb).consumed. As a result, a consumed OOB skb (unix_skb_len(skb)
== 0) is handed to recv_actor() and re-delivers the already consumed OOB
byte, and a partially consumed skb re-delivers its already consumed
prefix.

In unix_stream_read_skb(), skip and free zero-length consumed skbs (after
calling unix_orphan_scm() so SCM_RIGHTS fd accounting remains balanced)
and pull UNIXCB(skb).consumed bytes via pskb_pull() (which safely handles
both linear and non-linear paged skbs) before invoking recv_actor().

Tested in QEMU against Linux 7.3.0-rc3 with BPF_MAP_TYPE_SOCKMAP and a
BPF_SK_SKB_STREAM_VERDICT program:
1. Consuming a 1-byte MSG_OOB packet ("Z") before inserting the socket
   into sockmap and sending "HELLO" re-delivers "ZHELLO" on the unfixed
   kernel, whereas with this patch applied recv() receives "HELLO!".
2. Consuming a 3-byte prefix ("123") of "12345678" before inserting the
   socket into sockmap re-delivers "12345678" on the unfixed kernel,
   whereas with this patch applied recv() receives "45678".

Fixes: 77462de14a43 ("af_unix: Add read_sock for stream socket types")
Fixes: 314001f0bf92 ("af_unix: Add OOB support")
Fixes: 638f32604385 ("af_unix: Disable MSG_OOB handling for sockets in sockmap/sockhash")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split out from the manage_oob() fix into patch 2/2 as requested by
  Kuniyuki Iwashima.
- Call unix_orphan_scm(sk, skb) before consume_skb(skb) when dropping a
  zero-length consumed skb so u->scm_stat.nr_fds is decremented, and use
  pskb_pull() instead of skb_pull() to safely handle non-linear paged
  skbs without hitting BUG() in __skb_pull(), as noted by Sashiko.

 net/unix/af_unix.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 1770af3c2684..f5d64e7b5c1c 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -2885,6 +2885,7 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
 		return err;
 
 	mutex_lock(&u->iolock);
+again:
 	spin_lock(&queue->lock);
 
 	skb = __skb_dequeue(queue);
@@ -2894,6 +2895,13 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
 		return -EAGAIN;
 	}
 
+	if (!unix_skb_len(skb)) {
+		spin_unlock(&queue->lock);
+		unix_orphan_scm(sk, skb);
+		consume_skb(skb);
+		goto again;
+	}
+
 	WRITE_ONCE(u->inq_len, u->inq_len - unix_skb_len(skb));
 
 #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
@@ -2913,6 +2921,14 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
 
 	mutex_unlock(&u->iolock);
 
+	if (UNIXCB(skb).consumed) {
+		if (!pskb_pull(skb, UNIXCB(skb).consumed)) {
+			kfree_skb(skb);
+			return -ENOMEM;
+		}
+		UNIXCB(skb).consumed = 0;
+	}
+
 	return recv_actor(sk, skb);
 }
 
-- 
2.49.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-21  5:48 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-19 22:17 [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb() Hui Peng
2026-09-20 19:27 ` Kuniyuki Iwashima
2026-09-21  5:48   ` [PATCH net v2 1/2] af_unix: decrement u->inq_len when skipping unread OOB skb in manage_oob() Hui Peng
2026-09-21  5:48     ` [PATCH net v2 2/2] af_unix: skip consumed OOB skb and pull consumed bytes in unix_stream_read_skb() Hui Peng
2026-09-20 23:19 ` [PATCH] af_unix: fix u->inq_len accounting in manage_oob() and unix_stream_read_skb() netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®