[PATCH] kcm: fix socket memory accounting leak on TX completion, error, and close

From: Hui Peng

Date: Sat Sep 19 2026 - 17:29:34 EST


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 <benquike@xxxxxxxxx>

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