rtnetlink: fix double call of rtnl_link_get_net_ifla()
Currently rtnl_link_get_net_ifla() gets called twice when we create peer devices, once in rtnl_add_peer_net() and once in each ->newlink() implementation. This looks safer, however, it leads to a classic Time-of-Check to Time-of-Use (TOCTOU) bug since IFLA_NET_NS_PID is very dynamic. And because of the lack of checking error pointer of the second call, it also leads to a kernel crash as reported by syzbot. Fix this by getting rid of the second call, which already becomes redudant after Kuniyuki's work. We have to propagate the result of the first rtnl_link_get_net_ifla() down to each ->newlink(). Reported-by: syzbot+21ba4d5adff0b6a7cfc6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=21ba4d5adff0b6a7cfc6 Fixes:0eb87b02a7
("veth: Set VETH_INFO_PEER to veth_link_ops.peer_type.") Fixes:6b84e558e9
("vxcan: Set VXCAN_INFO_PEER to vxcan_link_ops.peer_type.") Fixes:fefd5d0821
("netkit: Set IFLA_NETKIT_PEER_INFO to netkit_link_ops.peer_type.") Cc: Kuniyuki Iwashima <kuniyu@amazon.com> Signed-off-by: Cong Wang <cong.wang@bytedance.com> Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com> Link: https://patch.msgid.link/20241129212519.825567-1-xiyou.wangcong@gmail.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
7a0ea70da5
commit
4832756676
4 changed files with 31 additions and 46 deletions
|
@ -172,13 +172,12 @@ static void vxcan_setup(struct net_device *dev)
|
||||||
/* forward declaration for rtnl_create_link() */
|
/* forward declaration for rtnl_create_link() */
|
||||||
static struct rtnl_link_ops vxcan_link_ops;
|
static struct rtnl_link_ops vxcan_link_ops;
|
||||||
|
|
||||||
static int vxcan_newlink(struct net *net, struct net_device *dev,
|
static int vxcan_newlink(struct net *peer_net, struct net_device *dev,
|
||||||
struct nlattr *tb[], struct nlattr *data[],
|
struct nlattr *tb[], struct nlattr *data[],
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
{
|
{
|
||||||
struct vxcan_priv *priv;
|
struct vxcan_priv *priv;
|
||||||
struct net_device *peer;
|
struct net_device *peer;
|
||||||
struct net *peer_net;
|
|
||||||
|
|
||||||
struct nlattr *peer_tb[IFLA_MAX + 1], **tbp = tb;
|
struct nlattr *peer_tb[IFLA_MAX + 1], **tbp = tb;
|
||||||
char ifname[IFNAMSIZ];
|
char ifname[IFNAMSIZ];
|
||||||
|
@ -203,20 +202,15 @@ static int vxcan_newlink(struct net *net, struct net_device *dev,
|
||||||
name_assign_type = NET_NAME_ENUM;
|
name_assign_type = NET_NAME_ENUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer_net = rtnl_link_get_net(net, tbp);
|
|
||||||
peer = rtnl_create_link(peer_net, ifname, name_assign_type,
|
peer = rtnl_create_link(peer_net, ifname, name_assign_type,
|
||||||
&vxcan_link_ops, tbp, extack);
|
&vxcan_link_ops, tbp, extack);
|
||||||
if (IS_ERR(peer)) {
|
if (IS_ERR(peer))
|
||||||
put_net(peer_net);
|
|
||||||
return PTR_ERR(peer);
|
return PTR_ERR(peer);
|
||||||
}
|
|
||||||
|
|
||||||
if (ifmp && dev->ifindex)
|
if (ifmp && dev->ifindex)
|
||||||
peer->ifindex = ifmp->ifi_index;
|
peer->ifindex = ifmp->ifi_index;
|
||||||
|
|
||||||
err = register_netdevice(peer);
|
err = register_netdevice(peer);
|
||||||
put_net(peer_net);
|
|
||||||
peer_net = NULL;
|
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
free_netdev(peer);
|
free_netdev(peer);
|
||||||
return err;
|
return err;
|
||||||
|
|
|
@ -327,7 +327,7 @@ static int netkit_validate(struct nlattr *tb[], struct nlattr *data[],
|
||||||
|
|
||||||
static struct rtnl_link_ops netkit_link_ops;
|
static struct rtnl_link_ops netkit_link_ops;
|
||||||
|
|
||||||
static int netkit_new_link(struct net *src_net, struct net_device *dev,
|
static int netkit_new_link(struct net *peer_net, struct net_device *dev,
|
||||||
struct nlattr *tb[], struct nlattr *data[],
|
struct nlattr *tb[], struct nlattr *data[],
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
{
|
{
|
||||||
|
@ -342,7 +342,6 @@ static int netkit_new_link(struct net *src_net, struct net_device *dev,
|
||||||
struct net_device *peer;
|
struct net_device *peer;
|
||||||
char ifname[IFNAMSIZ];
|
char ifname[IFNAMSIZ];
|
||||||
struct netkit *nk;
|
struct netkit *nk;
|
||||||
struct net *net;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -385,13 +384,10 @@ static int netkit_new_link(struct net *src_net, struct net_device *dev,
|
||||||
(tb[IFLA_ADDRESS] || tbp[IFLA_ADDRESS]))
|
(tb[IFLA_ADDRESS] || tbp[IFLA_ADDRESS]))
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
net = rtnl_link_get_net(src_net, tbp);
|
peer = rtnl_create_link(peer_net, ifname, ifname_assign_type,
|
||||||
peer = rtnl_create_link(net, ifname, ifname_assign_type,
|
|
||||||
&netkit_link_ops, tbp, extack);
|
&netkit_link_ops, tbp, extack);
|
||||||
if (IS_ERR(peer)) {
|
if (IS_ERR(peer))
|
||||||
put_net(net);
|
|
||||||
return PTR_ERR(peer);
|
return PTR_ERR(peer);
|
||||||
}
|
|
||||||
|
|
||||||
netif_inherit_tso_max(peer, dev);
|
netif_inherit_tso_max(peer, dev);
|
||||||
|
|
||||||
|
@ -408,7 +404,6 @@ static int netkit_new_link(struct net *src_net, struct net_device *dev,
|
||||||
bpf_mprog_bundle_init(&nk->bundle);
|
bpf_mprog_bundle_init(&nk->bundle);
|
||||||
|
|
||||||
err = register_netdevice(peer);
|
err = register_netdevice(peer);
|
||||||
put_net(net);
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto err_register_peer;
|
goto err_register_peer;
|
||||||
netif_carrier_off(peer);
|
netif_carrier_off(peer);
|
||||||
|
|
|
@ -1765,7 +1765,7 @@ static int veth_init_queues(struct net_device *dev, struct nlattr *tb[])
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int veth_newlink(struct net *src_net, struct net_device *dev,
|
static int veth_newlink(struct net *peer_net, struct net_device *dev,
|
||||||
struct nlattr *tb[], struct nlattr *data[],
|
struct nlattr *tb[], struct nlattr *data[],
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
{
|
{
|
||||||
|
@ -1776,7 +1776,6 @@ static int veth_newlink(struct net *src_net, struct net_device *dev,
|
||||||
struct nlattr *peer_tb[IFLA_MAX + 1], **tbp;
|
struct nlattr *peer_tb[IFLA_MAX + 1], **tbp;
|
||||||
unsigned char name_assign_type;
|
unsigned char name_assign_type;
|
||||||
struct ifinfomsg *ifmp;
|
struct ifinfomsg *ifmp;
|
||||||
struct net *net;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* create and register peer first
|
* create and register peer first
|
||||||
|
@ -1800,13 +1799,10 @@ static int veth_newlink(struct net *src_net, struct net_device *dev,
|
||||||
name_assign_type = NET_NAME_ENUM;
|
name_assign_type = NET_NAME_ENUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
net = rtnl_link_get_net(src_net, tbp);
|
peer = rtnl_create_link(peer_net, ifname, name_assign_type,
|
||||||
peer = rtnl_create_link(net, ifname, name_assign_type,
|
|
||||||
&veth_link_ops, tbp, extack);
|
&veth_link_ops, tbp, extack);
|
||||||
if (IS_ERR(peer)) {
|
if (IS_ERR(peer))
|
||||||
put_net(net);
|
|
||||||
return PTR_ERR(peer);
|
return PTR_ERR(peer);
|
||||||
}
|
|
||||||
|
|
||||||
if (!ifmp || !tbp[IFLA_ADDRESS])
|
if (!ifmp || !tbp[IFLA_ADDRESS])
|
||||||
eth_hw_addr_random(peer);
|
eth_hw_addr_random(peer);
|
||||||
|
@ -1817,8 +1813,6 @@ static int veth_newlink(struct net *src_net, struct net_device *dev,
|
||||||
netif_inherit_tso_max(peer, dev);
|
netif_inherit_tso_max(peer, dev);
|
||||||
|
|
||||||
err = register_netdevice(peer);
|
err = register_netdevice(peer);
|
||||||
put_net(net);
|
|
||||||
net = NULL;
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto err_register_peer;
|
goto err_register_peer;
|
||||||
|
|
||||||
|
|
|
@ -3746,6 +3746,7 @@ static int rtnl_group_changelink(const struct sk_buff *skb,
|
||||||
static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
|
static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
|
||||||
const struct rtnl_link_ops *ops,
|
const struct rtnl_link_ops *ops,
|
||||||
struct net *tgt_net, struct net *link_net,
|
struct net *tgt_net, struct net *link_net,
|
||||||
|
struct net *peer_net,
|
||||||
const struct nlmsghdr *nlh,
|
const struct nlmsghdr *nlh,
|
||||||
struct nlattr **tb, struct nlattr **data,
|
struct nlattr **tb, struct nlattr **data,
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
|
@ -3776,8 +3777,13 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
|
||||||
|
|
||||||
dev->ifindex = ifm->ifi_index;
|
dev->ifindex = ifm->ifi_index;
|
||||||
|
|
||||||
|
if (link_net)
|
||||||
|
net = link_net;
|
||||||
|
if (peer_net)
|
||||||
|
net = peer_net;
|
||||||
|
|
||||||
if (ops->newlink)
|
if (ops->newlink)
|
||||||
err = ops->newlink(link_net ? : net, dev, tb, data, extack);
|
err = ops->newlink(net, dev, tb, data, extack);
|
||||||
else
|
else
|
||||||
err = register_netdevice(dev);
|
err = register_netdevice(dev);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
@ -3812,40 +3818,33 @@ out_unregister:
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtnl_add_peer_net(struct rtnl_nets *rtnl_nets,
|
static struct net *rtnl_get_peer_net(const struct rtnl_link_ops *ops,
|
||||||
const struct rtnl_link_ops *ops,
|
struct nlattr *data[],
|
||||||
struct nlattr *data[],
|
struct netlink_ext_ack *extack)
|
||||||
struct netlink_ext_ack *extack)
|
|
||||||
{
|
{
|
||||||
struct nlattr *tb[IFLA_MAX + 1];
|
struct nlattr *tb[IFLA_MAX + 1];
|
||||||
struct net *net;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (!data || !data[ops->peer_type])
|
if (!data || !data[ops->peer_type])
|
||||||
return 0;
|
return NULL;
|
||||||
|
|
||||||
err = rtnl_nla_parse_ifinfomsg(tb, data[ops->peer_type], extack);
|
err = rtnl_nla_parse_ifinfomsg(tb, data[ops->peer_type], extack);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return ERR_PTR(err);
|
||||||
|
|
||||||
if (ops->validate) {
|
if (ops->validate) {
|
||||||
err = ops->validate(tb, NULL, extack);
|
err = ops->validate(tb, NULL, extack);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return ERR_PTR(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
net = rtnl_link_get_net_ifla(tb);
|
return rtnl_link_get_net_ifla(tb);
|
||||||
if (IS_ERR(net))
|
|
||||||
return PTR_ERR(net);
|
|
||||||
if (net)
|
|
||||||
rtnl_nets_add(rtnl_nets, net);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
static int __rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
const struct rtnl_link_ops *ops,
|
const struct rtnl_link_ops *ops,
|
||||||
struct net *tgt_net, struct net *link_net,
|
struct net *tgt_net, struct net *link_net,
|
||||||
|
struct net *peer_net,
|
||||||
struct rtnl_newlink_tbs *tbs,
|
struct rtnl_newlink_tbs *tbs,
|
||||||
struct nlattr **data,
|
struct nlattr **data,
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
|
@ -3894,14 +3893,15 @@ static int __rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtnl_newlink_create(skb, ifm, ops, tgt_net, link_net, nlh, tb, data, extack);
|
return rtnl_newlink_create(skb, ifm, ops, tgt_net, link_net, peer_net, nlh,
|
||||||
|
tb, data, extack);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
{
|
{
|
||||||
|
struct net *tgt_net, *link_net = NULL, *peer_net = NULL;
|
||||||
struct nlattr **tb, **linkinfo, **data = NULL;
|
struct nlattr **tb, **linkinfo, **data = NULL;
|
||||||
struct net *tgt_net, *link_net = NULL;
|
|
||||||
struct rtnl_link_ops *ops = NULL;
|
struct rtnl_link_ops *ops = NULL;
|
||||||
struct rtnl_newlink_tbs *tbs;
|
struct rtnl_newlink_tbs *tbs;
|
||||||
struct rtnl_nets rtnl_nets;
|
struct rtnl_nets rtnl_nets;
|
||||||
|
@ -3971,9 +3971,11 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ops->peer_type) {
|
if (ops->peer_type) {
|
||||||
ret = rtnl_add_peer_net(&rtnl_nets, ops, data, extack);
|
peer_net = rtnl_get_peer_net(ops, data, extack);
|
||||||
if (ret < 0)
|
if (IS_ERR(peer_net))
|
||||||
goto put_ops;
|
goto put_ops;
|
||||||
|
if (peer_net)
|
||||||
|
rtnl_nets_add(&rtnl_nets, peer_net);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4004,7 +4006,7 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
}
|
}
|
||||||
|
|
||||||
rtnl_nets_lock(&rtnl_nets);
|
rtnl_nets_lock(&rtnl_nets);
|
||||||
ret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, tbs, data, extack);
|
ret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, peer_net, tbs, data, extack);
|
||||||
rtnl_nets_unlock(&rtnl_nets);
|
rtnl_nets_unlock(&rtnl_nets);
|
||||||
|
|
||||||
put_net:
|
put_net:
|
||||||
|
|
Loading…
Add table
Reference in a new issue