* Re: [BUG] ipv4: slab-use-after-free in fib_nhc_update_mtu() - unlocked walk of kfree_rcu'd fib_nh_exception on NETDEV_CHANGEMTU
[not found] <CAJzEm4pwK2rC=1YpA1XG3qCR5BPQ6O6oFY5=L5dDViKJN-jqbQ@mail.gmail.com>
@ 2026-08-06 12:32 ` Ido Schimmel
2026-08-06 13:36 ` Chengfeng Ye
0 siblings, 1 reply; 2+ messages in thread
From: Ido Schimmel @ 2026-08-06 12:32 UTC (permalink / raw)
To: 안도현, nicoyip.dev
Cc: dsahern, davem, edumazet, kuba, pabeni, sd, netdev
Please send plain text emails. HTML emails are filtered and don't make
their way to the ML. See a comment at the end.
On Thu, Aug 06, 2026 at 03:07:27PM +0900, 안도현 wrote:
> HANDLING NOTE
> =============
> This bug was identified with the assistance of an automated (AI) code
> audit. Per Documentation/process/security-bugs.rst, an AI-assisted
> finding must be treated as public, so this is reported in the open and
> was not submitted to the [1]security@kernel.org embargo process.
> For the same reason, I am not including the reproducer in this public
> message. I have a tested source reproducer and will provide it privately
> to maintainers on request.
>
> SUMMARY
> =======
> fib_nhc_update_mtu() traverses and writes fib_nh_exception ("fnhe")
> objects using rcu_dereference_protected(x, 1) -- asserting a lock is held
> -- but its only caller holds RTNL, not the fnhe lock, and is not in an RCU
> read-side critical section. fnhe objects are freed with kfree_rcu() from
> softirq under fnhe_lock, so on a CONFIG_PREEMPT_RCU kernel a grace period
> can end mid-walk and free an fnhe the loop still dereferences. This is a
> slab-use-after-free (read and write) of a kmalloc-96 object, reproduced
> under KASAN on 7.2-rc6. Present unchanged since v4.19.
>
> AFFECTED VERSIONS
> =================
> Introduced in v4.19 by:
> af7d6cce5369 ("net: ipv4: update fnhe_pmtu when first hop's MTU
> changes")
> which added the loop (then nh_update_mtu(); renamed and exported as
> fib_nhc_update_mtu() by 06c77c3e67b0 "ipv4: Rename and export
> nh_update_mtu"). The unlocked walk is identical from v4.19 to mainline.
>
> Reproduced on:
> Linux 7.2-rc6, mainline commit 075b74841bd0, arm64, KASAN.
> Present in current mainline (fib_semantics.c fib_nhc_update_mtu()).
>
> THE DEFECT
> ==========
> net/ipv4/fib_semantics.c, fib_nhc_update_mtu():
>
> bucket = rcu_dereference_protected(nhc->nhc_exceptions, 1);
> if (!bucket)
> return;
> for (i = 0; i < FNHE_HASH_SIZE; i++) {
> struct fib_nh_exception *fnhe;
> for (fnhe = rcu_dereference_protected(bucket[i].chain, 1); fnhe;
> fnhe = rcu_dereference_protected(fnhe->fnhe_next, 1)) {
> if (fnhe->fnhe_mtu_locked) {
> if (new <= fnhe->fnhe_pmtu) {
> fnhe->fnhe_pmtu = new;
> fnhe->fnhe_mtu_locked = false;
> }
> } else if (new < fnhe->fnhe_pmtu || orig == fnhe->fnhe_pmtu) {
> fnhe->fnhe_pmtu = new;
> }
> }
> }
>
> The "1" asserts the caller holds the fnhe update-side lock. It does not.
> The only caller is fib_sync_mtu() (same file), reached on a device MTU
> change:
>
> NETDEV_CHANGEMTU -> fib_netdev_event() -> fib_sync_mtu()
> -> fib_nhc_update_mtu()
>
> and call_netdevice_notifiers_info() (net/core/dev.c) runs the chain under
> ASSERT_RTNL() only -- no rcu_read_lock(), no local_bh_disable().
>
> fnhe objects are created and freed under fnhe_lock in softirq on the
> ICMP/redirect input path:
>
> __ip_rt_update_pmtu() / __ip_do_redirect() -> update_or_create_fnhe()
> -> fnhe_remove_oldest() / ip_del_fnhe() -> kfree_rcu()
>
> RTNL and fnhe_lock are unrelated locks; RTNL does not exclude softirq.
> Under CONFIG_PREEMPT_RCU a process-context loop that never entered an RCU
> read-side critical section is not an RCU reader, so a grace period may end
> mid-walk and the kfree_rcu() callback frees an fnhe the loop is still
> dereferencing.
>
> fnhe_lock is static to net/ipv4/route.c and not visible in
> fib_semantics.c, which is presumably why the code used the constant
> assertion instead of taking the lock.
>
> KASAN REPORT (7.2-rc6, arm64; timestamps removed, stacks trimmed)
> =================================================================
> BUG: KASAN: slab-use-after-free in fib_nhc_update_mtu+0x78/0xd0
> Read of size 1 at addr ffff000016596794 by task payload/100
>
> CPU: 0 ... 7.2.0-rc6 #1 PREEMPT
> Call trace:
> fib_nhc_update_mtu
> fib_sync_mtu
> fib_netdev_event
> call_netdevice_notifiers_info
> netif_set_mtu_ext
> dev_set_mtu
> dev_ioctl
> sock_ioctl
>
> Allocated by task 99:
> update_or_create_fnhe
> __ip_rt_update_pmtu
> icmp_unreach
> icmp_rcv
> ip_local_deliver
>
> Freed by task 27:
> __rcu_free_sheaf_prepare
> rcu_free_sheaf
> rcu_core
> handle_softirqs
>
> The buggy address belongs to the cache kmalloc-96 of size 96.
> The buggy address is located 20 bytes inside of freed 96-byte region.
>
> (struct fib_nh_exception is 80 bytes -> kmalloc-96; offset 20 is the bool
> fnhe_mtu_locked read at the top of the loop body.)
>
> CONDITIONS
> ==========
> - CONFIG_PREEMPT_RCU=y (all CONFIG_PREEMPT / PREEMPT_RT kernels).
> - Concurrency between a device MTU change (the walk) and ICMP-induced fnhe
> churn/expiry (the alloc + kfree_rcu free).
> - Reachability: SIOCSIFMTU on a device carrying routes with exceptions
> needs CAP_NET_ADMIN in the device's netns; the fnhe population is driven
> by ICMP "fragmentation needed". Where unprivileged user namespaces are
> permitted, both are reachable by an unprivileged local user via
> unshare(CLONE_NEWUSER|CLONE_NEWNET) + /dev/net/tun; where restricted,
> it requires CAP_NET_ADMIN in some netns.
>
> REPRODUCER
> ==========
> I have a tested C reproducer that triggers the KASAN report above on
> 7.2-rc6/arm64, typically within seconds. Per the handling note it is not
> included here; I can provide it privately to maintainers on request.
>
> IMPACT
> ======
> The demonstrated result is a KASAN-detected read, and a write of dev->mtu,
> to an RCU-freed kmalloc-96 object (a memory-safety violation). No
> privilege-escalation exploit has been developed or demonstrated.
>
> MITIGATION
> ==========
> Restricting unprivileged user namespaces, restricting creation of network
> namespaces, or denying unprivileged access to /dev/net/tun reduces
> exposure to unprivileged local users. None of these protect a context
> that already holds CAP_NET_ADMIN in a network namespace. (I have not
> verified that these fully block every path.)
>
> PROPOSED FIX
> ============
> fnhe objects are freed via kfree_rcu(), so an RCU read-side critical
> section around the traversal keeps them alive for its duration; fnhe_lock
> is not visible here, so this is also the least invasive fix.
> Best regards,
> Dohyeon An
Chengfeng Ye sent a fix last week, but looks like we need v2 which
acquires fnhe_lock for the per-entry update (while still using RCU for
the traversal).
https://lore.kernel.org/netdev/20260731162938.3388534-1-nicoyip.dev@gmail.com/
^ permalink raw reply [flat|nested] 2+ messages in thread