mirror of
https://gitee.com/bianbu-linux/linux-6.6
synced 2025-04-24 14:07:52 -04:00
[ Upstream commit 36c92936e868601fa1f43da6758cf55805043509 ] Pass the already obtained vlan group pointer to br_mst_vlan_set_state() instead of dereferencing it again. Each caller has already correctly dereferenced it for their context. This change is required for the following suspicious RCU dereference fix. No functional changes intended. Fixes: 3a7c1661ae13 ("net: bridge: mst: fix vlan use-after-free") Reported-by: syzbot+9bbe2de1bc9d470eb5fe@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9bbe2de1bc9d470eb5fe Signed-off-by: Nikolay Aleksandrov <razor@blackwall.org> Link: https://lore.kernel.org/r/20240609103654.914987-2-razor@blackwall.org Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
360 lines
7.6 KiB
C
360 lines
7.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Bridge Multiple Spanning Tree Support
|
|
*
|
|
* Authors:
|
|
* Tobias Waldekranz <tobias@waldekranz.com>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <net/switchdev.h>
|
|
|
|
#include "br_private.h"
|
|
|
|
DEFINE_STATIC_KEY_FALSE(br_mst_used);
|
|
|
|
bool br_mst_enabled(const struct net_device *dev)
|
|
{
|
|
if (!netif_is_bridge_master(dev))
|
|
return false;
|
|
|
|
return br_opt_get(netdev_priv(dev), BROPT_MST_ENABLED);
|
|
}
|
|
EXPORT_SYMBOL_GPL(br_mst_enabled);
|
|
|
|
int br_mst_get_info(const struct net_device *dev, u16 msti, unsigned long *vids)
|
|
{
|
|
const struct net_bridge_vlan_group *vg;
|
|
const struct net_bridge_vlan *v;
|
|
const struct net_bridge *br;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
if (!netif_is_bridge_master(dev))
|
|
return -EINVAL;
|
|
|
|
br = netdev_priv(dev);
|
|
if (!br_opt_get(br, BROPT_MST_ENABLED))
|
|
return -EINVAL;
|
|
|
|
vg = br_vlan_group(br);
|
|
|
|
list_for_each_entry(v, &vg->vlan_list, vlist) {
|
|
if (v->msti == msti)
|
|
__set_bit(v->vid, vids);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(br_mst_get_info);
|
|
|
|
int br_mst_get_state(const struct net_device *dev, u16 msti, u8 *state)
|
|
{
|
|
const struct net_bridge_port *p = NULL;
|
|
const struct net_bridge_vlan_group *vg;
|
|
const struct net_bridge_vlan *v;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
p = br_port_get_check_rtnl(dev);
|
|
if (!p || !br_opt_get(p->br, BROPT_MST_ENABLED))
|
|
return -EINVAL;
|
|
|
|
vg = nbp_vlan_group(p);
|
|
|
|
list_for_each_entry(v, &vg->vlan_list, vlist) {
|
|
if (v->brvlan->msti == msti) {
|
|
*state = v->state;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
EXPORT_SYMBOL_GPL(br_mst_get_state);
|
|
|
|
static void br_mst_vlan_set_state(struct net_bridge_vlan_group *vg,
|
|
struct net_bridge_vlan *v,
|
|
u8 state)
|
|
{
|
|
if (br_vlan_get_state(v) == state)
|
|
return;
|
|
|
|
br_vlan_set_state(v, state);
|
|
|
|
if (v->vid == vg->pvid)
|
|
br_vlan_set_pvid_state(vg, state);
|
|
}
|
|
|
|
int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct switchdev_attr attr = {
|
|
.id = SWITCHDEV_ATTR_ID_PORT_MST_STATE,
|
|
.orig_dev = p->dev,
|
|
.u.mst_state = {
|
|
.msti = msti,
|
|
.state = state,
|
|
},
|
|
};
|
|
struct net_bridge_vlan_group *vg;
|
|
struct net_bridge_vlan *v;
|
|
int err = 0;
|
|
|
|
rcu_read_lock();
|
|
vg = nbp_vlan_group(p);
|
|
if (!vg)
|
|
goto out;
|
|
|
|
/* MSTI 0 (CST) state changes are notified via the regular
|
|
* SWITCHDEV_ATTR_ID_PORT_STP_STATE.
|
|
*/
|
|
if (msti) {
|
|
err = switchdev_port_attr_set(p->dev, &attr, extack);
|
|
if (err && err != -EOPNOTSUPP)
|
|
goto out;
|
|
}
|
|
|
|
err = 0;
|
|
list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
|
|
if (v->brvlan->msti != msti)
|
|
continue;
|
|
|
|
br_mst_vlan_set_state(vg, v, state);
|
|
}
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return err;
|
|
}
|
|
|
|
static void br_mst_vlan_sync_state(struct net_bridge_vlan *pv, u16 msti)
|
|
{
|
|
struct net_bridge_vlan_group *vg = nbp_vlan_group(pv->port);
|
|
struct net_bridge_vlan *v;
|
|
|
|
list_for_each_entry(v, &vg->vlan_list, vlist) {
|
|
/* If this port already has a defined state in this
|
|
* MSTI (through some other VLAN membership), inherit
|
|
* it.
|
|
*/
|
|
if (v != pv && v->brvlan->msti == msti) {
|
|
br_mst_vlan_set_state(vg, pv, v->state);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Otherwise, start out in a new MSTI with all ports disabled. */
|
|
return br_mst_vlan_set_state(vg, pv, BR_STATE_DISABLED);
|
|
}
|
|
|
|
int br_mst_vlan_set_msti(struct net_bridge_vlan *mv, u16 msti)
|
|
{
|
|
struct switchdev_attr attr = {
|
|
.id = SWITCHDEV_ATTR_ID_VLAN_MSTI,
|
|
.orig_dev = mv->br->dev,
|
|
.u.vlan_msti = {
|
|
.vid = mv->vid,
|
|
.msti = msti,
|
|
},
|
|
};
|
|
struct net_bridge_vlan_group *vg;
|
|
struct net_bridge_vlan *pv;
|
|
struct net_bridge_port *p;
|
|
int err;
|
|
|
|
if (mv->msti == msti)
|
|
return 0;
|
|
|
|
err = switchdev_port_attr_set(mv->br->dev, &attr, NULL);
|
|
if (err && err != -EOPNOTSUPP)
|
|
return err;
|
|
|
|
mv->msti = msti;
|
|
|
|
list_for_each_entry(p, &mv->br->port_list, list) {
|
|
vg = nbp_vlan_group(p);
|
|
|
|
pv = br_vlan_find(vg, mv->vid);
|
|
if (pv)
|
|
br_mst_vlan_sync_state(pv, msti);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void br_mst_vlan_init_state(struct net_bridge_vlan *v)
|
|
{
|
|
/* VLANs always start out in MSTI 0 (CST) */
|
|
v->msti = 0;
|
|
|
|
if (br_vlan_is_master(v))
|
|
v->state = BR_STATE_FORWARDING;
|
|
else
|
|
v->state = v->port->state;
|
|
}
|
|
|
|
int br_mst_set_enabled(struct net_bridge *br, bool on,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct switchdev_attr attr = {
|
|
.id = SWITCHDEV_ATTR_ID_BRIDGE_MST,
|
|
.orig_dev = br->dev,
|
|
.u.mst = on,
|
|
};
|
|
struct net_bridge_vlan_group *vg;
|
|
struct net_bridge_port *p;
|
|
int err;
|
|
|
|
list_for_each_entry(p, &br->port_list, list) {
|
|
vg = nbp_vlan_group(p);
|
|
|
|
if (!vg->num_vlans)
|
|
continue;
|
|
|
|
NL_SET_ERR_MSG(extack,
|
|
"MST mode can't be changed while VLANs exist");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (br_opt_get(br, BROPT_MST_ENABLED) == on)
|
|
return 0;
|
|
|
|
err = switchdev_port_attr_set(br->dev, &attr, extack);
|
|
if (err && err != -EOPNOTSUPP)
|
|
return err;
|
|
|
|
if (on)
|
|
static_branch_enable(&br_mst_used);
|
|
else
|
|
static_branch_disable(&br_mst_used);
|
|
|
|
br_opt_toggle(br, BROPT_MST_ENABLED, on);
|
|
return 0;
|
|
}
|
|
|
|
size_t br_mst_info_size(const struct net_bridge_vlan_group *vg)
|
|
{
|
|
DECLARE_BITMAP(seen, VLAN_N_VID) = { 0 };
|
|
const struct net_bridge_vlan *v;
|
|
size_t sz;
|
|
|
|
/* IFLA_BRIDGE_MST */
|
|
sz = nla_total_size(0);
|
|
|
|
list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
|
|
if (test_bit(v->brvlan->msti, seen))
|
|
continue;
|
|
|
|
/* IFLA_BRIDGE_MST_ENTRY */
|
|
sz += nla_total_size(0) +
|
|
/* IFLA_BRIDGE_MST_ENTRY_MSTI */
|
|
nla_total_size(sizeof(u16)) +
|
|
/* IFLA_BRIDGE_MST_ENTRY_STATE */
|
|
nla_total_size(sizeof(u8));
|
|
|
|
__set_bit(v->brvlan->msti, seen);
|
|
}
|
|
|
|
return sz;
|
|
}
|
|
|
|
int br_mst_fill_info(struct sk_buff *skb,
|
|
const struct net_bridge_vlan_group *vg)
|
|
{
|
|
DECLARE_BITMAP(seen, VLAN_N_VID) = { 0 };
|
|
const struct net_bridge_vlan *v;
|
|
struct nlattr *nest;
|
|
int err = 0;
|
|
|
|
list_for_each_entry(v, &vg->vlan_list, vlist) {
|
|
if (test_bit(v->brvlan->msti, seen))
|
|
continue;
|
|
|
|
nest = nla_nest_start_noflag(skb, IFLA_BRIDGE_MST_ENTRY);
|
|
if (!nest ||
|
|
nla_put_u16(skb, IFLA_BRIDGE_MST_ENTRY_MSTI, v->brvlan->msti) ||
|
|
nla_put_u8(skb, IFLA_BRIDGE_MST_ENTRY_STATE, v->state)) {
|
|
err = -EMSGSIZE;
|
|
break;
|
|
}
|
|
nla_nest_end(skb, nest);
|
|
|
|
__set_bit(v->brvlan->msti, seen);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct nla_policy br_mst_nl_policy[IFLA_BRIDGE_MST_ENTRY_MAX + 1] = {
|
|
[IFLA_BRIDGE_MST_ENTRY_MSTI] = NLA_POLICY_RANGE(NLA_U16,
|
|
1, /* 0 reserved for CST */
|
|
VLAN_N_VID - 1),
|
|
[IFLA_BRIDGE_MST_ENTRY_STATE] = NLA_POLICY_RANGE(NLA_U8,
|
|
BR_STATE_DISABLED,
|
|
BR_STATE_BLOCKING),
|
|
};
|
|
|
|
static int br_mst_process_one(struct net_bridge_port *p,
|
|
const struct nlattr *attr,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct nlattr *tb[IFLA_BRIDGE_MST_ENTRY_MAX + 1];
|
|
u16 msti;
|
|
u8 state;
|
|
int err;
|
|
|
|
err = nla_parse_nested(tb, IFLA_BRIDGE_MST_ENTRY_MAX, attr,
|
|
br_mst_nl_policy, extack);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!tb[IFLA_BRIDGE_MST_ENTRY_MSTI]) {
|
|
NL_SET_ERR_MSG_MOD(extack, "MSTI not specified");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!tb[IFLA_BRIDGE_MST_ENTRY_STATE]) {
|
|
NL_SET_ERR_MSG_MOD(extack, "State not specified");
|
|
return -EINVAL;
|
|
}
|
|
|
|
msti = nla_get_u16(tb[IFLA_BRIDGE_MST_ENTRY_MSTI]);
|
|
state = nla_get_u8(tb[IFLA_BRIDGE_MST_ENTRY_STATE]);
|
|
|
|
return br_mst_set_state(p, msti, state, extack);
|
|
}
|
|
|
|
int br_mst_process(struct net_bridge_port *p, const struct nlattr *mst_attr,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct nlattr *attr;
|
|
int err, msts = 0;
|
|
int rem;
|
|
|
|
if (!br_opt_get(p->br, BROPT_MST_ENABLED)) {
|
|
NL_SET_ERR_MSG_MOD(extack, "Can't modify MST state when MST is disabled");
|
|
return -EBUSY;
|
|
}
|
|
|
|
nla_for_each_nested(attr, mst_attr, rem) {
|
|
switch (nla_type(attr)) {
|
|
case IFLA_BRIDGE_MST_ENTRY:
|
|
err = br_mst_process_one(p, attr, extack);
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
msts++;
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
if (!msts) {
|
|
NL_SET_ERR_MSG_MOD(extack, "Found no MST entries to process");
|
|
err = -EINVAL;
|
|
}
|
|
|
|
return err;
|
|
}
|