From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from out-182.mta1.migadu.com (out-182.mta1.migadu.com [95.215.58.182]) (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 141C7325727 for ; Sun, 19 Apr 2026 09:08:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776589722; cv=none; b=sQ2s0rkIHJP70nV22TXxl8Py+oK9/DYbB4U9c/frSv2xV2ylwZXVMsRdsW/ANDeC6pttVgFHHz6Ty2bSztWp9vYTJiyXA30yJ6Vtdd1W7odRXOsRdKcmIMUly0JSQpRbffVtv8vQ/mGGw53KV5+Gty+8wTzsLEhJ1ye4Nuns77A= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776589722; c=relaxed/simple; bh=aY09xrU18fMJgxyjA44/PqjhbGZzoFeYOaYOxOgpNAQ=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=gOA5NG82DAeH1IBXTcYvBMVPp8ZgclcYLcrattWXOHBcwK5gGNma+fW4sqQ4kCyYs/45TnexngLTKBdZrgbCvVEZQwXcY6tb9jBttf80ZZCYMikExD3Qx+IAWC5kikhTHgHF88A1Kd1ahcmZfMfYWDiLwXDFBQJgbbk9f+XchNI= 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=t81rIP6b; arc=none smtp.client-ip=95.215.58.182 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="t81rIP6b" 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=1776589717; 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=MIa8/P0nVLx8HcPykNLu4zyr5QA2THIZIUzWDxhbvQ0=; b=t81rIP6bV+cWb5itq4qjDDlbwaYmWY0TP106eF5Qr5f8OhEpdbBOeYXRLOsApfaKz5zQoz 7cOEXNyV0olm02Tg+q4ZdjIHtNAK1XT4ywDmjc9aihk+cP6gAgRD0noTTnSj0tIRJgTaQK OyQzhop5scWghmXZ2m6hENL/P6XEWmU= From: Jiayuan Chen To: netdev@vger.kernel.org Cc: Jiayuan Chen , syzbot+83181a31faf9455499c5@syzkaller.appspotmail.com, "David S. Miller" , David Ahern , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Pravin B Shelar , Tom Herbert , linux-kernel@vger.kernel.org Subject: [PATCH net v2] net: iptunnel: fix stale transport header after GRE/TEB decap Date: Sun, 19 Apr 2026 17:08:15 +0800 Message-ID: <20260419090817.127334-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: 8bit X-Migadu-Flow: FLOW_OUT syzbot reported a BUG. I found that after GRE decapsulation in gretap/ip6gretap paths, the transport_header becomes stale with a negative offset. The sequence is: 1. Before decap, transport_header points to the outer L4 (GRE) header. 2. __iptunnel_pull_header() calls skb_pull_rcsum() to advance skb->data past the GRE header, but does not update transport_header. 3. For TEB (gretap/ip6gretap), eth_type_trans() in ip_tunnel_rcv() / __ip6_tnl_rcv() further pulls ETH_HLEN (14 bytes) from skb->data. After these two pulls, skb->data has moved forward while transport_header still points to the old (now behind skb->data) position, resulting in a negative skb_transport_offset(): typically -4 after GRE pull alone, or -18 after GRE + inner Ethernet pull. In the normal case where the inner frame is a recognizable protocol (e.g., IPv4/TCP), the transport_header is subsequently overwritten by ip_rcv_core() (or inet_gro_receive() on the GRO path) via skb_set_transport_header(), and the stale value never reaches downstream consumers. However, if the inner frame cannot be parsed (e.g., eth_type_trans() classifies it as ETH_P_802_2 due to a zero/invalid inner Ethernet header), neither rescue runs, and the stale offset persists into __netif_receive_skb_core(). When this stale offset is combined with contradictory GSO metadata (e.g., SKB_GSO_TCPV4 injected via virtio_net_hdr from a tun device), qdisc_pkt_len_segs_init() trusts the negative offset: the unsigned wraparound makes pskb_may_pull() effectively a no-op, and __tcp_hdrlen() then reads from an invalid memory location, causing a use-after-free. The UAF only triggers on the GSO path, where qdisc_pkt_len_segs_init() dereferences the transport header to compute per-segment length. Fix this by introducing iptunnel_rebuild_transport_header(), which is a no-op for non-GSO packets and otherwise re-probes the transport header via the flow dissector. If re-probing fails, the contradictory GSO metadata is cleared via skb_gso_reset() so downstream consumers cannot trust stale offsets. Restricting the rebuild to GSO packets keeps the flow-dissector cost off the common rx fast path. reproducer: https://gist.github.com/mrpre/5ba943fd86367af748b70de99263da4b Link: https://syzkaller.appspot.com/bug?extid=83181a31faf9455499c5 Fixes: c54419321455 ("GRE: Refactor GRE tunneling code.") Fixes: 0d3c703a9d17 ("ipv6: Cleanup IPv6 tunnel receive path") Reported-by: syzbot+83181a31faf9455499c5@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/69de2bee.a00a0220.475f0.0041.GAE@google.com/T/ Signed-off-by: Jiayuan Chen --- As a follow-up for production reliability, I am wondering whether we can extend the existing safety net in __netif_receive_skb_core() to also handle set-but-negative transport_header: if (!skb_transport_header_was_set(skb) || skb_transport_offset(skb) < 0) skb_reset_transport_header(skb); --- include/net/ip_tunnels.h | 12 ++++++++++++ net/ipv4/ip_tunnel.c | 2 ++ net/ipv6/ip6_tunnel.c | 2 ++ 3 files changed, 16 insertions(+) diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index d708b66e55cd..9b4e662833a1 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -662,6 +662,18 @@ static inline int iptunnel_pull_offloads(struct sk_buff *skb) return 0; } +static inline void iptunnel_rebuild_transport_header(struct sk_buff *skb) +{ + if (!skb_is_gso(skb)) + return; + + skb->transport_header = (typeof(skb->transport_header))~0U; + skb_probe_transport_header(skb); + + if (!skb_transport_header_was_set(skb)) + skb_gso_reset(skb); +} + static inline void iptunnel_xmit_stats(struct net_device *dev, int pkt_len) { if (pkt_len > 0) { diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 50d0f5fe4e4c..c46be68cfafa 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -445,6 +445,8 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb, if (tun_dst) skb_dst_set(skb, (struct dst_entry *)tun_dst); + iptunnel_rebuild_transport_header(skb); + gro_cells_receive(&tunnel->gro_cells, skb); return 0; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 46bc06506470..f95348cf3c77 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -879,6 +879,8 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb, if (tun_dst) skb_dst_set(skb, (struct dst_entry *)tun_dst); + iptunnel_rebuild_transport_header(skb); + gro_cells_receive(&tunnel->gro_cells, skb); return 0; -- 2.43.0