[PATCH net v2] udp: revalidate socket family before publishing an IPv6 cork

From: Daehyeon Ko

Date: Sat Aug 29 2026 - 09:22:14 EST


udpv6_sendmsg() prepares the IPv6 flow and route 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 while the send path is doing that
lockless preparation because no cork has been published yet.

If the conversion wins the race, udpv6_sendmsg() later publishes an
AF_INET6 cork on an AF_INET socket. Uncorking through the IPv4 socket
operations then interprets the IPv6 cork as IPv4 state. The IPv4
finalizer writes a 20-byte IPv4 header into the 40-byte IPv6 header
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; one of three fresh boots
contained recognizable stale heap data.

The route prepared by the racing send is not published in the socket dst
cache. An implicit send on the IPv4-mapped peer required by ADDRFORM
delegates to udp_sendmsg() before the IPv6 lookup. An explicit native IPv6
destination reaches the lookup, but leaves connected false and therefore
cannot call ip6_sk_dst_store_flow(). Conversely, a native connected send
can publish an IPv6 dst, but ADDRFORM rejects that peer with EADDRNOTAVAIL.
The same reasoning covers the non-corking explicit send path.

After taking the lock, revalidate that IPV6_ADDRFORM has not changed the
socket family before publishing the cork. The existing error path releases
the invocation's prepared dst, flowlabel, and transmit-option references;
there is no socket dst cache entry from this send to reset. With this
change, the serialized controls retain their existing results and the
forbidden mixed state occurred zero times across 20,000 sockets.

Fixes: 03485f2adcde ("udpv6: Add lockless sendmsg() support")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Daehyeon Ko <4ncienth@xxxxxxxxx>
---
Changes in v2:
- Add the requested individual maintainer Cc recipients.
- Explain why the corked and non-corked explicit IPv6 sends do not publish
an IPv6 socket dst cache, and why sk_dst_reset() is not needed here.
- Note that the separately raised UDP sockmap proto mismatch was reproduced
and will be handled independently.
- No code changes.
---
net/ipv6/udp.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index fd875908ac0c66..566c634a5a5945 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -1716,6 +1716,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: 2188569e7e1b0bc3f3b557dc97ab7a02befc11c8
--
2.54.0