* [PATCH v1 net-next 1/2] tcp: Do not allow buggy transitions between ehash and lhash2.
2026-09-04 3:35 [PATCH v1 net-next 0/2] net: Disallow buggy TCP transitions and IPV6_ADDRFORM Kuniyuki Iwashima
@ 2026-09-04 3:35 ` Kuniyuki Iwashima
2026-09-04 11:32 ` Jakub Sitnicki
2026-09-04 3:35 ` [PATCH v1 net-next 2/2] ipv6: Remove IPV6_ADDRFORM Kuniyuki Iwashima
1 sibling, 1 reply; 5+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-04 3:35 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Neal Cardwell, Willem de Bruijn, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev,
Kyle Zeng, Michal Luczaj, Hyunwoo Kim
The following state transitions have long been a playground for
syzbot, and recently AI joined in, reporting a lot more bugs.
* listen() + shutdown() + connect()
* connect() + connect(AF_UNSPEC) + listen()
All the fix attempts would add more code to the fast path, which
is not worth it.
Instead of playing whack-a-mole with these edge-case bugs,
let's disallow these transitions.
Note that unhashed_state is placed in the 4-byte hole after
icsk_pmtu_cookie.
$ pahole -C inet_connection_sock vmlinux
struct inet_connection_sock {
...
__u32 icsk_pmtu_cookie; /* 1208 4 */
unsigned char unhashed_state; /* 1212 1 */
/* XXX 3 bytes hole, try to pack */
Reported-by: Kyle Zeng <kylebot@openai.com>
Closes: https://lore.kernel.org/netdev/20260731140512.566464-1-david.lee@trailofbits.com/
Reported-by: Michal Luczaj <mhal@rbox.co>
Closes: https://lore.kernel.org/netdev/20260803-sockmap-lookup-tcp-leak-v2-0-306e025bfe66@rbox.co/
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Closes: https://lore.kernel.org/netdev/20260824033331.1084971-1-imv4bel@gmail.com/
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/inet_connection_sock.h | 1 +
net/ipv4/inet_connection_sock.c | 1 +
net/ipv4/inet_hashtables.c | 11 +++++++++++
3 files changed, 13 insertions(+)
diff --git a/include/net/inet_connection_sock.h b/include/net/inet_connection_sock.h
index 433c2df23076..903499581db7 100644
--- a/include/net/inet_connection_sock.h
+++ b/include/net/inet_connection_sock.h
@@ -94,6 +94,7 @@ struct inet_connection_sock {
u32 icsk_rto_max;
__u32 icsk_delack_max;
__u32 icsk_pmtu_cookie;
+ unsigned char unhashed_state;
const struct tcp_congestion_ops *icsk_ca_ops;
const struct inet_connection_sock_af_ops *icsk_af_ops;
const struct tcp_ulp_ops *icsk_ulp_ops;
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 6257459bcee2..560ca861b6d0 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -1253,6 +1253,7 @@ struct sock *inet_csk_clone_lock(const struct sock *sk,
memset(&newicsk->icsk_accept_queue, 0,
sizeof(newicsk->icsk_accept_queue));
+ newicsk->unhashed_state = 0;
inet_sk_set_state(newsk, TCP_SYN_RECV);
inet_clone_ulp(req, newsk, priority);
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index ba0faa9ae2bb..5abcb0debb27 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -803,6 +803,11 @@ int inet_hash(struct sock *sk)
inet_init_ehash_secret();
WARN_ON(!sk_unhashed(sk));
+
+ if (unlikely(inet_csk(sk)->unhashed_state &&
+ inet_csk(sk)->unhashed_state != TCP_LISTEN))
+ return -EINVAL;
+
ilb2 = inet_lhash2_bucket_sk(hashinfo, sk);
spin_lock(&ilb2->lock);
@@ -832,6 +837,9 @@ void inet_unhash(struct sock *sk)
return;
sock_rps_delete_flow(sk);
+
+ inet_csk(sk)->unhashed_state = sk->sk_state;
+
if (sk->sk_state == TCP_LISTEN) {
struct inet_listen_hashbucket *ilb2;
@@ -1058,6 +1066,9 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row,
int ret, i, low, high;
bool local_ports;
+ if (unlikely(inet_csk(sk)->unhashed_state == TCP_LISTEN))
+ return -EINVAL;
+
if (port) {
local_bh_disable();
ret = check_established(death_row, sk, port, NULL, false,
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v1 net-next 2/2] ipv6: Remove IPV6_ADDRFORM.
2026-09-04 3:35 [PATCH v1 net-next 0/2] net: Disallow buggy TCP transitions and IPV6_ADDRFORM Kuniyuki Iwashima
2026-09-04 3:35 ` [PATCH v1 net-next 1/2] tcp: Do not allow buggy transitions between ehash and lhash2 Kuniyuki Iwashima
@ 2026-09-04 3:35 ` Kuniyuki Iwashima
2026-09-04 9:10 ` Paolo Abeni
1 sibling, 1 reply; 5+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-04 3:35 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Neal Cardwell, Willem de Bruijn, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev,
Daehyeon Ko, Hyunwoo Kim
Recently, IPV6_ADDRFORM has received many AI-driven bug reports.
Fixing them properly would needlessly churn the fast paths in TCP
and UDP.
IPV6_ADDRFORM was initially introduced in RFC 2133 in 1997,
but only two years later, it was removed from RFC 2553 in 1999.
In 2026, modern applications natively support dual-stack sockets;
notably, systemd's socket activation does not use IPV6_ADDRFORM.
Also, getsockopt(IPV6_ADDRFORM) can be replaced with SO_DOMAIN.
Let's remove IPV6_ADDRFORM.
Later, we can remove sk->sk_prot_creator and revert commit
c26c192c3d48 ("udp: properly deal with xfrm encap and ADDRFORM").
Reported-by: Daehyeon Ko <4ncienth@gmail.com>
Closes: https://lore.kernel.org/netdev/20260902010408.1057857-1-4ncienth@gmail.com/
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Closes: https://lore.kernel.org/netdev/20260824033331.1084971-1-imv4bel@gmail.com/
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/linux/net.h | 2 +-
net/core/sock.c | 4 +-
net/ipv4/af_inet.c | 3 --
net/ipv6/af_inet6.c | 4 --
net/ipv6/ipv6_sockglue.c | 87 ----------------------------------------
5 files changed, 2 insertions(+), 98 deletions(-)
diff --git a/include/linux/net.h b/include/linux/net.h
index 3d82966e2243..470100ae7107 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -166,7 +166,7 @@ struct socket {
struct file *file;
struct sock *sk;
- const struct proto_ops *ops; /* Might change with IPV6_ADDRFORM or MPTCP. */
+ const struct proto_ops *ops; /* Might change with MPTCP. */
struct socket_wq wq;
};
diff --git a/net/core/sock.c b/net/core/sock.c
index fa60b7494c58..1d5927cd49a1 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -770,7 +770,7 @@ bool sk_mc_loop(const struct sock *sk)
return false;
if (!sk)
return true;
- /* IPV6_ADDRFORM can change sk->sk_family under us. */
+
switch (READ_ONCE(sk->sk_family)) {
case AF_INET:
return inet_test_bit(MC_LOOP, sk);
@@ -4010,7 +4010,6 @@ int sock_common_getsockopt(struct socket *sock, int level, int optname,
{
struct sock *sk = sock->sk;
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
return READ_ONCE(sk->sk_prot)->getsockopt(sk, level, optname, optval, optlen);
}
EXPORT_SYMBOL(sock_common_getsockopt);
@@ -4032,7 +4031,6 @@ int sock_common_setsockopt(struct socket *sock, int level, int optname,
{
struct sock *sk = sock->sk;
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
return READ_ONCE(sk->sk_prot)->setsockopt(sk, level, optname, optval, optlen);
}
EXPORT_SYMBOL(sock_common_setsockopt);
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index 32d006c1a8ee..d9421ac38d78 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -582,7 +582,6 @@ int inet_dgram_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
if (addr_len < sizeof(uaddr->sa_family))
return -EINVAL;
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
prot = READ_ONCE(sk->sk_prot);
if (uaddr->sa_family == AF_UNSPEC)
@@ -789,7 +788,6 @@ int inet_accept(struct socket *sock, struct socket *newsock,
{
struct sock *sk1 = sock->sk, *sk2;
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
arg->err = -EINVAL;
sk2 = READ_ONCE(sk1->sk_prot)->accept(sk1, arg);
if (!sk2)
@@ -875,7 +873,6 @@ void inet_splice_eof(struct socket *sock)
if (unlikely(inet_send_prepare(sk)))
return;
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
prot = READ_ONCE(sk->sk_prot);
if (prot->splice_eof)
prot->splice_eof(sock);
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 282912a11999..f0efdc13baf4 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -429,7 +429,6 @@ int inet6_bind_sk(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len)
const struct proto *prot;
int err = 0;
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
prot = READ_ONCE(sk->sk_prot);
/* If the socket has its own bind function then use it. */
if (prot->bind)
@@ -567,7 +566,6 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
case SIOCSIFDSTADDR:
return addrconf_set_dstaddr(net, argp);
default:
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
prot = READ_ONCE(sk->sk_prot);
if (!prot->ioctl)
return -ENOIOCTLCMD;
@@ -636,7 +634,6 @@ int inet6_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
if (unlikely(inet_send_prepare(sk)))
return -EAGAIN;
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
prot = READ_ONCE(sk->sk_prot);
return INDIRECT_CALL_2(prot->sendmsg, tcp_sendmsg, udpv6_sendmsg,
sk, msg, size);
@@ -651,7 +648,6 @@ int inet6_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
if (likely(!(flags & MSG_ERRQUEUE)))
sock_rps_record_flow(sk);
- /* IPV6_ADDRFORM can change sk->sk_prot under us. */
prot = READ_ONCE(sk->sk_prot);
return INDIRECT_CALL_2(prot->recvmsg, tcp_recvmsg, udpv6_recvmsg,
sk, msg, size, flags);
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index b4c977434c2e..1f68fb64a43e 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -547,86 +547,7 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
sockopt_lock_sock(sk);
- /* Another thread has converted the socket into IPv4 with
- * IPV6_ADDRFORM concurrently.
- */
- if (unlikely(sk->sk_family != AF_INET6))
- goto unlock;
-
switch (optname) {
-
- case IPV6_ADDRFORM:
- if (optlen < sizeof(int))
- goto e_inval;
- if (val == PF_INET) {
- if (sk->sk_type == SOCK_RAW)
- break;
-
- if (sk->sk_protocol == IPPROTO_UDP) {
- if (udp_sk(sk)->pending == AF_INET6) {
- retv = -EBUSY;
- break;
- }
- } else if (sk->sk_protocol == IPPROTO_TCP) {
- if (sk->sk_prot != &tcpv6_prot) {
- retv = -EBUSY;
- break;
- }
- } else {
- break;
- }
-
- if (sk->sk_state != TCP_ESTABLISHED) {
- retv = -ENOTCONN;
- break;
- }
-
- if (ipv6_only_sock(sk) ||
- !ipv6_addr_v4mapped(&sk->sk_v6_daddr)) {
- retv = -EADDRNOTAVAIL;
- break;
- }
-
- __ipv6_sock_mc_close(sk);
- __ipv6_sock_ac_close(sk);
-
- if (sk->sk_protocol == IPPROTO_TCP) {
- struct inet_connection_sock *icsk = inet_csk(sk);
-
- sock_prot_inuse_add(net, sk->sk_prot, -1);
- sock_prot_inuse_add(net, &tcp_prot, 1);
-
- /* Paired with READ_ONCE(sk->sk_prot) in inet6_stream_ops */
- WRITE_ONCE(sk->sk_prot, &tcp_prot);
- /* Paired with READ_ONCE() in tcp_(get|set)sockopt() */
- WRITE_ONCE(icsk->icsk_af_ops, &ipv4_specific);
- WRITE_ONCE(sk->sk_socket->ops, &inet_stream_ops);
- WRITE_ONCE(sk->sk_family, PF_INET);
- tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
- } else {
- sock_prot_inuse_add(net, sk->sk_prot, -1);
- sock_prot_inuse_add(net, &udp_prot, 1);
-
- /* Paired with READ_ONCE(sk->sk_prot) in inet6_dgram_ops */
- WRITE_ONCE(sk->sk_prot, &udp_prot);
- WRITE_ONCE(sk->sk_socket->ops, &inet_dgram_ops);
- WRITE_ONCE(sk->sk_family, PF_INET);
- }
-
- /* Disable all options not to allocate memory anymore,
- * but there is still a race. See the lockless path
- * in udpv6_sendmsg() and ipv6_local_rxpmtu().
- */
- np->rxopt.all = 0;
-
- inet6_cleanup_sock(sk);
-
- module_put(THIS_MODULE);
- retv = 0;
- break;
- }
- goto e_inval;
-
case IPV6_V6ONLY:
if (optlen < sizeof(int) ||
inet_sk(sk)->inet_num)
@@ -1088,14 +1009,6 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
if (copy_from_sockptr(&len, optlen, sizeof(int)))
return -EFAULT;
switch (optname) {
- case IPV6_ADDRFORM:
- if (sk->sk_protocol != IPPROTO_UDP &&
- sk->sk_protocol != IPPROTO_TCP)
- return -ENOPROTOOPT;
- if (sk->sk_state != TCP_ESTABLISHED)
- return -ENOTCONN;
- val = sk->sk_family;
- break;
case MCAST_MSFILTER:
if (in_compat_syscall())
return compat_ipv6_get_msfilter(sk, optval, optlen, len);
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 5+ messages in thread