mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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>
This commit is contained in:
parent
d76beefca7
commit
2e9ff7bb66
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user