Re: [PATCH net] tcp: fix AO info use-after-free in tcp_ao_connect_init()

From: Eric Dumazet

Date: Tue Aug 18 2026 - 12:40:37 EST


On Tue, Aug 18, 2026 at 6:21 PM Qing Ming <a0yami@xxxxxxxxxxx> wrote:
>
> tcp_v4_connect() adds a SYN-SENT socket to the ehash before calling
> tcp_connect(). If TCP-AO is configured, tcp_connect() first verifies that
> a key matches the peer and the bound device's current L3 master.
> tcp_ao_connect_init() later resolves the L3 master again and removes keys
> which do not match it.
>
> The socket lock does not stabilize the bound device's VRF membership.
> Detaching the device from its VRF between the initial validation and the
> L3-master calculation in tcp_ao_connect_init() can therefore make the
> validation succeed while initialization observes the default L3 domain and
> removes the only key. The subsequent AO lookup then fails, so the no-key
> path clears tp->ao_info and frees it directly.
>
> The receive path can find the socket in the ehash and load tp->ao_info
> under RCU before acquiring the socket lock. A reader which loaded the old
> pointer can thus continue into tcp_inbound_ao_hash() after the direct free.
>
> The issue was found during a static audit of TCP-AO object lifetime. An
> unprivileged reproducer in self-created user and network namespaces raced
> connect() with detaching a veth from its VRF while sending TCP-AO segments.
> It triggered the same KASAN report on two fresh boots:
>
> BUG: KASAN: slab-use-after-free in tcp_inbound_ao_hash+0x585/0x19f0
> Write of size 8 at addr ffff88800bf88128 by task tcp_ao_vrf_race/232
>
> Call Trace:
> tcp_inbound_ao_hash+0x585/0x19f0
> tcp_inbound_hash+0x677/0xa80
> tcp_v4_rcv+0x1c3e/0x3ab0
>
> Allocated by task 235:
> tcp_ao_alloc_info+0x43/0xf0
> tcp_ao_add_cmd+0xdf7/0x13b0
> do_tcp_setsockopt+0x168c/0x2640
>
> Freed by task 235:
> kfree+0x1b8/0x550
> tcp_connect+0x252/0x4f00
> tcp_v4_connect+0x1114/0x1720
>
> The bad address is 40 bytes inside the freed 128-byte object, matching the
> tcp_ao_info counters.key_not_found field. The two runs used 1000 attempts
> each, reached the no-key path 366 and 411 times, and produced one and two
> KASAN reports respectively. With this change, the same reproducer reached
> the no-key path 366 times in 1000 attempts without a KASAN report or oops.
>
> Use tcp_ao_destroy_sock() for the no-key path. It unpublishes the AO info,
> updates the socket memory and static-key accounting, and defers the free
> until after an RCU grace period.
>
> Fixes: 248411b8cb89 ("net/tcp: Wire up l3index to TCP-AO")
> Cc: stable@xxxxxxxxxxxxxxx
> Assisted-by: Codex:gpt-5
> Signed-off-by: Qing Ming <a0yami@xxxxxxxxxxx>
> ---
> net/ipv4/tcp_ao.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c
> index e4ec60a33496..4dc5910da076 100644
> --- a/net/ipv4/tcp_ao.c
> +++ b/net/ipv4/tcp_ao.c
> @@ -1191,8 +1191,7 @@ void tcp_ao_connect_init(struct sock *sk)
> * at least one tcp-ao key that matches the remote peer.
> */
> WARN_ON_ONCE(1);
> - rcu_assign_pointer(tp->ao_info, NULL);
> - kfree(ao_info);
> + tcp_ao_destroy_sock(sk, false);
> }

Why keeping WARN_ON_ONCE(1) then?
This will crash with panic_on_warn=1.

pw-bot: cr

Please wait ~24 hours before sending a new version.