public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
@ 2026-03-26  4:22 Hangbin Liu
  2026-03-26  6:23 ` Jiayuan Chen
  2026-03-26 12:05 ` Eric Dumazet
  0 siblings, 2 replies; 9+ messages in thread
From: Hangbin Liu @ 2026-03-26  4:22 UTC (permalink / raw)
  To: David S. Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman
  Cc: David Ahern, netdev, linux-kernel, Fei Liu, Hangbin Liu

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 replacing the plain pointer store with cmpxchg() and free
the allocation safely when competition failed.

Fixes: d4ead6b34b67 ("net/ipv6: move metrics from dst to rt6_info")
Reported-by: Fei Liu <feliu@redhat.com>
Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 net/ipv6/ip6_fib.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index dd26657b6a4a..64de761f40d5 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
 	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;
 
 		refcount_set(&p->refcnt, 1);
-		f6i->fib6_metrics = p;
+		if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
+			kfree(p);
 	}
 
 	f6i->fib6_metrics->metrics[metric - 1] = val;

---
base-commit: c4ea7d8907cf72b259bf70bd8c2e791e1c4ff70f
change-id: 20260326-b4-fib6_metric_set-kmemleak-7aa51978284a

Best regards,
-- 
Hangbin Liu <liuhangbin@gmail.com>


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26  4:22 [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg Hangbin Liu
@ 2026-03-26  6:23 ` Jiayuan Chen
  2026-03-26  6:44   ` Hangbin Liu
  2026-03-26 12:05 ` Eric Dumazet
  1 sibling, 1 reply; 9+ messages in thread
From: Jiayuan Chen @ 2026-03-26  6:23 UTC (permalink / raw)
  To: Hangbin Liu, David S. Miller, David Ahern, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: David Ahern, netdev, linux-kernel, Fei Liu


On 3/26/26 12:22 PM, Hangbin Liu wrote:
> 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 replacing the plain pointer store with cmpxchg() and free
> the allocation safely when competition failed.
>
> Fixes: d4ead6b34b67 ("net/ipv6: move metrics from dst to rt6_info")
> Reported-by: Fei Liu <feliu@redhat.com>
> Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
> ---
>   net/ipv6/ip6_fib.c | 6 ++++--
>   1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
> index dd26657b6a4a..64de761f40d5 100644
> --- a/net/ipv6/ip6_fib.c
> +++ b/net/ipv6/ip6_fib.c
> @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
>   	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;
>   
>   		refcount_set(&p->refcnt, 1);
> -		f6i->fib6_metrics = p;
> +		if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
> +			kfree(p);
>   	}
>   


[...]

>   	f6i->fib6_metrics->metrics[metric - 1] = val;

Suggest using marked accessors to suppress KCSAN warnings:

struct dst_metrics *m = READ_ONCE(f6i->fib6_metrics);
WRITE_ONCE(m->metrics[metric - 1], val);



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26  6:23 ` Jiayuan Chen
@ 2026-03-26  6:44   ` Hangbin Liu
  2026-03-26  7:13     ` Hangbin Liu
  0 siblings, 1 reply; 9+ messages in thread
From: Hangbin Liu @ 2026-03-26  6:44 UTC (permalink / raw)
  To: Jiayuan Chen
  Cc: David S. Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, David Ahern, netdev, linux-kernel,
	Fei Liu

On Thu, Mar 26, 2026 at 02:23:15PM +0800, Jiayuan Chen wrote:
> 
> On 3/26/26 12:22 PM, Hangbin Liu wrote:
> > 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 replacing the plain pointer store with cmpxchg() and free
> > the allocation safely when competition failed.
> > 
> > Fixes: d4ead6b34b67 ("net/ipv6: move metrics from dst to rt6_info")
> > Reported-by: Fei Liu <feliu@redhat.com>
> > Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
> > ---
> >   net/ipv6/ip6_fib.c | 6 ++++--
> >   1 file changed, 4 insertions(+), 2 deletions(-)
> > 
> > diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
> > index dd26657b6a4a..64de761f40d5 100644
> > --- a/net/ipv6/ip6_fib.c
> > +++ b/net/ipv6/ip6_fib.c
> > @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
> >   	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;
> >   		refcount_set(&p->refcnt, 1);
> > -		f6i->fib6_metrics = p;
> > +		if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
> > +			kfree(p);
> >   	}
> 
> 
> [...]
> 
> >   	f6i->fib6_metrics->metrics[metric - 1] = val;
> 
> Suggest using marked accessors to suppress KCSAN warnings:
> 
> struct dst_metrics *m = READ_ONCE(f6i->fib6_metrics);
> WRITE_ONCE(m->metrics[metric - 1], val);

