From 73a7c8fb2302ae78920b210c098b752b9caa6bf6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 25 May 2026 08:35:38 +0000 Subject: [PATCH 1/5] rtnetlink: use nla_nest_end_safe() in rtnl_fill_prop_list() Avoid corrupting a netlink message and confuse user space in the very unlikely case rtnl_fill_prop_list was able to produce a very big nested element. This is extremely unlikely, because rtnl_prop_list_size() provisions nla_total_size(ALTIFNAMSIZ) per altname. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260525083542.1565964-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/rtnetlink.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 0aa429336ffe..cd1004410dd7 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -1970,7 +1970,10 @@ static int rtnl_fill_prop_list(struct sk_buff *skb, if (ret <= 0) goto nest_cancel; - nla_nest_end(skb, prop_list); + ret = -EMSGSIZE; + if (nla_nest_end_safe(skb, prop_list) < 0) + goto nest_cancel; + return 0; nest_cancel: From 00888feb601497a58ed363aae13be01b55d8a028 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 25 May 2026 08:35:39 +0000 Subject: [PATCH 2/5] net: defer netdev_name_node_alt_flush() call to netdev_run_todo() In the following patch, we want to call rtnl_fill_prop_list() without RTNL being held, but after a device reference was taken. We need to free altnames in netdev_run_todo() instead of unregister_netdevice_many_notify(). Freeing will only happen once all device references have been released. Note that dev->name_node serves as the anchor for altnames, thus must be also freed in netdev_run_todo(). Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260525083542.1565964-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index a50cd68c6bfa..804e8ad25010 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -11730,6 +11730,8 @@ void netdev_run_todo(void) WARN_ON(rcu_access_pointer(dev->ip_ptr)); WARN_ON(rcu_access_pointer(dev->ip6_ptr)); + netdev_name_node_alt_flush(dev); + netdev_name_node_free(dev->name_node); netdev_do_free_pcpu_stats(dev); if (dev->priv_destructor) dev->priv_destructor(dev); @@ -12443,8 +12445,6 @@ void unregister_netdevice_many_notify(struct list_head *head, dev_uc_flush(dev); dev_mc_flush(dev); - netdev_name_node_alt_flush(dev); - netdev_name_node_free(dev->name_node); netdev_rss_contexts_free(dev); From e896e5c0734b559b5b58f356ebf100ccf5fcd16e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 25 May 2026 08:35:40 +0000 Subject: [PATCH 3/5] rtnetlink: do not acquire RTNL in rtnl_getlink() with RTEXT_FILTER_NAME_ONLY When RTEXT_FILTER_NAME_ONLY is requested, rtnl_fill_ifinfo() is dumping device attributes which do not need RTNL protection. Many shell scripts invoke iproute2 commands specifying a device by its name. After this patch, they will no longer add RTNL pressure. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260525083542.1565964-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/rtnetlink.c | 96 +++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index cd1004410dd7..6041e008b22d 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -2068,7 +2068,6 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct nlmsghdr *nlh; struct Qdisc *qdisc; - ASSERT_RTNL(); nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ifm), flags); if (nlh == NULL) return -EMSGSIZE; @@ -2091,6 +2090,7 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, if (ext_filter_mask & RTEXT_FILTER_NAME_ONLY) goto end; + ASSERT_RTNL(); if (tgt_netnsid >= 0 && nla_put_s32(skb, IFLA_TARGET_NETNSID, tgt_netnsid)) goto nla_put_failure; @@ -3468,6 +3468,21 @@ static struct net_device *rtnl_dev_get(struct net *net, return __dev_get_by_name(net, ifname); } +static struct net_device *rtnl_dev_get_rcu(struct net *net, + struct nlattr *tb[]) +{ + char ifname[ALTIFNAMSIZ]; + + if (tb[IFLA_IFNAME]) + nla_strscpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ); + else if (tb[IFLA_ALT_IFNAME]) + nla_strscpy(ifname, tb[IFLA_ALT_IFNAME], ALTIFNAMSIZ); + else + return NULL; + + return dev_get_by_name_rcu(net, ifname); +} + static int rtnl_setlink(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack) { @@ -4187,14 +4202,16 @@ static int rtnl_getlink(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack) { struct net *net = sock_net(skb->sk); - struct net *tgt_net = net; - struct ifinfomsg *ifm; - struct nlattr *tb[IFLA_MAX+1]; + struct nlattr *tb[IFLA_MAX + 1]; + netdevice_tracker dev_tracker; struct net_device *dev = NULL; + struct net *tgt_net = net; + u32 ext_filter_mask = 0; + struct ifinfomsg *ifm; struct sk_buff *nskb; int netnsid = -1; + bool need_rtnl; int err; - u32 ext_filter_mask = 0; err = rtnl_valid_getlink_req(skb, nlh, tb, extack); if (err < 0) @@ -4214,43 +4231,65 @@ static int rtnl_getlink(struct sk_buff *skb, struct nlmsghdr *nlh, if (tb[IFLA_EXT_MASK]) ext_filter_mask = nla_get_u32(tb[IFLA_EXT_MASK]); - err = -EINVAL; ifm = nlmsg_data(nlh); - if (ifm->ifi_index > 0) - dev = __dev_get_by_index(tgt_net, ifm->ifi_index); - else if (tb[IFLA_IFNAME] || tb[IFLA_ALT_IFNAME]) - dev = rtnl_dev_get(tgt_net, tb); - else + rcu_read_lock(); + if (ifm->ifi_index > 0) { + dev = dev_get_by_index_rcu(tgt_net, ifm->ifi_index); + } else if (tb[IFLA_IFNAME] || tb[IFLA_ALT_IFNAME]) { + dev = rtnl_dev_get_rcu(tgt_net, tb); + } else { + rcu_read_unlock(); + err = -EINVAL; goto out; + } + netdev_hold(dev, &dev_tracker, GFP_ATOMIC); + rcu_read_unlock(); err = -ENODEV; if (dev == NULL) goto out; + need_rtnl = !(ext_filter_mask & RTEXT_FILTER_NAME_ONLY); + +retry: + if (need_rtnl) { + rtnl_lock(); + /* Synchronize the carrier state so we don't report a state + * that we're not actually going to honour immediately; if + * the driver just did a carrier off->on transition, we can + * only TX if link watch work has run, but without this we'd + * already report carrier on, even if it doesn't work yet. + */ + linkwatch_sync_dev(dev); + } + err = -ENOBUFS; nskb = nlmsg_new_large(if_nlmsg_size(dev, ext_filter_mask)); - if (nskb == NULL) - goto out; + if (nskb) + err = rtnl_fill_ifinfo(nskb, dev, net, + RTM_NEWLINK, NETLINK_CB(skb).portid, + nlh->nlmsg_seq, 0, 0, ext_filter_mask, + 0, NULL, 0, netnsid, GFP_KERNEL); - /* Synchronize the carrier state so we don't report a state - * that we're not actually going to honour immediately; if - * the driver just did a carrier off->on transition, we can - * only TX if link watch work has run, but without this we'd - * already report carrier on, even if it doesn't work yet. - */ - linkwatch_sync_dev(dev); + if (need_rtnl) + rtnl_unlock(); - err = rtnl_fill_ifinfo(nskb, dev, net, - RTM_NEWLINK, NETLINK_CB(skb).portid, - nlh->nlmsg_seq, 0, 0, ext_filter_mask, - 0, NULL, 0, netnsid, GFP_KERNEL); if (err < 0) { - /* -EMSGSIZE implies BUG in if_nlmsg_size */ - WARN_ON(err == -EMSGSIZE); kfree_skb(nskb); - } else + if (err == -EMSGSIZE) { + if (!need_rtnl) { + /* Some altnames were added, retry with RTNL. */ + need_rtnl = true; + goto retry; + } + /* -EMSGSIZE implies BUG in if_nlmsg_size */ + WARN_ON_ONCE(1); + } + } else { err = rtnl_unicast(nskb, net, NETLINK_CB(skb).portid); + } out: + netdev_put(dev, &dev_tracker); if (netnsid >= 0) put_net(tgt_net); @@ -7117,7 +7156,8 @@ static const struct rtnl_msg_handler rtnetlink_rtnl_msg_handlers[] __initconst = {.msgtype = RTM_DELLINK, .doit = rtnl_dellink, .flags = RTNL_FLAG_DOIT_PERNET_WIP}, {.msgtype = RTM_GETLINK, .doit = rtnl_getlink, - .dumpit = rtnl_dump_ifinfo, .flags = RTNL_FLAG_DUMP_SPLIT_NLM_DONE}, + .dumpit = rtnl_dump_ifinfo, + .flags = RTNL_FLAG_DUMP_SPLIT_NLM_DONE | RTNL_FLAG_DOIT_UNLOCKED}, {.msgtype = RTM_SETLINK, .doit = rtnl_setlink, .flags = RTNL_FLAG_DOIT_PERNET_WIP}, {.msgtype = RTM_GETADDR, .dumpit = rtnl_dump_all}, From 6768c7c3d70f0d6f9cb6fad2b33357ec7379d952 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 25 May 2026 08:35:41 +0000 Subject: [PATCH 4/5] rtnetlink: do not assume RTNL is held in link_master_filtered() RTNL might be no longer held by the caller in the following patch. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260525083542.1565964-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/rtnetlink.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 6041e008b22d..0a59036d5f93 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -2371,22 +2371,24 @@ static struct rtnl_link_ops *linkinfo_to_kind_ops(const struct nlattr *nla, static bool link_master_filtered(struct net_device *dev, int master_idx) { struct net_device *master; + bool res = false; if (!master_idx) return false; - master = netdev_master_upper_dev_get(dev); + rcu_read_lock(); + master = netdev_master_upper_dev_get_rcu(dev); /* 0 is already used to denote IFLA_MASTER wasn't passed, therefore need * another invalid value for ifindex to denote "no master". */ if (master_idx == -1) - return !!master; + res = !!master; + else if (!master || master->ifindex != master_idx) + res = true; + rcu_read_unlock(); - if (!master || master->ifindex != master_idx) - return true; - - return false; + return res; } static bool link_kind_filtered(const struct net_device *dev, From d628604f7ea75a4dfe7ee3792f3c79c29ca81c04 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 25 May 2026 08:35:42 +0000 Subject: [PATCH 5/5] rtnetlink: add RTEXT_FILTER_NAME_ONLY support to rtnl_dump_ifinfo() When user requests RTEXT_FILTER_NAME_ONLY flag, we limit the dump parts to: - struct nlmsghdr - IFLA_IFNAME - IFLA_PROP_LIST (alternate names) - This saves space in the dump, pushing more devices per system call. - This can be done without acquiring RTNL. I still have a medium term goal to avoid RTNL in rtnl_dump_ifinfo() regardless of RTEXT_FILTER_NAME_ONLY being used. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260525083542.1565964-6-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/rtnetlink.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 0a59036d5f93..652dd008955a 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -2499,6 +2499,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb) int ops_srcu_index; int master_idx = 0; int netnsid = -1; + bool need_rtnl; int err, i; err = rtnl_valid_dump_ifinfo_req(nlh, cb->strict_check, tb, extack); @@ -2548,6 +2549,12 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb) walk_entries: err = 0; + need_rtnl = !(ext_filter_mask & RTEXT_FILTER_NAME_ONLY); + if (need_rtnl) + rtnl_lock(); + else + rcu_read_lock(); + for_each_netdev_dump(tgt_net, dev, ctx->ifindex) { if (link_dump_filtered(dev, master_idx, kind_ops)) continue; @@ -2559,11 +2566,13 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb) if (err < 0) break; } - - - cb->seq = tgt_net->dev_base_seq; + cb->seq = READ_ONCE(tgt_net->dev_base_seq); nl_dump_check_consistent(cb, nlmsg_hdr(skb)); + if (need_rtnl) + rtnl_unlock(); + else + rcu_read_unlock(); out: if (kind_ops) @@ -7159,7 +7168,9 @@ static const struct rtnl_msg_handler rtnetlink_rtnl_msg_handlers[] __initconst = .flags = RTNL_FLAG_DOIT_PERNET_WIP}, {.msgtype = RTM_GETLINK, .doit = rtnl_getlink, .dumpit = rtnl_dump_ifinfo, - .flags = RTNL_FLAG_DUMP_SPLIT_NLM_DONE | RTNL_FLAG_DOIT_UNLOCKED}, + .flags = RTNL_FLAG_DUMP_SPLIT_NLM_DONE | + RTNL_FLAG_DOIT_UNLOCKED | + RTNL_FLAG_DUMP_UNLOCKED}, {.msgtype = RTM_SETLINK, .doit = rtnl_setlink, .flags = RTNL_FLAG_DOIT_PERNET_WIP}, {.msgtype = RTM_GETADDR, .dumpit = rtnl_dump_all},