]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
vrf: Fix a potential NPD when removing a port from a VRF
authorIdo Schimmel <idosch@nvidia.com>
Thu, 23 Apr 2026 06:36:07 +0000 (09:36 +0300)
committerJakub Kicinski <kuba@kernel.org>
Tue, 28 Apr 2026 00:43:22 +0000 (17:43 -0700)
RCU readers that identified a net device as a VRF port using
netif_is_l3_slave() assume that a subsequent call to
netdev_master_upper_dev_get_rcu() will return a VRF device. They then
continue to dereference its l3mdev operations.

This assumption is not always correct and can result in a NPD [1]. There
is no RCU synchronization when removing a port from a VRF, so it is
possible for an RCU reader to see a new master device (e.g., a bridge)
that does not have l3mdev operations.

Fix by adding RCU synchronization after clearing the IFF_L3MDEV_SLAVE
flag. Skip this synchronization when a net device is removed from a VRF
as part of its deletion and when the VRF device itself is deleted. In
the latter case an RCU grace period will pass by the time RTNL is
released.

[1]
BUG: kernel NULL pointer dereference, address: 0000000000000000
[...]
RIP: 0010:l3mdev_fib_table_rcu (net/l3mdev/l3mdev.c:181)
[...]
Call Trace:
<TASK>
l3mdev_fib_table_by_index (net/l3mdev/l3mdev.c:201 net/l3mdev/l3mdev.c:189)
__inet_bind (net/ipv4/af_inet.c:499 (discriminator 3))
inet_bind_sk (net/ipv4/af_inet.c:469)
__sys_bind (./include/linux/file.h:62 (discriminator 1) ./include/linux/file.h:83 (discriminator 1) net/socket.c:1951 (discriminator 1))
__x64_sys_bind (net/socket.c:1969 (discriminator 1) net/socket.c:1967 (discriminator 1) net/socket.c:1967 (discriminator 1))
do_syscall_64 (arch/x86/entry/syscall_64.c:63 (discriminator 1) arch/x86/entry/syscall_64.c:94 (discriminator 1))
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)

Fixes: fdeea7be88b1 ("net: vrf: Set slave's private flag before linking")
Reported-by: Haoze Xie <royenheart@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Yuan Tan <yuantan098@gmail.com>
Closes: https://lore.kernel.org/netdev/20260419145332.3988923-1-n05ec@lzu.edu.cn/
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Link: https://patch.msgid.link/20260423063607.1208202-1-idosch@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/vrf.c

index 2cf2dbd1c12fc9406082ff47d299e0e0dad9f210..46209917ae4d69e5e6d559a32db302b7bd7fc11a 100644 (file)
@@ -1034,6 +1034,7 @@ static int do_vrf_add_slave(struct net_device *dev, struct net_device *port_dev,
 
 err:
        port_dev->priv_flags &= ~IFF_L3MDEV_SLAVE;
+       synchronize_net();
        return ret;
 }
 
@@ -1053,10 +1054,16 @@ static int vrf_add_slave(struct net_device *dev, struct net_device *port_dev,
 }
 
 /* inverse of do_vrf_add_slave */
-static int do_vrf_del_slave(struct net_device *dev, struct net_device *port_dev)
+static int do_vrf_del_slave(struct net_device *dev, struct net_device *port_dev,
+                           bool needs_sync)
 {
        netdev_upper_dev_unlink(port_dev, dev);
        port_dev->priv_flags &= ~IFF_L3MDEV_SLAVE;
+       /* Make sure that concurrent RCU readers that identified the device
+        * as a VRF port see a VRF master or no master at all.
+        */
+       if (needs_sync)
+               synchronize_net();
 
        cycle_netdev(port_dev, NULL);
 
@@ -1065,7 +1072,7 @@ static int do_vrf_del_slave(struct net_device *dev, struct net_device *port_dev)
 
 static int vrf_del_slave(struct net_device *dev, struct net_device *port_dev)
 {
-       return do_vrf_del_slave(dev, port_dev);
+       return do_vrf_del_slave(dev, port_dev, true);
 }
 
 static void vrf_dev_uninit(struct net_device *dev)
@@ -1619,7 +1626,7 @@ static void vrf_dellink(struct net_device *dev, struct list_head *head)
        struct list_head *iter;
 
        netdev_for_each_lower_dev(dev, port_dev, iter)
-               vrf_del_slave(dev, port_dev);
+               do_vrf_del_slave(dev, port_dev, false);
 
        vrf_map_unregister_dev(dev);
 
@@ -1751,7 +1758,7 @@ static int vrf_device_event(struct notifier_block *unused,
                        goto out;
 
                vrf_dev = netdev_master_upper_dev_get(dev);
-               vrf_del_slave(vrf_dev, dev);
+               do_vrf_del_slave(vrf_dev, dev, false);
        }
 out:
        return NOTIFY_DONE;