From nobody Thu Apr 2 20:28:03 2026 Received: from out-178.mta1.migadu.com (out-178.mta1.migadu.com [95.215.58.178]) (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 CC94621D3F3 for ; Fri, 13 Feb 2026 06:12:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770963181; cv=none; b=Xjr4MeuZn1rs7yM6rUSR8VJgCFRYgJL9kNSaU6O+FfxZDoBPpVERb2TLseTh2dG5RvShMkUFIsrOCqWQKMiajhQxoYZJOSXptoIaMxmzf7r7VOBjqwX6+Ba7rSlUsqauOG7lMPz/+luY5XvAeJ+Z+SH4ERCowhqU5WRY//bm+Oo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770963181; c=relaxed/simple; bh=+GNwk9d83EcH3k9xakIWHe70Qxl/YthFBrXIr2n6h54=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=UlqHzQ0eZDw9f9Tp7/poSsxRF9qWqxMd+2Hw2uswOnK9LO+0FGGW993BAlcUCSguItSnkv1kwOFbWTmT6vOUZtl3o7DfLrJrMPo0KQ1h32cE9pDFmhO8Xy91hj7TQH6b/0fVXgxijMg65z13aRRj5VAYF/ah66H+pB8cEew21sk= 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=AOmwVZ1v; arc=none smtp.client-ip=95.215.58.178 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="AOmwVZ1v" 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=1770963167; 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=Rb89fMQcEbzS8oWtkA6rIAcqA5917DTp8BIsCstpH0s=; b=AOmwVZ1vKrzQtqUdSqax1OdH2oyXpCyxBb2GvkL270VGd707k3UE3MXYeG0JqQsRX7VsC8 4SUGVODQsw6h6g9JIlUu5OFlu4Vdwvsr6AuwdzJRUA9tBJ8WV618uFAVpp/UDnzmnLR8i1 9o4HZIHRP+l9vzupghsHlslIPkmHvjw= From: Jiayuan Chen To: netdev@vger.kernel.org Cc: jiayuan.chen@linux.dev, Jiayuan Chen , syzbot+52624bdfbf2746d37d70@syzkaller.appspotmail.com, "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Michal Luczaj , Sven Stegemann , Christian Brauner , Tom Herbert , linux-kernel@vger.kernel.org Subject: [PATCH net v1] kcm: fix zero-frag skb in frag_list on partial sendmsg error Date: Fri, 13 Feb 2026 14:12:27 +0800 Message-ID: <20260213061232.338244-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. A subsequent zero-length write(fd, NULL, 0) implies MSG_EOR, which queues the message 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