In the Linux kernel, the following vulnerability has been resolved:
tcp: defer md5siginfo kfree past RCU grace period in tcpconnect
The md5+ao reconciliation in tcpconnect() (net/ipv4/tcpoutput.c) has two symmetric branches:
if (needs_md5) {
tcp_ao_destroy_sock(sk, false);
} else if (needs_ao) {
tcp_clear_md5_list(sk);
kfree(rcu_replace_pointer(tp->md5sig_info, NULL, ...));
}
Both branches free a per-socket auth-info object while the socket is in TCPSYNSENT and is already on the inet ehash (inserted by inethashconnect() in tcpv4connect()). Both branches are reachable by softirq RX-path readers that load the corresponding info pointer via implicit RCU before bhlocksock_nested() is taken.
The needsmd5 branch is fixed in the prior patch by re-introducing the callrcu() free in tcpaodestroysock(): the equivalent per-key loop runs inside tcpaoinfofreercu(), the RCU callback, so by the time it frees each tcpaokey all softirq readers that captured the container have already completed rcuread_unlock().
The needsao branch is not symmetric in the same way. The container free can be deferred via kfreercu(md5sig, rcu) -- struct tcpmd5siginfo already has the required rcu member (include/net/tcp.h:1999-2002), and the rest of the tree already does this in the tcpmd5siginfoadd() rollback paths (net/ipv4/tcpipv4.c:1410, 1436). But the per-key teardown is done by tcpclearmd5list() in process context BEFORE the container's RCU grace period: it walks &md5sig->head and frees each tcpmd5sigkey with bare hlistdel + kfree. A concurrent softirq reader in __tcpmd5do_lookup() / _tcpmd5dolookupexact() (tcpipv4.c:1253, 1298) walks the same list via hlistforeachentryrcu() and races with that bare kfree on the keys themselves -- a per-key slab use-after-free of the same class as the TCP-AO bug, on the same race window.
Fix this in two halves:
Convert the bare kfree() in tcpconnect() to kfreercu() so the md5siginfo container joins the rest of the md5sig lifecycle. The local-variable lift is mechanical and required because kfreercu() is a macro that expects an lvalue.
Make tcpclearmd5list() RCU-safe by replacing hlistdel + kfree(key) with hlistdelrcu + kfreercu(key, rcu). struct tcpmd5sigkey already carries the rcu member (include/net/tcp.h:1995) and tcpmd5dodel() (net/ipv4/tcpipv4.c:1456) already uses kfreercu, so this restores the lifecycle invariant the rest of the file follows rather than introducing a one-off.
The other caller of tcpclearmd5list() is tcpmd5destructsock() (net/ipv4/tcp.c:412), which runs from the sock destructor when the socket is already unhashed and unreachable; the extra grace period there is unnecessary but harmless. Making the helper unconditionally RCU-safe is the cleaner contract.
The needsao branch is not reachable by the userns reproducer used to demonstrate the AO-side splat (the repro installs both keys but ends up in the needsmd5 branch because the connect peer matches the MD5 key, not the AO key); however the symmetric race exists and a maintainer touching this code should not have to think about which branch escapes RCU and which one does not.
[also credits to Qihang, who found that this races with tcp-diag]
{
"osv_generated_from": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/72xxx/CVE-2026-72139.json",
"cna_assigner": "Linux"
}