Thanks, I will update this in next version.

Hangbin

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26  6:44   ` Hangbin Liu
@ 2026-03-26  7:13     ` Hangbin Liu
  2026-03-26  7:59       ` Jiayuan Chen
  0 siblings, 1 reply; 9+ messages in thread
From: Hangbin Liu @ 2026-03-26  7:13 UTC (permalink / raw)
  To: Jiayuan Chen
  Cc: David S. Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, David Ahern, netdev, linux-kernel,
	Fei Liu

On Thu, Mar 26, 2026 at 06:44:43AM +0000, Hangbin Liu wrote:
> On Thu, Mar 26, 2026 at 02:23:15PM +0800, Jiayuan Chen wrote:
> > 
> > On 3/26/26 12:22 PM, Hangbin Liu wrote:
> > > 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 replacing the plain pointer store with cmpxchg() and free
> > > the allocation safely when competition failed.
> > > 
> > > Fixes: d4ead6b34b67 ("net/ipv6: move metrics from dst to rt6_info")
> > > Reported-by: Fei Liu <feliu@redhat.com>
> > > Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
> > > ---
> > >   net/ipv6/ip6_fib.c | 6 ++++--
> > >   1 file changed, 4 insertions(+), 2 deletions(-)
> > > 
> > > diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
> > > index dd26657b6a4a..64de761f40d5 100644
> > > --- a/net/ipv6/ip6_fib.c
> > > +++ b/net/ipv6/ip6_fib.c
> > > @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
> > >   	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;
> > >   		refcount_set(&p->refcnt, 1);
> > > -		f6i->fib6_metrics = p;
> > > +		if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
> > > +			kfree(p);
> > >   	}
> > 
> > 
> > [...]
> > 
> > >   	f6i->fib6_metrics->metrics[metric - 1] = val;
> > 
> > Suggest using marked accessors to suppress KCSAN warnings:
> > 
> > struct dst_metrics *m = READ_ONCE(f6i->fib6_metrics);
> > WRITE_ONCE(m->metrics[metric - 1], val);
> 
> Thanks, I will update this in next version.

BTW, do we really need to WRITE_ONCE here? What if the `val` are different
on 2 CPUs? This would hide the problem, right?

Thanks
Hangbin

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26  7:13     ` Hangbin Liu
@ 2026-03-26  7:59       ` Jiayuan Chen
  0 siblings, 0 replies; 9+ messages in thread
From: Jiayuan Chen @ 2026-03-26  7:59 UTC (permalink / raw)
  To: Hangbin Liu
  Cc: David S. Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, David Ahern, netdev, linux-kernel,
	Fei Liu


On 3/26/26 3:13 PM, Hangbin Liu wrote:
> On Thu, Mar 26, 2026 at 06:44:43AM +0000, Hangbin Liu wrote:
>> On Thu, Mar 26, 2026 at 02:23:15PM +0800, Jiayuan Chen wrote:
>>> On 3/26/26 12:22 PM, Hangbin Liu wrote:
>>>> 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 replacing the plain pointer store with cmpxchg() and free
>>>> the allocation safely when competition failed.
>>>>
>>>> Fixes: d4ead6b34b67 ("net/ipv6: move metrics from dst to rt6_info")
>>>> Reported-by: Fei Liu <feliu@redhat.com>
>>>> Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
>>>> ---
>>>>    net/ipv6/ip6_fib.c | 6 ++++--
>>>>    1 file changed, 4 insertions(+), 2 deletions(-)
>>>>
>>>> diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
>>>> index dd26657b6a4a..64de761f40d5 100644
>>>> --- a/net/ipv6/ip6_fib.c
>>>> +++ b/net/ipv6/ip6_fib.c
>>>> @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
>>>>    	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;
>>>>    		refcount_set(&p->refcnt, 1);
>>>> -		f6i->fib6_metrics = p;
>>>> +		if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
>>>> +			kfree(p);
>>>>    	}
>>>
>>> [...]
>>>
>>>>    	f6i->fib6_metrics->metrics[metric - 1] = val;
>>> Suggest using marked accessors to suppress KCSAN warnings:
>>>
>>> struct dst_metrics *m = READ_ONCE(f6i->fib6_metrics);
>>> WRITE_ONCE(m->metrics[metric - 1], val);
>> Thanks, I will update this in next version.
> BTW, do we really need to WRITE_ONCE here? What if the `val` are different
> on 2 CPUs? This would hide the problem, right?
>
> Thanks
> Hangbin
If concurrent writes to the same memory by two CPUs are considered a bug,
the solution is prevention, not detection by KCSAN. Furthermore, WRITE_ONCE
also helps suppress warnings from read-write races. For example, commit
6e84fc395e90 demonstrates this.



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26  4:22 [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg Hangbin Liu
  2026-03-26  6:23 ` Jiayuan Chen
