From nobody Thu Apr 2 20:28:03 2026 Received: from out-181.mta1.migadu.com (out-181.mta1.migadu.com [95.215.58.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 26A057478 for ; Thu, 19 Feb 2026 01:43:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771465392; cv=none; b=bgaZfu20Edh/LwDzWd55C+yph8iPjr5k6QJ6dNIlfkG57ZOLVGY10fZ8Mo48NtKs41WEGzHNAo4X9H/Ic9S7wA9VZg1nanuTesJbAmEyIBQ817Oc6VMLcmMOS0oCBXSrByECT/wiVp28+o88psweY/JNClx5e4p6CCx/g2dyfn4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771465392; c=relaxed/simple; bh=AxMPpVAT1o+/GaN6fRCY6Kna2c7HdF+7HKwspa16UeE=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=OocchcmiTIcbK2OTXCQPqC0f+q4GXzHfqC5kL0zwid08txm44R41wr4LQM1ZiM7iUcl5gkRYaD7VK0XUZtv+9mbPGzaQZYIHZd7DR7dUpddvaCR3fN24xNgTFrJUfv2K5nzP1enrx9q1o/cff4glkHE6hBjfXasIdiIvd8gOYCU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=XvUvpwsl; arc=none smtp.client-ip=95.215.58.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="XvUvpwsl" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1771465389; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=E0Cm2HRg+eNwZMTxcDiP0In5Zf3pSqK9RC9M/49yMMo=; b=XvUvpwslHUFNmMJCQrKlfffeEzHWuXo4R54GJM8h6QFLIXCA+YuTauTcWObdz64S+v40Ez uICqmoLBTUf2rOWdgEkAIFlxWmCfx47yNHWb6ssrb7m78gmAqlzWW1ZNMzhxg2JOCJkLPy G4PuBjKRh0KKAIhZiA87NWG75d3tkGA= From: Jiayuan Chen To: netdev@vger.kernel.org, pabeni@redhat.com Cc: jiayuan.chen@linux.dev, Jiayuan Chen , syzbot+52624bdfbf2746d37d70@syzkaller.appspotmail.com, "David S. Miller" , Eric Dumazet , Jakub Kicinski , Simon Horman , Michal Luczaj , Sven Stegemann , Christian Brauner , Tom Herbert , linux-kernel@vger.kernel.org Subject: [PATCH net v2] kcm: fix zero-frag skb in frag_list on partial sendmsg error Date: Thu, 19 Feb 2026 09:42:51 +0800 Message-ID: <20260219014256.370092-1-jiayuan.chen@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" From: Jiayuan Chen Syzkaller reported a warning in kcm_write_msgs() when processing a message with a zero-fragment skb in the frag_list. When kcm_sendmsg() fills MAX_SKB_FRAGS fragments in the current skb, it allocates a new skb (tskb) and links it into the frag_list before copying data. If the copy subsequently fails (e.g. -EFAULT from user memory), tskb remains in the frag_list with zero fragments: head skb (msg being assembled, NOT yet in sk_write_queue) +-----------+ | frags[17] | (MAX_SKB_FRAGS, all filled with data) | frag_list-+--> tskb +-----------+ +----------+ | frags[0] | (empty! copy failed before filling) +----------+ For SOCK_SEQPACKET with partial data already copied, the error path saves this message via partial_message for later completion. For SOCK_SEQPACKET, sock_write_iter() automatically sets MSG_EOR, so a subsequent zero-length write(fd, NULL, 0) completes the message and queues it to sk_write_queue. kcm_write_msgs() then walks the frag_list and hits: WARN_ON(!skb_shinfo(skb)->nr_frags) TCP has a similar pattern where skbs are enqueued before data copy and cleaned up on failure via tcp_remove_empty_skb(). KCM was missing the equivalent cleanup. Fix this by tracking the predecessor skb (frag_prev) when allocating a new frag_list entry. On error, if the tail skb has zero frags, use frag_prev to unlink and free it in O(1) without walking the singly-linked frag_list. frag_prev is safe to dereference because the entire message chain is only held locally (or in kcm->seq_skb) and is not added to sk_write_queue until MSG_EOR, so the send path cannot free it underneath us. Also change the WARN_ON to WARN_ON_ONCE to avoid flooding the log if the condition is somehow hit repeatedly. There are currently no KCM selftests in the kernel tree; a simple reproducer is available at [1]. [1] https://gist.github.com/mrpre/a94d431c757e8d6f168f4dd1a3749daa Reported-by: syzbot+52624bdfbf2746d37d70@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/000000000000269a1405a12fdc77@google.com= /T/ Fixes: ab7ac4eb9832 ("kcm: Kernel Connection Multiplexor module") Signed-off-by: Jiayuan Chen --- net/kcm/kcmsock.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/net/kcm/kcmsock.c b/net/kcm/kcmsock.c index 5dd7e0509a48..3912e75079f5 100644 --- a/net/kcm/kcmsock.c +++ b/net/kcm/kcmsock.c @@ -628,7 +628,7 @@ static int kcm_write_msgs(struct kcm_sock *kcm) skb =3D txm->frag_skb; } =20 - if (WARN_ON(!skb_shinfo(skb)->nr_frags) || + if (WARN_ON_ONCE(!skb_shinfo(skb)->nr_frags) || WARN_ON_ONCE(!skb_frag_page(&skb_shinfo(skb)->frags[0]))) { ret =3D -EINVAL; goto out; @@ -749,7 +749,7 @@ static int kcm_sendmsg(struct socket *sock, struct msgh= dr *msg, size_t len) { struct sock *sk =3D sock->sk; struct kcm_sock *kcm =3D kcm_sk(sk); - struct sk_buff *skb =3D NULL, *head =3D NULL; + struct sk_buff *skb =3D NULL, *head =3D NULL, *frag_prev =3D NULL; size_t copy, copied =3D 0; long timeo =3D sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT); int eor =3D (sock->type =3D=3D SOCK_DGRAM) ? @@ -824,6 +824,7 @@ static int kcm_sendmsg(struct socket *sock, struct msgh= dr *msg, size_t len) else skb->next =3D tskb; =20 + frag_prev =3D skb; skb =3D tskb; skb->ip_summed =3D CHECKSUM_UNNECESSARY; continue; @@ -933,6 +934,22 @@ static int kcm_sendmsg(struct socket *sock, struct msg= hdr *msg, size_t len) out_error: kcm_push(kcm); =20 + /* When MAX_SKB_FRAGS was reached, a new skb was allocated and + * linked into the frag_list before data copy. If the copy + * subsequently failed, this skb has zero frags. Remove it from + * the frag_list to prevent kcm_write_msgs from later hitting + * WARN_ON(!skb_shinfo(skb)->nr_frags). + */ + if (frag_prev && !skb_shinfo(skb)->nr_frags) { + if (head =3D=3D frag_prev) + skb_shinfo(head)->frag_list =3D NULL; + else + frag_prev->next =3D NULL; + kfree_skb(skb); + /* Update skb as it may be saved in partial_message via goto */ + skb =3D frag_prev; + } + if (sock->type =3D=3D SOCK_SEQPACKET) { /* Wrote some bytes before encountering an * error, return partial success. --=20 2.43.0