* [PATCH net v3] udp: revalidate socket family before publishing an IPv6 cork
@ 2026-09-02 1:04 Daehyeon Ko
2026-09-03 13:05 ` [net,v3] " netdev-bot+sashiko
0 siblings, 1 reply; 4+ messages in thread
From: Daehyeon Ko @ 2026-09-02 1:04 UTC (permalink / raw)
To: netdev
Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Willem de Bruijn,
Vladislav Yasevich, linux-kernel
udpv6_sendmsg() prepares IPv6 flow and route state before taking the socket
lock when a datagram is corked. IPV6_ADDRFORM takes the same lock, but it
can convert the socket to AF_INET during that lockless preparation because
no cork has been published yet.
If the conversion wins, udpv6_sendmsg() can publish an AF_INET6 cork on an
AF_INET socket. Uncorking through the IPv4 operations then writes a
20-byte IPv4 header into the 40-byte IPv6 reservation while the retained
IPv6 dst routes the skb through ip6_output(). ip6_finish_output2()
consequently consumes the unwritten 20-byte tail.
An unprivileged reproducer triggered the mixed state on 12 of 10,000
sockets. KMSAN reported an uninitialized-value read in
ip6_finish_output2() on three fresh boots, with the allocation origin in
__alloc_skb() through __ip6_append_data(). The same process recovered the
20-byte region from the TX timestamp error queue on all three boots; one
contained recognizable nonzero stale heap data.
There is also socket state to handle before cork publication. A sendto()
with an AF_UNSPEC address clears its explicit destination and later selects
the stored peer with connected set. On an ADDRFORM-eligible socket with a
mapped peer and a suitable IPv6 route, ip6_sk_dst_lookup_flow() can
therefore publish an IPv6 dst in the socket cache. A native connected send
racing a new mapped connect can reach the same store. Revalidating only
after lock_sock() prevents the mixed cork, but does not cover the non-cork
path or undo a dst stored before conversion.
Close both orderings. After a connected IPv6 lookup, reject a socket which
has already been converted and reset any dst the lookup may have stored.
When UDP ADDRFORM publishes the AF_INET family, reset the socket dst after
the family write. Thus, if the send stores and checks first, the later
conversion clears the cache; if conversion and its reset happen first, the
send observes AF_INET after its store and clears it. Keep the locked
family check before cork publication to prevent the original mixed cork.
With a mapped loopback route supplied before dropping to uid 65534 with no
capabilities, an unpatched KASAN control reached ip6_sk_dst_store_flow()
from the AF_UNSPEC send. On patched current net, the sequential conversion
released that dst from do_ipv6_setsockopt(). Corked and non-corked races
both reached the dst store, then returned EAFNOSUPPORT when conversion won;
the converted socket's follow-up send used only udp_sendmsg(). No KASAN,
Oops, warning, or panic followed. The concurrent mapped-connect variant
was validated from the same source ordering, but was not forced in a
dedicated runtime interleaving.
Fixes: 03485f2adcde ("udpv6: Add lockless sendmsg() support")
Closes: https://lore.kernel.org/netdev/20260825160630.1888866-1-4ncienth@gmail.com/
Link: https://lore.kernel.org/netdev/a86aa34a-8bea-46d2-a785-6c423ac00b1d@redhat.com/
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Daehyeon Ko <4ncienth@gmail.com>
---
Changes in v3:
- Address Paolo and Sashiko's finding that AF_UNSPEC and a concurrent mapped
connect can publish an IPv6 socket dst before the family revalidation.
- Revalidate after a connected route lookup and reset a dst stored after an
already completed conversion.
- Reset the UDP socket dst after ADDRFORM publishes AF_INET, covering the
opposite store-before-convert ordering and the non-cork path.
- Retain the post-lock family check which prevents late AF_INET6 cork
publication.
- Add route-enabled, zero-capability KASAN matrix and cork/non-cork race
validation. The separate privileged sockmap mismatch remains out of scope.
- State that the concurrent mapped-connect variant was source-validated but
was not forced in a dedicated runtime interleaving.
v2: https://lore.kernel.org/netdev/20260829132125.1160893-1-4ncienth@gmail.com/
v1: https://lore.kernel.org/netdev/20260825160630.1888866-1-4ncienth@gmail.com/
Testing notes:
- Build and runtime validation used x86_64. Sparse was unavailable;
allmodconfig and multi-architecture builds were not run.
- The tested reproducer is available privately on request and is not included
in this public AI-assisted security-fix submission.
---
net/ipv6/ipv6_sockglue.c | 1 +
net/ipv6/udp.c | 11 +++++++++++
2 files changed, 12 insertions(+)
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index b4c977434c2e..59ca446d3156 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -611,6 +611,7 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
WRITE_ONCE(sk->sk_prot, &udp_prot);
WRITE_ONCE(sk->sk_socket->ops, &inet_dgram_ops);
WRITE_ONCE(sk->sk_family, PF_INET);
+ sk_dst_reset(sk);
}
/* Disable all options not to allocate memory anymore,
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 93478d1ad576..8ba552f1a6b2 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -1705,6 +1705,12 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
dst = NULL;
goto out;
}
+ if (unlikely(connected &&
+ READ_ONCE(sk->sk_family) != AF_INET6)) {
+ sk_dst_reset(sk);
+ err = -EAFNOSUPPORT;
+ goto out;
+ }
if (ipc6.hlimit < 0)
ipc6.hlimit = ip6_sk_dst_hoplimit(np, fl6, dst);
@@ -1729,6 +1735,11 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
}
lock_sock(sk);
+ if (unlikely(sk->sk_family != AF_INET6)) {
+ release_sock(sk);
+ err = -EAFNOSUPPORT;
+ goto out;
+ }
if (unlikely(up->pending)) {
/* The socket is already corked while preparing it. */
/* ... which is an evident application bug. --ANK */
base-commit: 70f3995830d3f1e79faa14eb0605914f778feca9
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [net,v3] udp: revalidate socket family before publishing an IPv6 cork 2026-09-02 1:04 [PATCH net v3] udp: revalidate socket family before publishing an IPv6 cork Daehyeon Ko @ 2026-09-03 13:05 ` netdev-bot+sashiko 2026-09-03 13:22 ` Paolo Abeni 0 siblings, 1 reply; 4+ messages in thread From: netdev-bot+sashiko @ 2026-09-03 13:05 UTC (permalink / raw) To: 4ncienth Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms, willemdebruijn.kernel, vyasevic, linux-kernel Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: Critical: 0 · High: 2 · Medium: 0 · Low: 0 - [High] Incomplete fix: the sibling IPv6 socket-dst publisher in the connect() path is not covered, so an IPv6 dst can still be installed… - [High] The fix is writer-side and after-the-fact, so the mixed-family socket dst it claims to eliminate remains observable by the unguarded… -- udp: revalidate socket family before publishing an IPv6 cork This adds a family recheck after the connected IPv6 route lookup in udpv6_sendmsg(), another one after lock_sock() before the cork is published, and a sk_dst_reset() in the UDP IPV6_ADDRFORM path once PF_INET has been written. The goal is to stop an AF_INET6 cork or an IPv6 socket dst from surviving on a socket converted to AF_INET. Closes: https://lore.kernel.org/netdev/20260825160630.1888866-1-4ncienth@gmail.com/ Link: https://lore.kernel.org/netdev/a86aa34a-8bea-46d2-a785-6c423ac00b1d@redhat.com/ A couple of questions about the two orderings the changelog says are closed. > diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c > index b4c977434c2e0..59ca446d3156b 100644 > --- a/net/ipv6/ipv6_sockglue.c > +++ b/net/ipv6/ipv6_sockglue.c > @@ -611,6 +611,7 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname, > WRITE_ONCE(sk->sk_prot, &udp_prot); > WRITE_ONCE(sk->sk_socket->ops, &inet_dgram_ops); > WRITE_ONCE(sk->sk_family, PF_INET); > + sk_dst_reset(sk); > } [Severity: High] Can an IPv6 socket dst still be published after this reset by the connect() path, which has no family revalidation? inet_dgram_connect() in net/ipv4/af_inet.c resolves the protocol before it can block, and says so: /* IPV6_ADDRFORM can change sk->sk_prot under us. */ prot = READ_ONCE(sk->sk_prot); ... return prot->connect(sk, uaddr, addr_len); So a connect() that already dispatched to udpv6_connect() sits in lock_sock() while do_ipv6_setsockopt() performs the conversion above and drops the lock. __ip6_datagram_connect() in net/ipv6/datagram.c then resumes and checks only the address family supplied by userspace and ipv6_only_sock(); it never re-reads sk->sk_family after the lock was taken: if (usin->sin6_family != AF_INET6) return -EAFNOSUPPORT; ... err = ip6_datagram_dst_update(sk, true); and ip6_datagram_dst_update() ends with: ip6_sk_dst_store_flow(sk, dst, fl6); That installs an rt6_info into sk->sk_dst_cache on a socket that is already AF_INET/udp_prot, i.e. after the new sk_dst_reset() has run. The IPv4 consumer then takes it without any family test, in udp_sendmsg(): if (connected) rt = dst_rtable(sk_dst_check(sk, 0)); Note the cookie 0 does not reject it for every route type, since ip6_dst_check() short-circuits: if (rt->sernum) return rt6_is_valid(rt) ? dst : NULL; and rt_genid_ipv6() is seeded from fib6_sernum, which starts at 1, so pcpu routes of nexthop-object fib entries always carry a nonzero sernum. Given that, is the claim in the changelog accurate? "Thus, if the send stores and checks first, the later conversion clears the cache; if conversion and its reset happen first, the send observes AF_INET after its store and clears it." The reset here only orders against stores made by code that revalidates the family; would a family recheck in __ip6_datagram_connect() after lock_sock(), or a dst->ops->family check on the IPv4 consumer side, be needed as well? > diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c > index 93478d1ad5769..8ba552f1a6b26 100644 > --- a/net/ipv6/udp.c > +++ b/net/ipv6/udp.c > @@ -1705,6 +1705,12 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) > dst = NULL; > goto out; > } > + if (unlikely(connected && > + READ_ONCE(sk->sk_family) != AF_INET6)) { > + sk_dst_reset(sk); > + err = -EAFNOSUPPORT; > + goto out; > + } [Severity: High] Is the mixed-family socket dst observable to a third thread in the window between the store and this check? The store happens inside the lookup just above, in ip6_sk_dst_lookup_flow(): dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_dst); if (connected && !IS_ERR(dst)) ip6_sk_dst_store_flow(sk, dst_clone(dst), fl6); so in the "conversion wins first" ordering the sequence is: CPU1 (setsockopt IPV6_ADDRFORM) WRITE_ONCE(sk->sk_family, PF_INET); sk_dst_reset(sk); CPU0 (udpv6_sendmsg, connected == true) ip6_sk_dst_store_flow(sk, ...); /* rt6_info now cached */ <preemptible window, no socket lock held> READ_ONCE(sk->sk_family) != AF_INET6 -> sk_dst_reset(sk); CPU2 (udp_sendmsg on the converted socket) rt = dst_rtable(sk_dst_check(sk, 0)); The IPv4 side has no family validation, unlike its IPv6 counterpart ip6_sk_dst_check() which rejects a foreign entry outright: if (dst->ops->family != AF_INET6) { dst_release(dst); return NULL; } Does that leave an rt6_info being used as a struct rtable, with an IPv4-formatted skb reaching dst->output == ip6_output()? The symmetric ordering also looks reachable: in do_ipv6_setsockopt() the udp_prot, inet_dgram_ops and PF_INET writes are published before the new sk_dst_reset(), and inet6_sendmsg() dispatches on the lockless read: /* IPV6_ADDRFORM can change sk->sk_prot under us. */ prot = READ_ONCE(sk->sk_prot); Can a thread therefore enter IPv4 udp_sendmsg() and pick up the still cached IPv6 dst before the reset runs, in which case the reset cannot revoke the reference it already took? Would validating the family (or performing the connected store) under lock_sock, or adding a dst->ops->family check to the IPv4 path, cover these two windows instead of the after-the-fact reset? [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902010408.1057857-1-4ncienth%40gmail.com ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [net,v3] udp: revalidate socket family before publishing an IPv6 cork 2026-09-03 13:05 ` [net,v3] " netdev-bot+sashiko @ 2026-09-03 13:22 ` Paolo Abeni 2026-09-03 21:55 ` Kuniyuki Iwashima 0 siblings, 1 reply; 4+ messages in thread From: Paolo Abeni @ 2026-09-03 13:22 UTC (permalink / raw) To: 4ncienth, edumazet, kuba Cc: netdev, dsahern, idosch, davem, horms, willemdebruijn.kernel, vyasevic, linux-kernel, netdev-bot+sashiko On 9/3/26 3:05 PM, netdev-bot+sashiko@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 2 potential > issue(s) to consider: > > Critical: 0 · High: 2 · Medium: 0 · Low: 0 > > - [High] Incomplete fix: the sibling IPv6 socket-dst publisher in the > connect() path is not covered, so an IPv6 dst can still be installed… > - [High] The fix is writer-side and after-the-fact, so the mixed-family > socket dst it claims to eliminate remains observable by the unguarded… It looks like fixing this kind of issues, if possible at all, would require adding a significant complexity to the data-path. I'm wondering if we could just remove setsockopt(IPV6_ADDRFORM) support? /P ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [net,v3] udp: revalidate socket family before publishing an IPv6 cork 2026-09-03 13:22 ` Paolo Abeni @ 2026-09-03 21:55 ` Kuniyuki Iwashima 0 siblings, 0 replies; 4+ messages in thread From: Kuniyuki Iwashima @ 2026-09-03 21:55 UTC (permalink / raw) To: pabeni Cc: 4ncienth, davem, dsahern, edumazet, horms, idosch, kuba, linux-kernel, netdev-bot+sashiko, netdev, vyasevic, willemdebruijn.kernel From: Paolo Abeni <pabeni@redhat.com> Date: Thu, 3 Sep 2026 15:22:57 +0200 > On 9/3/26 3:05 PM, netdev-bot+sashiko@kernel.org wrote: > > Thank you for your contribution! Sashiko AI review found 2 potential > > issue(s) to consider: > > > > Critical: 0 · High: 2 · Medium: 0 · Low: 0 > > > > - [High] Incomplete fix: the sibling IPv6 socket-dst publisher in the > > connect() path is not covered, so an IPv6 dst can still be installed… > > - [High] The fix is writer-side and after-the-fact, so the mixed-family > > socket dst it claims to eliminate remains observable by the unguarded… > > It looks like fixing this kind of issues, if possible at all, would > require adding a significant complexity to the data-path. > > I'm wondering if we could just remove setsockopt(IPV6_ADDRFORM) support? +1. The feature was initially defined in RFC 2133 in 1997, but only two years later, it was removed from RFC 2553 in 1999. It's 2026 now, and I think most (all?) applications are ready to be passed IPv6 socekt fd given systemd does not use IPV6_ADDRFORM. ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-03 21:57 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-02 1:04 [PATCH net v3] udp: revalidate socket family before publishing an IPv6 cork Daehyeon Ko 2026-09-03 13:05 ` [net,v3] " netdev-bot+sashiko 2026-09-03 13:22 ` Paolo Abeni 2026-09-03 21:55 ` Kuniyuki Iwashima
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox