[PATCH net 2/4] af_unix: Fix scm_stat.nr_fds leaks for OOB skb.
From: Siddharth Chintamaneni
Date: Fri Nov 06 2026 - 00:20:07 EST
When queue_oob() enqueues an out-of-band skb carrying SCM_RIGHTS file
descriptors, it accounts the inflight descriptors in u->scm_stat.nr_fds
via scm_stat_add().
However, the paths that later free or skip an OOB skb do not call
scm_stat_del():
1. In manage_oob(), when !sock_flag(sk, SOCK_URGINLINE) and an unconsumed
u->oob_skb is unlinked as unread_skb and dropped via
kfree_skb_reason().
2. In manage_oob(), when a consumed OOB skb (read_skb) is unlinked and
freed via consume_skb().
3. In unix_stream_recv_urg(), when a consecutive MSG_OOB read unlinks and
frees a previously consumed OOB skb (read_skb).
4. In unix_stream_read_skb() (SOCKMAP), when skb == u->oob_skb, the skb
is unlinked and dropped via kfree_skb_reason() before reaching
unix_orphan_scm().
Because unix_destruct_scm() has no reference to the receiving socket when
passed descriptors are not AF_UNIX sockets, u->scm_stat.nr_fds (reported
as scm_fds in /proc/<pid>/fdinfo/<fd>) remains permanently elevated.
Call scm_stat_del() before freeing read_skb or unread_skb in
unix_stream_recv_urg(), manage_oob(), and unix_stream_read_skb().
Fixes: 2aab4b969002 ("af_unix: fix struct pid leaks in OOB support")
Fixes: 32ca245464e1 ("af_unix: Don't leave consecutive consumed OOB skbs.")
Fixes: 965dc93481d1 ("af_unix: Drop all SCM attributes for SOCKMAP.")
Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@xxxxxxxxx>
---
net/unix/af_unix.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index bfaab7c6d720..44c0a81dd423 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -2807,7 +2807,10 @@ static int unix_stream_recv_urg(struct unix_stream_read_state *state)
mutex_unlock(&u->iolock);
- consume_skb(read_skb);
+ if (read_skb) {
+ scm_stat_del(sk, read_skb);
+ consume_skb(read_skb);
+ }
if (chunk < 0)
return -EFAULT;
@@ -2867,8 +2870,14 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sk_buff **last,
unlock:
spin_unlock(&sk->sk_receive_queue.lock);
- consume_skb(read_skb);
- kfree_skb_reason(unread_skb, SKB_DROP_REASON_UNIX_SKIP_OOB);
+ if (read_skb) {
+ scm_stat_del(sk, read_skb);
+ consume_skb(read_skb);
+ }
+ if (unread_skb) {
+ scm_stat_del(sk, unread_skb);
+ kfree_skb_reason(unread_skb, SKB_DROP_REASON_UNIX_SKIP_OOB);
+ }
return skb;
}
@@ -2906,6 +2915,7 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
spin_unlock(&queue->lock);
mutex_unlock(&u->iolock);
+ scm_stat_del(sk, skb);
kfree_skb_reason(skb, SKB_DROP_REASON_UNIX_SKIP_OOB);
return -EAGAIN;
}
--
2.43.0