From: Eric Dumazet <edumazet@kernel.org>
To: "David S . Miller" <davem@davemloft.net>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Kuniyuki Iwashima <kuniyu@google.com>,
Willem de Bruijn <willemb@google.com>
Cc: Simon Horman <horms@kernel.org>,
netdev@vger.kernel.org, Eric Dumazet <edumazet@kernel.org>
Subject: [PATCH net] udp: do not use a cross-family sk_rx_dst in early demux
Date: Fri, 6 Nov 2026 12:01:35 +0100 [thread overview]
Message-ID: <20261106110135.2161204-1-edumazet@kernel.org> (raw)
A dual-stack UDP socket connected to a v4-mapped address can receive
both IPv4 packets and IPv6 packets using v4-mapped addresses:
ip6_rcv_core() no longer rejects a v4-mapped source since commit
dcc32f4f183a ("ipv6: weaken the v4mapped source check"), which kept
that check only in tcp_v6_conn_request(). ipv6_portaddr_hash() hashes
v4-mapped addresses like IPv4 ones, so both early demux handlers find
the socket, and both __udp4_lib_rcv() and __udp6_lib_rcv() store their
input dst in the same sk->sk_rx_dst.
udp_v6_early_demux() validates the cached dst with dst_check(), but
ipv4_dst_check() ignores the cookie, so an IPv4 dst gets attached to
an IPv6 skb. ip6_rcv_finish_core() then skips the route lookup and
ip_local_deliver() parses the IPv6 header as an IPv4 one: the IPv6
next header and hop limit are read as a fragment offset, and the IPv4
protocol is taken from a source address byte.
The reverse direction looks possible too: per-cpu dsts of IPv6 routes
using nexthop objects have rt->sernum set, and ip6_dst_check() then
ignores the cookie, so udp_v4_early_demux() can attach an IPv6 dst to
an IPv4 skb.
Kyle Zeng reported that such a packet can reach XFRM input still
holding the prefetched, non refcounted, socket set by early demux,
which can then outlive its RCU grace period.
Only use the cached dst if its family matches the packet. Otherwise
fall back to a regular route lookup.
TCP does not have the same problem: sk_ehashfn() hashes a v4-mapped
socket with inet_ehashfn(), while IPv6 lookups use inet6_ehashfn(),
so IPv6 packets do not normally find a v4-mapped TCP socket.
Fixes: 5425077d73e0 ("net: ipv6: Add early demux handler for UDP unicast")
Fixes: c9f2c1ae123a ("udp6: fix socket leak on early demux")
Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
This was found while reviewing an AI-generated XFRM patch from Kyle
Zeng, which did not state how the issue was discovered. The
cross-family dst was then identified by manual code inspection.
The IPv4 dst on IPv6 skb case was reproduced on an unmodified kernel,
without error injection, using two netns joined by a veth pair:
- target: AF_INET6 UDP socket bound to ::ffff:10.0.0.1 port 5000,
connected to ::ffff:10.0.0.2 port 6000.
- peer: send a few IPv4 UDP datagrams 10.0.0.2:6000 -> 10.0.0.1:5000.
- peer: inject with AF_PACKET IPv6/UDP packets from ::ffff:10.0.0.2
port 6000 to ::ffff:10.0.0.1 port 5000.
Before this patch, IpReasmReqds in the target netns increases once per
injected IPv6 packet. After it, these packets are dropped by IPv6
routing (Ip6InNoRoutes), and the socket still receives the IPv4
datagrams. The IPv6 dst on IPv4 skb case was not reproduced.
reuseport_dualstack, udpgso.sh, udpgro.sh and udpgro_fwd.sh selftests
pass with this patch.
net/ipv4/udp.c | 5 ++++-
net/ipv6/udp.c | 5 ++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 303d0102519ac1aeac0b04d53e70f846c3a43779..2e5562f353f85d880a15793750d01037303f376e 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -2850,8 +2850,11 @@ enum skb_drop_reason udp_v4_early_demux(struct sk_buff *skb)
skb->destructor = sock_pfree;
dst = rcu_dereference(sk->sk_rx_dst);
- if (dst)
+ /* A dual-stack socket can cache an IPv6 dst. */
+ if (dst && dst->ops->family == AF_INET)
dst = dst_check(dst, 0);
+ else
+ dst = NULL;
if (dst) {
u32 itag = 0;
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 59f9872c355211704ba0899b1953d97d4ffb4617..9dcefa162522352465d25c234e70b8adedea7cd7 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -1249,8 +1249,11 @@ void udp_v6_early_demux(struct sk_buff *skb)
skb->destructor = sock_pfree;
dst = rcu_dereference(sk->sk_rx_dst);
- if (dst)
+ /* A dual-stack socket can cache an IPv4 dst. */
+ if (dst && dst->ops->family == AF_INET6)
dst = dst_check(dst, sk->sk_rx_dst_cookie);
+ else
+ dst = NULL;
if (dst) {
/* set noref for now.
* any place which wants to hold dst has to call
--
2.53.0
next reply other threads:[~2026-11-06 11:01 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 11:01 Eric Dumazet [this message]
2026-11-07 19:52 ` [PATCH net] udp: do not use a cross-family sk_rx_dst in early demux Kuniyuki Iwashima
2026-11-09 23:02 ` netdev-bot+sashiko
2026-11-10 7:13 ` Eric Dumazet
2026-11-11 0:50 ` patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106110135.2161204-1-edumazet@kernel.org \
--to=edumazet@kernel.org \
--cc=davem@davemloft.net \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=willemb@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox