[PATCH] kcm: fix socket memory accounting leak on TX completion, error, and close
From: Hui Peng <hidden>
Date: 2026-09-19 21:28:55
Also in:
lkml
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
In `kcm_sendmsg()`, every TX fragment is charged to both
`sk->sk_wmem_queued` and `sk->sk_forward_alloc` (`sk_mem_charge()`) via
`skb_copy_to_page_nocache()` or `MSG_SPLICE_PAGES`. However, the
assembled `head` skb is queued onto `&sk->sk_write_queue` (or held in
`kcm->seq_skb`) without `head->sk` or a destructor set.
Consequently:
1. When `kcm_write_msgs()` finishes transmitting `head`, it subtracts
`txm->sent` from `sk->sk_wmem_queued` but never calls
`sk_mem_uncharge(sk, txm->sent)`.
2. When `kcm_sendmsg()` fails under `out_error`, or when `kcm_release()`
frees `kcm->seq_skb` and purges `&sk->sk_write_queue`, `kfree_skb()`
is called without decrementing `sk->sk_wmem_queued` or calling
`sk_mem_uncharge()`, triggering a `WARN_ON` in `inet_sock_destruct()`
and `__sk_destruct()` when the KCM socket is closed.
Use `sk_wmem_queued_add(sk, -...)` and `sk_mem_uncharge(sk, ...)`
whenever freeing TX skbs in `kcm_write_msgs()`, `kcm_sendmsg()`, and
`kcm_release()`.
Fixes: ab7ac4eb9832 ("kcm: Kernel Connection Multiplexor module")
Assisted-by: LLM
Signed-off-by: Hui Peng <redacted>
---
net/kcm/kcmsock.c | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/net/kcm/kcmsock.c b/net/kcm/kcmsock.c
index 71af69d442f2..accf0d427e11 100644
--- a/net/kcm/kcmsock.c
+++ b/net/kcm/kcmsock.c@@ -693,7 +693,8 @@ static int kcm_write_msgs(struct kcm_sock *kcm) } /* Successfully sent the whole packet, account for it. */ - sk->sk_wmem_queued -= txm->sent; + sk_wmem_queued_add(sk, -txm->sent); + sk_mem_uncharge(sk, txm->sent); total_sent += txm->sent; skb_dequeue(&sk->sk_write_queue); kfree_skb(head);
@@ -960,10 +961,17 @@ static int kcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) */ if (copied) goto partial_message; - if (head != kcm->seq_skb) + if (head && head != kcm->seq_skb) { + sk_wmem_queued_add(sk, -head->len); + sk_mem_uncharge(sk, head->len); kfree_skb(head); + } } else { - kfree_skb(head); + if (head) { + sk_wmem_queued_add(sk, -head->len); + sk_mem_uncharge(sk, head->len); + kfree_skb(head); + } kcm->seq_skb = NULL; }
@@ -1686,6 +1694,7 @@ static int kcm_release(struct socket *sock) struct kcm_sock *kcm; struct kcm_mux *mux; struct kcm_psock *psock; + struct sk_buff *skb; if (!sk) return 0;
@@ -1695,13 +1704,21 @@ static int kcm_release(struct socket *sock) lock_sock(sk); sock_orphan(sk); - kfree_skb(kcm->seq_skb); + if (kcm->seq_skb) { + sk_wmem_queued_add(sk, -kcm->seq_skb->len); + sk_mem_uncharge(sk, kcm->seq_skb->len); + kfree_skb(kcm->seq_skb); + } /* Purge queue under lock to avoid race condition with tx_work trying * to act when queue is nonempty. If tx_work runs after this point * it will just return. */ - __skb_queue_purge(&sk->sk_write_queue); + while ((skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) { + sk_wmem_queued_add(sk, -skb->len); + sk_mem_uncharge(sk, skb->len); + kfree_skb(skb); + } release_sock(sk);
--
2.55.0.1082.g2b9226bbc0-goog