@ 2026-03-26 12:05 ` Eric Dumazet
  2026-03-26 13:13   ` Hangbin Liu
  1 sibling, 1 reply; 9+ messages in thread
From: Eric Dumazet @ 2026-03-26 12:05 UTC (permalink / raw)
  To: Hangbin Liu
  Cc: David S. Miller, David Ahern, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, netdev, linux-kernel, Fei Liu

On Wed, Mar 25, 2026 at 9:22 PM Hangbin Liu <liuhangbin@gmail.com> wrote:
>
> 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 replacing the plain pointer store with cmpxchg() and free
> the allocation safely when competition failed.
>
> Fixes: d4ead6b34b67 ("net/ipv6: move metrics from dst to rt6_info")
> Reported-by: Fei Liu <feliu@redhat.com>
> Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
> ---
>  net/ipv6/ip6_fib.c | 6 ++++--
>  1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
> index dd26657b6a4a..64de761f40d5 100644
> --- a/net/ipv6/ip6_fib.c
> +++ b/net/ipv6/ip6_fib.c
> @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
>         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;
>
>                 refcount_set(&p->refcnt, 1);
> -               f6i->fib6_metrics = p;
> +               if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
> +                       kfree(p);
>         }
>

The following line should happen before the cmpxchg(),
 ->metrics[X] accesses also need READ_ONCE()/WRITE_ONCE() annotations.


>         f6i->fib6_metrics->metrics[metric - 1] = val;
>
> ---
> base-commit: c4ea7d8907cf72b259bf70bd8c2e791e1c4ff70f
> change-id: 20260326-b4-fib6_metric_set-kmemleak-7aa51978284a
>
> Best regards,
> --
> Hangbin Liu <liuhangbin@gmail.com>
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26 12:05 ` Eric Dumazet
@ 2026-03-26 13:13   ` Hangbin Liu
  2026-03-26 13:43     ` Jiayuan Chen
  0 siblings, 1 reply; 9+ messages in thread
From: Hangbin Liu @ 2026-03-26 13:13 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S. Miller, David Ahern, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, netdev, Jiayuan Chen, linux-kernel,
	Fei Liu

On Thu, Mar 26, 2026 at 05:05:57AM -0700, Eric Dumazet wrote:
> > diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
> > index dd26657b6a4a..64de761f40d5 100644
> > --- a/net/ipv6/ip6_fib.c
> > +++ b/net/ipv6/ip6_fib.c
> > @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
> >         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;
> >
> >                 refcount_set(&p->refcnt, 1);
> > -               f6i->fib6_metrics = p;
> > +               if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
> > +                       kfree(p);
> >         }
> >
> 
> The following line should happen before the cmpxchg(),
>  ->metrics[X] accesses also need READ_ONCE()/WRITE_ONCE() annotations.

Hi Eric,

Jiayuan also suggested to using READ_ONCE()/WRITE_ONCE() for metrics[X]
accesses. But I don't get why this line should happen before the cmpxchg(),
Would you please help explain?

> 
> 
> >         f6i->fib6_metrics->metrics[metric - 1] = val;
> >

Thanks
Hangbin

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26 13:13   ` Hangbin Liu
@ 2026-03-26 13:43     ` Jiayuan Chen
  2026-03-26 14:01       ` Eric Dumazet
  0 siblings, 1 reply; 9+ messages in thread
From: Jiayuan Chen @ 2026-03-26 13:43 UTC (permalink / raw)
  To: Hangbin Liu, Eric Dumazet
  Cc: David S. Miller, David Ahern, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, netdev, linux-kernel, Fei Liu


