From 311cca40661f428b7aa114fb5af578cfdbe3e8b6 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 17 Oct 2023 18:38:13 -0700 Subject: net: fix ifname in netlink ntf during netns move dev_get_valid_name() overwrites the netdev's name on success. This makes it hard to use in prepare-commit-like fashion, where we do validation first, and "commit" to the change later. Factor out a helper which lets us save the new name to a buffer. Use it to fix the problem of notification on netns move having incorrect name: 5: eth0: mtu 1500 qdisc noop state DOWN group default link/ether be:4d:58:f9:d5:40 brd ff:ff:ff:ff:ff:ff 6: eth1: mtu 1500 qdisc noop state DOWN group default link/ether 1e:4a:34:36:e3:cd brd ff:ff:ff:ff:ff:ff [ ~]# ip link set dev eth0 netns 1 name eth1 ip monitor inside netns: Deleted inet eth0 Deleted inet6 eth0 Deleted 5: eth1: mtu 1500 qdisc noop state DOWN group default link/ether be:4d:58:f9:d5:40 brd ff:ff:ff:ff:ff:ff new-netnsid 0 new-ifindex 7 Name is reported as eth1 in old netns for ifindex 5, already renamed. Fixes: d90310243fd7 ("net: device name allocation cleanups") Signed-off-by: Jakub Kicinski Reviewed-by: Jiri Pirko Signed-off-by: Paolo Abeni --- net/core/dev.c | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) (limited to 'net/core/dev.c') diff --git a/net/core/dev.c b/net/core/dev.c index 5aaf5753d4e4..f109ad34d660 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1123,6 +1123,26 @@ static int __dev_alloc_name(struct net *net, const char *name, char *buf) return -ENFILE; } +static int dev_prep_valid_name(struct net *net, struct net_device *dev, + const char *want_name, char *out_name) +{ + int ret; + + if (!dev_valid_name(want_name)) + return -EINVAL; + + if (strchr(want_name, '%')) { + ret = __dev_alloc_name(net, want_name, out_name); + return ret < 0 ? ret : 0; + } else if (netdev_name_in_use(net, want_name)) { + return -EEXIST; + } else if (out_name != want_name) { + strscpy(out_name, want_name, IFNAMSIZ); + } + + return 0; +} + static int dev_alloc_name_ns(struct net *net, struct net_device *dev, const char *name) @@ -1160,19 +1180,13 @@ EXPORT_SYMBOL(dev_alloc_name); static int dev_get_valid_name(struct net *net, struct net_device *dev, const char *name) { - BUG_ON(!net); - - if (!dev_valid_name(name)) - return -EINVAL; - - if (strchr(name, '%')) - return dev_alloc_name_ns(net, dev, name); - else if (netdev_name_in_use(net, name)) - return -EEXIST; - else if (dev->name != name) - strscpy(dev->name, name, IFNAMSIZ); + char buf[IFNAMSIZ]; + int ret; - return 0; + ret = dev_prep_valid_name(net, dev, name, buf); + if (ret >= 0) + strscpy(dev->name, buf, IFNAMSIZ); + return ret; } /** @@ -11038,6 +11052,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat, int new_ifindex) { struct net *net_old = dev_net(dev); + char new_name[IFNAMSIZ] = {}; int err, new_nsid; ASSERT_RTNL(); @@ -11064,7 +11079,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, /* We get here if we can't use the current device name */ if (!pat) goto out; - err = dev_get_valid_name(net, dev, pat); + err = dev_prep_valid_name(net, dev, pat, new_name); if (err < 0) goto out; } @@ -11135,6 +11150,9 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, kobject_uevent(&dev->dev.kobj, KOBJ_ADD); netdev_adjacent_add_links(dev); + if (new_name[0]) /* Rename the netdev to prepared name */ + strscpy(dev->name, new_name, IFNAMSIZ); + /* Fixup kobjects */ err = device_rename(&dev->dev, dev->name); WARN_ON(err); -- cgit From 7663d522099ecc464512164e660bc771b2ff7b64 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 17 Oct 2023 18:38:14 -0700 Subject: net: check for altname conflicts when changing netdev's netns It's currently possible to create an altname conflicting with an altname or real name of another device by creating it in another netns and moving it over: [ ~]$ ip link add dev eth0 type dummy [ ~]$ ip netns add test [ ~]$ ip -netns test link add dev ethX netns test type dummy [ ~]$ ip -netns test link property add dev ethX altname eth0 [ ~]$ ip -netns test link set dev ethX netns 1 [ ~]$ ip link ... 3: eth0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 02:40:88:62:ec:b8 brd ff:ff:ff:ff:ff:ff ... 5: ethX: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 26:b7:28:78:38:0f brd ff:ff:ff:ff:ff:ff altname eth0 Create a macro for walking the altnames, this hopefully makes it clearer that the list we walk contains only altnames. Which is otherwise not entirely intuitive. Fixes: 36fbf1e52bd3 ("net: rtnetlink: add linkprop commands to add and delete alternative ifnames") Reviewed-by: Jiri Pirko Signed-off-by: Jakub Kicinski Signed-off-by: Paolo Abeni --- net/core/dev.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'net/core/dev.c') diff --git a/net/core/dev.c b/net/core/dev.c index f109ad34d660..ae557193b77c 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1086,7 +1086,8 @@ static int __dev_alloc_name(struct net *net, const char *name, char *buf) for_each_netdev(net, d) { struct netdev_name_node *name_node; - list_for_each_entry(name_node, &d->name_node->list, list) { + + netdev_for_each_altname(d, name_node) { if (!sscanf(name_node->name, name, &i)) continue; if (i < 0 || i >= max_netdevices) @@ -11051,6 +11052,7 @@ EXPORT_SYMBOL(unregister_netdev); int __dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat, int new_ifindex) { + struct netdev_name_node *name_node; struct net *net_old = dev_net(dev); char new_name[IFNAMSIZ] = {}; int err, new_nsid; @@ -11083,6 +11085,11 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, if (err < 0) goto out; } + /* Check that none of the altnames conflicts. */ + err = -EEXIST; + netdev_for_each_altname(dev, name_node) + if (netdev_name_in_use(net, name_node->name)) + goto out; /* Check that new_ifindex isn't used yet. */ if (new_ifindex) { -- cgit From 1a83f4a7c156fa6bbd6b530e89fa3270bf3d9d1b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 17 Oct 2023 18:38:15 -0700 Subject: net: avoid UAF on deleted altname Altnames are accessed under RCU (dev_get_by_name_rcu()) but freed by kfree() with no synchronization point. Each node has one or two allocations (node and a variable-size name, sometimes the name is netdev->name). Adding rcu_heads here is a bit tedious. Besides most code which unlists the names already has rcu barriers - so take the simpler approach of adding synchronize_rcu(). Note that the one on the unregistration path (which matters more) is removed by the next fix. Fixes: ff92741270bf ("net: introduce name_node struct to be used in hashlist") Reviewed-by: Jiri Pirko Signed-off-by: Jakub Kicinski Signed-off-by: Paolo Abeni --- net/core/dev.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'net/core/dev.c') diff --git a/net/core/dev.c b/net/core/dev.c index ae557193b77c..559705aeefe4 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -345,7 +345,6 @@ int netdev_name_node_alt_create(struct net_device *dev, const char *name) static void __netdev_name_node_alt_destroy(struct netdev_name_node *name_node) { list_del(&name_node->list); - netdev_name_node_del(name_node); kfree(name_node->name); netdev_name_node_free(name_node); } @@ -364,6 +363,8 @@ int netdev_name_node_alt_destroy(struct net_device *dev, const char *name) if (name_node == dev->name_node || name_node->dev != dev) return -EINVAL; + netdev_name_node_del(name_node); + synchronize_rcu(); __netdev_name_node_alt_destroy(name_node); return 0; @@ -10941,6 +10942,7 @@ void unregister_netdevice_many_notify(struct list_head *head, synchronize_net(); list_for_each_entry(dev, head, unreg_list) { + struct netdev_name_node *name_node; struct sk_buff *skb = NULL; /* Shutdown queueing discipline. */ @@ -10968,6 +10970,9 @@ void unregister_netdevice_many_notify(struct list_head *head, dev_uc_flush(dev); dev_mc_flush(dev); + netdev_for_each_altname(dev, name_node) + netdev_name_node_del(name_node); + synchronize_rcu(); netdev_name_node_alt_flush(dev); netdev_name_node_free(dev->name_node); -- cgit From 8e15aee621618a3ee3abecaf1fd8c1428098b7ef Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 17 Oct 2023 18:38:16 -0700 Subject: net: move altnames together with the netdevice The altname nodes are currently not moved to the new netns when netdevice itself moves: [ ~]# ip netns add test [ ~]# ip -netns test link add name eth0 type dummy [ ~]# ip -netns test link property add dev eth0 altname some-name [ ~]# ip -netns test link show dev some-name 2: eth0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 1e:67:ed:19:3d:24 brd ff:ff:ff:ff:ff:ff altname some-name [ ~]# ip -netns test link set dev eth0 netns 1 [ ~]# ip link ... 3: eth0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 02:40:88:62:ec:b8 brd ff:ff:ff:ff:ff:ff altname some-name [ ~]# ip li show dev some-name Device "some-name" does not exist. Remove them from the hash table when device is unlisted and add back when listed again. Fixes: 36fbf1e52bd3 ("net: rtnetlink: add linkprop commands to add and delete alternative ifnames") Reviewed-by: Jiri Pirko Signed-off-by: Jakub Kicinski Signed-off-by: Paolo Abeni --- net/core/dev.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'net/core/dev.c') diff --git a/net/core/dev.c b/net/core/dev.c index 559705aeefe4..9f3f8930c691 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -381,6 +381,7 @@ static void netdev_name_node_alt_flush(struct net_device *dev) /* Device list insertion */ static void list_netdevice(struct net_device *dev) { + struct netdev_name_node *name_node; struct net *net = dev_net(dev); ASSERT_RTNL(); @@ -391,6 +392,10 @@ static void list_netdevice(struct net_device *dev) hlist_add_head_rcu(&dev->index_hlist, dev_index_hash(net, dev->ifindex)); write_unlock(&dev_base_lock); + + netdev_for_each_altname(dev, name_node) + netdev_name_node_add(net, name_node); + /* We reserved the ifindex, this can't fail */ WARN_ON(xa_store(&net->dev_by_index, dev->ifindex, dev, GFP_KERNEL)); @@ -402,12 +407,16 @@ static void list_netdevice(struct net_device *dev) */ static void unlist_netdevice(struct net_device *dev, bool lock) { + struct netdev_name_node *name_node; struct net *net = dev_net(dev); ASSERT_RTNL(); xa_erase(&net->dev_by_index, dev->ifindex); + netdev_for_each_altname(dev, name_node) + netdev_name_node_del(name_node); + /* Unlink dev from the device chain */ if (lock) write_lock(&dev_base_lock); @@ -10942,7 +10951,6 @@ void unregister_netdevice_many_notify(struct list_head *head, synchronize_net(); list_for_each_entry(dev, head, unreg_list) { - struct netdev_name_node *name_node; struct sk_buff *skb = NULL; /* Shutdown queueing discipline. */ @@ -10970,9 +10978,6 @@ void unregister_netdevice_many_notify(struct list_head *head, dev_uc_flush(dev); dev_mc_flush(dev); - netdev_for_each_altname(dev, name_node) - netdev_name_node_del(name_node); - synchronize_rcu(); netdev_name_node_alt_flush(dev); netdev_name_node_free(dev->name_node); -- cgit