diff options
| author | Hangbin Liu <liuhangbin@gmail.com> | 2026-03-31 12:17:18 +0800 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-04-01 17:44:35 -0700 |
| commit | ffb5a4843c5bde702ed17cbcdbda98b37f7a6dad (patch) | |
| tree | 0865ded3fb1bb3e95d81a0d1394e23e124de759a | |
| parent | 48b3cd69265f346f64b93064723492da46206e9b (diff) | |
ipv6: fix data race in fib6_metric_set() using cmpxchgdavem/net/maindavem/net/HEAD
fib6_metric_set() may be called concurrently from softirq context without
holding the FIB table lock. A typical path is:
ndisc_router_discovery()
spin_unlock_bh(&table->tb6_lock) <- lock released
fib6_metric_set(rt, RTAX_HOPLIMIT, ...) <- lockless call
When two CPUs process Router Advertisement packets for the same router
simultaneously, they can both arrive at fib6_metric_set() with the same
fib6_info pointer whose fib6_metrics still points to dst_default_metrics.
if (f6i->fib6_metrics == &dst_default_metrics) { /* both CPUs: true */
struct dst_metrics *p = kzalloc_obj(*p, GFP_ATOMIC);
refcount_set(&p->refcnt, 1);
f6i->fib6_metrics = p; /* CPU1 overwrites CPU0's p -> p0 leaked */
}
The dst_metrics allocated by the losing CPU has refcnt=1 but no pointer
to it anywhere in memory, producing a kmemleak report:
unreferenced object 0xff1100025aca1400 (size 96):
comm "softirq", pid 0, jiffies 4299271239
backtrace:
kmalloc_trace+0x28a/0x380
fib6_metric_set+0xcd/0x180
ndisc_router_discovery+0x12dc/0x24b0
icmpv6_rcv+0xc16/0x1360
Fix this by:
- Set val for p->metrics before published via cmpxchg() so the metrics
value is ready before the pointer becomes visible to other CPUs.
- Replace the plain pointer store with cmpxchg() and free the allocation
safely when competition failed.
- Add READ_ONCE()/WRITE_ONCE() for metrics[] setting in the non-default
metrics path to prevent compiler-based data races.
Fixes: d4ead6b34b67 ("net/ipv6: move metrics from dst to rt6_info")
Reported-by: Fei Liu <feliu@redhat.com>
Reviewed-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260331-b4-fib6_metric_set-kmemleak-v3-1-88d27f4d8825@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | net/ipv6/ip6_fib.c | 14 |
1 files changed, 11 insertions, 3 deletions
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index dd26657b6a4a..45ef4d65dcbc 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -727,20 +727,28 @@ unlock: void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val) { + struct dst_metrics *m; + if (!f6i) return; - if (f6i->fib6_metrics == &dst_default_metrics) { + if (READ_ONCE(f6i->fib6_metrics) == &dst_default_metrics) { + struct dst_metrics *dflt = (struct dst_metrics *)&dst_default_metrics; struct dst_metrics *p = kzalloc_obj(*p, GFP_ATOMIC); if (!p) return; + p->metrics[metric - 1] = val; refcount_set(&p->refcnt, 1); - f6i->fib6_metrics = p; + if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt) + kfree(p); + else + return; } - f6i->fib6_metrics->metrics[metric - 1] = val; + m = READ_ONCE(f6i->fib6_metrics); + WRITE_ONCE(m->metrics[metric - 1], val); } /* |
