mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
vlan_device_event() generates nested UP/DOWN, MTU and feature
change events. It executes an event for the VLAN device directly
from the notifier - while the locks of the lower device are held.
This causes deadlocks, for example:
bond (3) bond_update_speed_duplex(vlan)
| ^ v
vlan (2) UP(vlan) (4) vlan_ethtool_get_link_ksettings()
| ^ v
dummy (1) UP(dummy) (5) __ethtool_get_link_ksettings()
The dummy device is ops locked, vlan creates a nested event (2),
then bond wants to ask vlan for link state (3). bond uses the
"I'm already holding the instance lock" flavor of API. But in
this case the lock held refers to vlan itself. We hit vlan's
link settings trampoline (4) and call __ethtool_get_link_ksettings()
which tries to lock dummy. Deadlock. There's no clean way for us
to tell the vlan_ethtool_get_link_ksettings() that the caller
is already in lower device's critical section.
Defer the propagation to the per-netdev work facility instead:
the notifier only schedules netdev_work_sched(vlandev, VLAN_WORK_*),
and ndo_work (vlan_dev_work) applies the change later. Hopefully
nobody expects the VLAN state changes to be instantaneous.
If someone does expect the changes to be instantaneous we will
have to do the same thing Stan did for rx_mode and "strategically"
place sync calls, to make sure such delayed works are executed
after we drop the ops lock but before we drop rtnl_lock.
Stan suggests that if we need that down the line we may
consider reshaping the mechanism into "async notifications".
AFAICT only vlan does this sort of netdev open chaining,
so as a first try I think that sticking the complexity into
the vlan code makes sense.
One corner case is that we need to cancel the event if user
explicitly changes the state before work could run. Consider
the following operations with vlan0 on top of dummy0:
ip link set dev dummy0 up # queues work to up vlan0
ip link set dev vlan0 down # user explicitly downs the vlan
ndo_work # acts on the stale event
Reported-by: syzbot+09da62a8b78959ceb8bb@syzkaller.appspotmail.com
Reported-by: syzbot+cb67c392b0b8f0fd0fc1@syzkaller.appspotmail.com
Reported-by: syzbot+9bb8bd77f3966641f298@syzkaller.appspotmail.com
Fixes: 9f275c2e90 ("net: ethtool: make sure __ethtool_get_link_ksettings() is ops-locked")
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
Link: https://patch.msgid.link/20260624182018.2445732-4-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
219 lines
6.8 KiB
C
219 lines
6.8 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef __BEN_VLAN_802_1Q_INC__
|
|
#define __BEN_VLAN_802_1Q_INC__
|
|
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/u64_stats_sync.h>
|
|
#include <linux/list.h>
|
|
|
|
/* if this changes, algorithm will have to be reworked because this
|
|
* depends on completely exhausting the VLAN identifier space. Thus
|
|
* it gives constant time look-up, but in many cases it wastes memory.
|
|
*/
|
|
#define VLAN_GROUP_ARRAY_SPLIT_PARTS 8
|
|
#define VLAN_GROUP_ARRAY_PART_LEN (VLAN_N_VID/VLAN_GROUP_ARRAY_SPLIT_PARTS)
|
|
|
|
enum vlan_protos {
|
|
VLAN_PROTO_8021Q = 0,
|
|
VLAN_PROTO_8021AD,
|
|
VLAN_PROTO_NUM,
|
|
};
|
|
|
|
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 vlan_info {
|
|
struct net_device *real_dev; /* The ethernet(like) device
|
|
* the vlan is attached to.
|
|
*/
|
|
struct vlan_group grp;
|
|
struct list_head vid_list;
|
|
unsigned int nr_vids;
|
|
bool auto_vid0;
|
|
struct rcu_head rcu;
|
|
};
|
|
|
|
static inline int vlan_proto_idx(__be16 proto)
|
|
{
|
|
switch (proto) {
|
|
case htons(ETH_P_8021Q):
|
|
return VLAN_PROTO_8021Q;
|
|
case htons(ETH_P_8021AD):
|
|
return VLAN_PROTO_8021AD;
|
|
default:
|
|
WARN(1, "invalid VLAN protocol: 0x%04x\n", ntohs(proto));
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static inline struct net_device *__vlan_group_get_device(struct vlan_group *vg,
|
|
unsigned int pidx,
|
|
u16 vlan_id)
|
|
{
|
|
struct net_device **array;
|
|
|
|
array = vg->vlan_devices_arrays[pidx]
|
|
[vlan_id / VLAN_GROUP_ARRAY_PART_LEN];
|
|
|
|
/* paired with smp_wmb() in vlan_group_prealloc_vid() */
|
|
smp_rmb();
|
|
|
|
return array ? array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] : NULL;
|
|
}
|
|
|
|
static inline struct net_device *vlan_group_get_device(struct vlan_group *vg,
|
|
__be16 vlan_proto,
|
|
u16 vlan_id)
|
|
{
|
|
int pidx = vlan_proto_idx(vlan_proto);
|
|
|
|
if (pidx < 0)
|
|
return NULL;
|
|
|
|
return __vlan_group_get_device(vg, pidx, vlan_id);
|
|
}
|
|
|
|
static inline void vlan_group_set_device(struct vlan_group *vg,
|
|
__be16 vlan_proto, u16 vlan_id,
|
|
struct net_device *dev)
|
|
{
|
|
int pidx = vlan_proto_idx(vlan_proto);
|
|
struct net_device **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;
|
|
}
|
|
|
|
/* Must be invoked with rcu_read_lock or with RTNL. */
|
|
static inline struct net_device *vlan_find_dev(struct net_device *real_dev,
|
|
__be16 vlan_proto, u16 vlan_id)
|
|
{
|
|
struct vlan_info *vlan_info = rcu_dereference_rtnl(real_dev->vlan_info);
|
|
|
|
if (vlan_info)
|
|
return vlan_group_get_device(&vlan_info->grp,
|
|
vlan_proto, vlan_id);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline netdev_features_t vlan_tnl_features(struct net_device *real_dev)
|
|
{
|
|
netdev_features_t ret;
|
|
|
|
ret = real_dev->hw_enc_features &
|
|
(NETIF_F_CSUM_MASK | NETIF_F_GSO_SOFTWARE |
|
|
NETIF_F_GSO_ENCAP_ALL);
|
|
|
|
if ((ret & NETIF_F_GSO_ENCAP_ALL) && (ret & NETIF_F_CSUM_MASK))
|
|
return (ret & ~NETIF_F_CSUM_MASK) | NETIF_F_HW_CSUM;
|
|
return 0;
|
|
}
|
|
|
|
#define vlan_group_for_each_dev(grp, i, dev) \
|
|
for ((i) = 0; i < VLAN_PROTO_NUM * VLAN_N_VID; i++) \
|
|
if (((dev) = __vlan_group_get_device((grp), (i) / VLAN_N_VID, \
|
|
(i) % VLAN_N_VID)))
|
|
|
|
int vlan_filter_push_vids(struct vlan_info *vlan_info, __be16 proto);
|
|
void vlan_filter_drop_vids(struct vlan_info *vlan_info, __be16 proto);
|
|
|
|
/* netdev_work events propagated from the real device, see vlan_dev_work(). */
|
|
enum {
|
|
VLAN_WORK_LINK_STATE = BIT(0), /* sync up/down with real_dev */
|
|
VLAN_WORK_MTU = BIT(1), /* clamp mtu to real_dev's */
|
|
VLAN_WORK_FEATURES = BIT(2), /* re-inherit real_dev features */
|
|
};
|
|
|
|
void vlan_stacked_transfer_operstate(const struct net_device *rootdev,
|
|
struct net_device *dev,
|
|
struct vlan_dev_priv *vlan);
|
|
|
|
/* found in vlan_dev.c */
|
|
void vlan_dev_set_ingress_priority(const struct net_device *dev,
|
|
u32 skb_prio, u16 vlan_prio);
|
|
int vlan_dev_set_egress_priority(const struct net_device *dev,
|
|
u32 skb_prio, u16 vlan_prio);
|
|
void vlan_dev_free_egress_priority(const struct net_device *dev);
|
|
int vlan_dev_change_flags(const struct net_device *dev, u32 flag, u32 mask);
|
|
void vlan_dev_get_realdev_name(const struct net_device *dev, char *result,
|
|
size_t size);
|
|
|
|
int vlan_check_real_dev(struct net_device *real_dev,
|
|
__be16 protocol, u16 vlan_id,
|
|
struct netlink_ext_ack *extack);
|
|
void vlan_setup(struct net_device *dev);
|
|
int register_vlan_dev(struct net_device *dev, struct netlink_ext_ack *extack);
|
|
void unregister_vlan_dev(struct net_device *dev, struct list_head *head);
|
|
bool vlan_dev_inherit_address(struct net_device *dev,
|
|
struct net_device *real_dev);
|
|
|
|
static inline u32 vlan_get_ingress_priority(struct net_device *dev,
|
|
u16 vlan_tci)
|
|
{
|
|
struct vlan_dev_priv *vip = vlan_dev_priv(dev);
|
|
|
|
return vip->ingress_priority_map[(vlan_tci >> VLAN_PRIO_SHIFT) & 0x7];
|
|
}
|
|
|
|
#ifdef CONFIG_VLAN_8021Q_GVRP
|
|
int vlan_gvrp_request_join(const struct net_device *dev);
|
|
void vlan_gvrp_request_leave(const struct net_device *dev);
|
|
int vlan_gvrp_init_applicant(struct net_device *dev);
|
|
void vlan_gvrp_uninit_applicant(struct net_device *dev);
|
|
int vlan_gvrp_init(void);
|
|
void vlan_gvrp_uninit(void);
|
|
#else
|
|
static inline int vlan_gvrp_request_join(const struct net_device *dev) { return 0; }
|
|
static inline void vlan_gvrp_request_leave(const struct net_device *dev) {}
|
|
static inline int vlan_gvrp_init_applicant(struct net_device *dev) { return 0; }
|
|
static inline void vlan_gvrp_uninit_applicant(struct net_device *dev) {}
|
|
static inline int vlan_gvrp_init(void) { return 0; }
|
|
static inline void vlan_gvrp_uninit(void) {}
|
|
#endif
|
|
|
|
#ifdef CONFIG_VLAN_8021Q_MVRP
|
|
int vlan_mvrp_request_join(const struct net_device *dev);
|
|
void vlan_mvrp_request_leave(const struct net_device *dev);
|
|
int vlan_mvrp_init_applicant(struct net_device *dev);
|
|
void vlan_mvrp_uninit_applicant(struct net_device *dev);
|
|
int vlan_mvrp_init(void);
|
|
void vlan_mvrp_uninit(void);
|
|
#else
|
|
static inline int vlan_mvrp_request_join(const struct net_device *dev) { return 0; }
|
|
static inline void vlan_mvrp_request_leave(const struct net_device *dev) {}
|
|
static inline int vlan_mvrp_init_applicant(struct net_device *dev) { return 0; }
|
|
static inline void vlan_mvrp_uninit_applicant(struct net_device *dev) {}
|
|
static inline int vlan_mvrp_init(void) { return 0; }
|
|
static inline void vlan_mvrp_uninit(void) {}
|
|
#endif
|
|
|
|
extern const char vlan_fullname[];
|
|
extern const char vlan_version[];
|
|
int vlan_netlink_init(void);
|
|
void vlan_netlink_fini(void);
|
|
|
|
extern struct rtnl_link_ops vlan_link_ops;
|
|
|
|
extern unsigned int vlan_net_id;
|
|
|
|
struct proc_dir_entry;
|
|
|
|
struct vlan_net {
|
|
/* /proc/net/vlan */
|
|
struct proc_dir_entry *proc_vlan_dir;
|
|
/* /proc/net/vlan/config */
|
|
struct proc_dir_entry *proc_vlan_conf;
|
|
/* Determines interface naming scheme. */
|
|
unsigned short name_type;
|
|
};
|
|
|
|
#endif /* !(__BEN_VLAN_802_1Q_INC__) */
|