diff options
| author | Jinjie Ruan <ruanjinjie@huawei.com> | 2026-09-02 15:33:33 +0800 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-09-05 12:43:12 -0700 |
| commit | 2e9ff7bb668b6f62f4789718f70fd1ec2eb87c12 (patch) | |
| tree | cc56c43e84dd117321e1abd0c99f17fa8d15fd2f | |
| parent | d76beefca7e52ef2fd7a4a504e862d85be60072a (diff) | |
| download | linux-next-2e9ff7bb668b6f62f4789718f70fd1ec2eb87c12.tar.gz linux-next-2e9ff7bb668b6f62f4789718f70fd1ec2eb87c12.zip | |
8021q: Fix data race when publishing vlan net_device pointers
A plain C read and assignment of the net_device pointer
in the vlan_devices_arrays leaf entries lack proper atomicity
and ordering barriers. A concurrent lockless reader on the packet
receive fast-path could observe a torn or partially initialized
net_device pointer, leading to a potential out-of-bounds read or kernel
panic.
The data race occurs between the netlink/ioctl configuration paths
(holding the per-netns rtnl_nets_lock or RTNL lock) and the softirq
receive fast-path (holding rcu_read_lock()):
CPU 0 (Writer, rtnl_nets_lock/RTNL) CPU 1 (Reader, rcu_read_lock())
----------------------------------- -------------------------------
rtnetlink_rcv_msg()
// RTM_NEWLINK handler with RTNL_FLAG_DOIT_PERNET
rtnl_newlink()
ops->newlink() == vlan_newlink()
OR
vlan_ioctl_handler()
[ADD_VLAN_CMD] -> register_vlan_device()
register_vlan_dev()
vlan_group_set_device()
netif_receive_skb_core()
vlan_do_receive()
vlan_find_dev()
__vlan_group_get_device()
// Speculative / torn read
[Loads bad net_device *]
[Plain C store]
array[vlan_id] = dev;
// Dereferences bad pointer
// during device status check
vlan_dev->flags (PANIC!)
Fix this by using rcu_assign_pointer() in vlan_group_set_device()
and rcu_dereference_raw() in __vlan_group_get_device() to enforce
proper ordering and memory atomicity for the leaf entry traversal.
Link: https://sashiko.dev/#/patchset/20260825095422.3166067-1-ruanjinjie%40huawei.com
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | net/8021q/vlan.c | 2 | ||||
| -rw-r--r-- | net/8021q/vlan.h | 12 |
2 files changed, 7 insertions, 7 deletions
diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c index 2d2efb877975..6df80cad40a2 100644 --- a/net/8021q/vlan.c +++ b/net/8021q/vlan.c @@ -49,7 +49,7 @@ const char vlan_version[] = DRV_VERSION; static int vlan_group_prealloc_vid(struct vlan_group *vg, __be16 vlan_proto, u16 vlan_id) { - struct net_device **array; + struct net_device __rcu **array; unsigned int vidx; unsigned int size; int pidx; diff --git a/net/8021q/vlan.h b/net/8021q/vlan.h index c41caaf94095..3cb4c8294130 100644 --- a/net/8021q/vlan.h +++ b/net/8021q/vlan.h @@ -22,8 +22,8 @@ enum vlan_protos { struct vlan_group { unsigned int nr_vlan_devs; struct hlist_node hlist; /* linked list */ - struct net_device **vlan_devices_arrays[VLAN_PROTO_NUM] - [VLAN_GROUP_ARRAY_SPLIT_PARTS]; + struct net_device __rcu **vlan_devices_arrays[VLAN_PROTO_NUM] + [VLAN_GROUP_ARRAY_SPLIT_PARTS]; }; struct vlan_info { @@ -54,7 +54,7 @@ static inline struct net_device *__vlan_group_get_device(struct vlan_group *vg, unsigned int pidx, u16 vlan_id) { - struct net_device **array; + struct net_device __rcu **array; array = vg->vlan_devices_arrays[pidx] [vlan_id / VLAN_GROUP_ARRAY_PART_LEN]; @@ -62,7 +62,7 @@ static inline struct net_device *__vlan_group_get_device(struct vlan_group *vg, /* paired with smp_wmb() in vlan_group_prealloc_vid() */ smp_rmb(); - return array ? array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] : NULL; + return array ? rcu_dereference_raw(array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN]) : NULL; } static inline struct net_device *vlan_group_get_device(struct vlan_group *vg, @@ -82,13 +82,13 @@ static inline void vlan_group_set_device(struct vlan_group *vg, struct net_device *dev) { int pidx = vlan_proto_idx(vlan_proto); - struct net_device **array; + struct net_device __rcu **array; if (!vg || pidx < 0) return; array = vg->vlan_devices_arrays[pidx] [vlan_id / VLAN_GROUP_ARRAY_PART_LEN]; - array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] = dev; + rcu_assign_pointer(array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN], dev); } /* Must be invoked with rcu_read_lock or with RTNL. */ |