On 3/26/26 9:13 PM, Hangbin Liu wrote:
> On Thu, Mar 26, 2026 at 05:05:57AM -0700, Eric Dumazet wrote:
>>> diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
>>> index dd26657b6a4a..64de761f40d5 100644
>>> --- a/net/ipv6/ip6_fib.c
>>> +++ b/net/ipv6/ip6_fib.c
>>> @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
>>>          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;
>>>
>>>                  refcount_set(&p->refcnt, 1);
>>> -               f6i->fib6_metrics = p;
>>> +               if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
>>> +                       kfree(p);
>>>          }
>>>
>> The following line should happen before the cmpxchg(),
>>   ->metrics[X] accesses also need READ_ONCE()/WRITE_ONCE() annotations.
> Hi Eric,
>
> Jiayuan also suggested to using READ_ONCE()/WRITE_ONCE() for metrics[X]
> accesses. But I don't get why this line should happen before the cmpxchg(),
> Would you please help explain?


I think what Eric means is something like this:


...
         struct dst_metrics *p = kzalloc_obj(*p, GFP_ATOMIC);

         if (!p)
             return;

         p->metrics[metric - 1] = val;
         refcount_set(&p->refcnt, 1);
         if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
             kfree(p);
         else
             return;
     }
}

m = READ_ONCE(f6i->fib6_metrics);
WRITE_ONCE(m->metrics[metric - 1], val);


Since p is private data before being published via cmpxchg(), we can
safely initialize its metrics beforehand. This way we don't need to
worry about concurrent access to f6i->fib6_metrics->metrics[] during
initialization. Right?



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg
  2026-03-26 13:43     ` Jiayuan Chen
@ 2026-03-26 14:01       ` Eric Dumazet
  0 siblings, 0 replies; 9+ messages in thread
From: Eric Dumazet @ 2026-03-26 14:01 UTC (permalink / raw)
  To: Jiayuan Chen
  Cc: Hangbin Liu, David S. Miller, David Ahern, Jakub Kicinski,
	Paolo Abeni, Simon Horman, David Ahern, netdev, linux-kernel,
	Fei Liu

On Thu, Mar 26, 2026 at 6:44 AM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
>
> On 3/26/26 9:13 PM, Hangbin Liu wrote:
> > On Thu, Mar 26, 2026 at 05:05:57AM -0700, Eric Dumazet wrote:
> >>> diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
> >>> index dd26657b6a4a..64de761f40d5 100644
> >>> --- a/net/ipv6/ip6_fib.c
> >>> +++ b/net/ipv6/ip6_fib.c
> >>> @@ -730,14 +730,16 @@ void fib6_metric_set(struct fib6_info *f6i, int metric, u32 val)
> >>>          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;
> >>>
> >>>                  refcount_set(&p->refcnt, 1);
> >>> -               f6i->fib6_metrics = p;
> >>> +               if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
> >>> +                       kfree(p);
> >>>          }
> >>>
> >> The following line should happen before the cmpxchg(),
> >>   ->metrics[X] accesses also need READ_ONCE()/WRITE_ONCE() annotations.
> > Hi Eric,
> >
> > Jiayuan also suggested to using READ_ONCE()/WRITE_ONCE() for metrics[X]
> > accesses. But I don't get why this line should happen before the cmpxchg(),
> > Would you please help explain?
>
>
> I think what Eric means is something like this:
>
>
> ...
>          struct dst_metrics *p = kzalloc_obj(*p, GFP_ATOMIC);
>
>          if (!p)
>              return;
>
>          p->metrics[metric - 1] = val;
>          refcount_set(&p->refcnt, 1);
>          if (cmpxchg(&f6i->fib6_metrics, dflt, p) != dflt)
>              kfree(p);
>          else
>              return;
>      }
> }
>
> m = READ_ONCE(f6i->fib6_metrics);
> WRITE_ONCE(m->metrics[metric - 1], val);
>
>
> Since p is private data before being published via cmpxchg(), we can
> safely initialize its metrics beforehand. This way we don't need to
> worry about concurrent access to f6i->fib6_metrics->metrics[] during
> initialization. Right?

Yes.  Think about RCU (Read Copy Update) rules.

We allocate an object, and populate it, then make sure changes are
committed, before publishing the new pointer.

Othewise an other cpu could read a 0 metric, while we wanted something else.

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-03-26 14:01 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-26  4:22 [PATCH net] ipv6: fix data race in fib6_metric_set() using cmpxchg Hangbin Liu
2026-03-26  6:23 ` Jiayuan Chen
2026-03-26  6:44   ` Hangbin Liu
2026-03-26  7:13     ` Hangbin Liu
2026-03-26  7:59       ` Jiayuan Chen
2026-03-26 12:05 ` Eric Dumazet
2026-03-26 13:13   ` Hangbin Liu
2026-03-26 13:43     ` Jiayuan Chen
2026-03-26 14:01       ` Eric Dumazet

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox