[PATCH net] ipv6: use READ_ONCE() for bindv6only default in inet6_create()
From: Runyu Xiao
Date: Sun May 31 2026 - 08:49:15 EST
inet6_create() copies net->ipv6.sysctl.bindv6only into sk->sk_ipv6only
without any locking. bindv6only is writable through the IPv6 sysctl
table via proc_dou8vec_minmax(), and adjacent lockless sysctl reads in
the same function already use READ_ONCE().
This read is reachable whenever AF_INET6 sockets are created while
/proc/sys/net/ipv6/bindv6only is being updated. In our QEMU/KCSAN stress
test on Linux v6.18.21, one actor repeatedly toggled
/proc/sys/net/ipv6/bindv6only while four concurrent readers repeatedly
created AF_INET6 stream and datagram sockets and queried IPV6_V6ONLY.
The writer completed 75313 sysctl updates in 45 seconds, and the readers
created more than 360000 IPv6 sockets in the same window.
KCSAN reported the following race:
==================================================================
BUG: KCSAN: data-race in inet6_create / proc_dou8vec_minmax
write (marked) to 0xffffffffaa27bbcd of 1 bytes by task 95 on cpu 1:
proc_dou8vec_minmax+0x206/0x220
proc_sys_call_handler+0x21d/0x300
proc_sys_write+0xe/0x20
vfs_write+0x559/0x6d0
ksys_write+0x88/0x110
__x64_sys_write+0x3c/0x50
x64_sys_call+0x1016/0x2020
do_syscall_64+0xb0/0x2c0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
read to 0xffffffffaa27bbcd of 1 bytes by task 97 on cpu 0:
inet6_create+0x351/0x700
__sock_create+0x149/0x280
__sys_socket+0x9f/0x130
__x64_sys_socket+0x3b/0x50
x64_sys_call+0x1c76/0x2020
do_syscall_64+0xb0/0x2c0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
value changed: 0x01 -> 0x00
==================================================================
Wrap the bindv6only read in READ_ONCE() to annotate the intentional
lockless access and match the surrounding per-net sysctl reader
contract.
This issue was first flagged by our static analysis tool while scanning
lockless sysctl readers, then manually audited and runtime-reproduced
with QEMU + KCSAN on Linux v6.18.21.
Build-tested by compiling net/ipv6/af_inet6.o on x86_64 netdev/main.
Runtime-tested with a QEMU/KCSAN stress test that concurrently toggled
/proc/sys/net/ipv6/bindv6only and created AF_INET6 sockets.
Fixes: 9fe516ba3fb2 ("inet: move ipv6only in sock_common")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Runyu Xiao <runyu.xiao@xxxxxxxxxx>
---
net/ipv6/af_inet6.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 0a88b376141d..79fc6ce6ff77 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -211,7 +211,7 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
np->pmtudisc = IPV6_PMTUDISC_WANT;
inet6_assign_bit(REPFLOW, sk, READ_ONCE(net->ipv6.sysctl.flowlabel_reflect) &
FLOWLABEL_REFLECT_ESTABLISHED);
- sk->sk_ipv6only = net->ipv6.sysctl.bindv6only;
+ sk->sk_ipv6only = READ_ONCE(net->ipv6.sysctl.bindv6only);
sk->sk_txrehash = READ_ONCE(net->core.sysctl_txrehash);
/* Init the ipv4 part of the socket since we can have sockets
--
2.34.1