ipv6: fix NULL pointer deref in ip6_rt_get_dev_rcu()
Subsystems (3)
Jakub Kicinski
maintainerPATCH 1/1 ipv6: fix NULL pointer deref in ip6_rt_get_dev_rcu()
l3mdev_master_dev_rcu() can return NULL when the slave device is being un-slaved from a VRF. All other callers deal with this, but we lost the fallback to loopback in ip6_rt_pcpu_alloc() -> ip6_rt_get_dev_rcu() with commit 4832c30d5458 ("net: ipv6: put host and anycast routes on device with address").
KASAN: null-ptr-deref in range [0x0000000000000108-0x000000000000010f] RIP: 0010:ip6_rt_pcpu_alloc (net/ipv6/route.c:1418) Call Trace: ip6_pol_route (net/ipv6/route.c:2318) fib6_rule_lookup (net/ipv6/fib6_rules.c:115) ip6_route_output_flags (net/ipv6/route.c:2607) vrf_process_v6_outbound (drivers/net/vrf.c:437)
I was tempted to rework the un-slaving code to clear the flag first and insert synchronize_rcu() before we remove the upper. But looks like the explicit fallback to loopback_dev is an established pattern. And I guess avoiding the synchronize_rcu() is nice, too.
---
now that NIPA is not full of the tc-action.sh crashes this 1 in ~30 runs crash in fcnal_ipv6.sh pops out quite clearly.
--- net/ipv6/route.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
net/ipv6/route.c +2 -1
@@ -1063,7 +1063,8 @@ static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res)*/ if (netif_is_l3_slave (dev) && !rt6_need_strict (&res->f6i->fib6_dst.addr)) - dev =l3mdev_master_dev_rcu (dev); + dev =l3mdev_master_dev_rcu (dev) ? : +dev_net (dev)->loopback_dev;else if (!netif_is_l3_master (dev)) dev =dev_net (dev)->loopback_dev;/* last case is netif_is_l3_master(dev) is true in which
Signature · 1 line
2.53.0
David Ahern
maintainernet/ipv6/route.c static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res) · line 1067
Quoted 11 lines · Jakub Kicinski
net/ipv6/route.c +2
-1@@ -1063,7 +1063,8 @@ static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res)*/ if (netif_is_l3_slave (dev) && !rt6_need_strict (&res->f6i->fib6_dst.addr)) - dev =l3mdev_master_dev_rcu (dev); + dev =l3mdev_master_dev_rcu (dev) ? : +dev_net (dev)->loopback_dev;
how can the flag on the netdev say there is L3 master, yet the device not be there within an rcu window?
Quoted 3 lines
net/ipv6/route.c +0
-0else if (!netif_is_l3_master (dev)) dev =dev_net (dev)->loopback_dev;/* last case is netif_is_l3_master(dev) is true in which
Jakub Kicinski
maintainerQuoted 10 lines · On Sun, 1 Mar 2026 18:38:53 -0700 David Ahern wrote:
net/ipv6/route.c static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res) · line 1067
Quoted 7 lines
net/ipv6/route.c +2
-1@@ -1063,7 +1063,8 @@ static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res)*/ if (netif_is_l3_slave (dev) && !rt6_need_strict (&res->f6i->fib6_dst.addr)) - dev =l3mdev_master_dev_rcu (dev); + dev =l3mdev_master_dev_rcu (dev) ? : +dev_net (dev)->loopback_dev;how can the flag on the netdev say there is L3 master, yet the device not be there within an rcu window?
1) We call netif_is_l3_slave() twice, once here and then again in l3mdev_master_dev_rcu(), the flag may get cleared in between the two.
2)
static int do_vrf_del_slave(struct net_device *dev, struct net_device *port_dev)
{
netdev_upper_dev_unlink(port_dev, dev);
// No sync RCU here, also I'd say the order is inverted?
port_dev->priv_flags &= ~IFF_L3MDEV_SLAVE;Quoted 21 lines · Jakub Kicinski
l3mdev_master_dev_rcu()can return NULL when the slave device is being un-slaved from a VRF. All other callers deal with this, but we lost the fallback to loopback inip6_rt_pcpu_alloc()->ip6_rt_get_dev_rcu()with commit4832c30d5458("net: ipv6: put host and anycast routes on device with address").KASAN: null-ptr-deref in range [0x0000000000000108-0x000000000000010f] RIP: 0010:ip6_rt_pcpu_alloc (net/ipv6/route.c:1418) Call Trace: ip6_pol_route (net/ipv6/route.c:2318) fib6_rule_lookup (net/ipv6/fib6_rules.c:115) ip6_route_output_flags (net/ipv6/route.c:2607) vrf_process_v6_outbound (drivers/net/vrf.c:437)I was tempted to rework the un-slaving code to clear the flag first and insert
synchronize_rcu()before we remove the upper. But looks like the explicit fallback toloopback_devis an established pattern. And I guess avoiding thesynchronize_rcu()is nice, too.Fixes 4832c30d5458 ("net: ipv6: put host and anycast routes on device with address")Signed-off-by Jakub Kicinski <[email protected]>
Quoted 9 lines
---
now that NIPA is not full of thetc-action.shcrashes this 1 in ~30 runs crash infcnal_ipv6.shpops out quite clearly.--- net/ipv6/route.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
This patch was applied to netdev/net.git (main) by Jakub Kicinski <[email protected]>:
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <[email protected]>:
Quoted 14 lines · On Sun, 1 Mar 2026 11:45:48 -0800 you wrote:
l3mdev_master_dev_rcu()can return NULL when the slave device is being un-slaved from a VRF. All other callers deal with this, but we lost the fallback to loopback inip6_rt_pcpu_alloc()->ip6_rt_get_dev_rcu()with commit4832c30d5458("net: ipv6: put host and anycast routes on device with address").KASAN: null-ptr-deref in range [0x0000000000000108-0x000000000000010f] RIP: 0010:ip6_rt_pcpu_alloc (net/ipv6/route.c:1418) Call Trace: ip6_pol_route (net/ipv6/route.c:2318) fib6_rule_lookup (net/ipv6/fib6_rules.c:115) ip6_route_output_flags (net/ipv6/route.c:2607) vrf_process_v6_outbound (drivers/net/vrf.c:437)
Quoted 1 line
[...]
Here is the summary with links:
- [net] ipv6: fix NULL pointer deref in ip6_rt_get_dev_rcu()
https://git.kernel.org/netdev/net/c/2ffb4f5c2ccb
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html