* [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry
@ 2026-11-10 17:26 Stephen Hemminger
2026-11-10 17:26 ` [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit Stephen Hemminger
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Stephen Hemminger @ 2026-11-10 17:26 UTC (permalink / raw)
To: netdev; +Cc: dsahern, Stephen Hemminger
While analyzing one of the OSS Scanner dump reports for iproute2,
a bug was uncovered in netlink route dump.
A route dump whose first entry is too large for the dump skb ends with
NLMSG_DONE and status 0. The route and everything after it is silently
dropped; "ip route show table 100" prints nothing and exits 0.
It takes some non real world stuff to do this
like a 500 way multipath route is enough to trigger this.
The series is broken into three parts:
1. Fix regression in rtnl_dumpit().
Do not hide EMSGSIZE on empty skb case.
2. Fix IPv6 FIB dump to propagate error properly
3. Add a test for this bug.
v2 - drop Assisted-by
- correct commit message about "ip route get"
Stephen Hemminger (3):
rtnetlink: return EMSGSIZE when first dump entry does not fit
ipv6: return EMSGSIZE when first route in dump does not fit
selftests: net: netlink-dumps: test dump of route larger than skb
net/core/rtnetlink.c | 5 +-
net/ipv6/ip6_fib.c | 6 +
tools/testing/selftests/net/config | 3 +
tools/testing/selftests/net/netlink-dumps.c | 271 +++++++++++++++++++-
4 files changed, 280 insertions(+), 5 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit
2026-11-10 17:26 [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Stephen Hemminger
@ 2026-11-10 17:26 ` Stephen Hemminger
2026-11-12 5:27 ` netdev-bot+sashiko
2026-11-12 15:31 ` Ido Schimmel
2026-11-10 17:26 ` [PATCH net v2 2/3] ipv6: return EMSGSIZE when first route in dump " Stephen Hemminger
` (2 subsequent siblings)
3 siblings, 2 replies; 10+ messages in thread
From: Stephen Hemminger @ 2026-11-10 17:26 UTC (permalink / raw)
To: netdev
Cc: dsahern, Stephen Hemminger, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
Ido Schimmel, Sabrina Dubroca, Stanislav Fomichev, Andrea Parri,
Nikolaos Gkarlis, Willem de Bruijn, open list
If a route entry is too large for the dump skb, the dump is silently
truncated and no error is returned.
For handlers with RTNL_FLAG_DUMP_SPLIT_NLM_DONE, rtnl_dumpit() drops
-EMSGSIZE and returns skb->len. With an empty skb this is 0, so the
dump ends with NLMSG_DONE and status 0, and the remaining entries are
lost.
The first dump skb is allocated from __netlink_dump_start() while
max_recvmsg_len is still 0, so it is only NLMSG_GOODSIZE. On a 4K page
kernel that is under 4000 bytes, and a 500 way multipath route does not
fit:
ip link add dummy0 type dummy
ip link set dummy0 up
ip route add 198.51.100.0/24 table 100 \
$(for i in $(seq 500); do echo -n "nexthop dev dummy0 "; done)
ip route show table 100
The dump prints nothing and exits 0
Only ignore -EMSGSIZE when the skb has data, so that an entry which
does not fit in an empty skb reports the error to user space.
Fixes: 5b4b62a169e1 ("rtnetlink: make the "split" NLM_DONE handling generic")
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
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 e141340172ce..2777796a4ed2 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -7043,7 +7043,10 @@ static int rtnl_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
* Some applications which parse netlink manually depend on this.
*/
if (cb->flags & RTNL_FLAG_DUMP_SPLIT_NLM_DONE) {
- if (err < 0 && err != -EMSGSIZE)
+ /* -EMSGSIZE with nothing in the skb is an error, as returning
+ * a length of 0 would end the dump as if it was complete.
+ */
+ if (err < 0 && (err != -EMSGSIZE || !skb->len))
return err;
if (!err)
cb->data = NULL;
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net v2 2/3] ipv6: return EMSGSIZE when first route in dump does not fit
2026-11-10 17:26 [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Stephen Hemminger
2026-11-10 17:26 ` [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit Stephen Hemminger
@ 2026-11-10 17:26 ` Stephen Hemminger
2026-11-12 15:31 ` Ido Schimmel
2026-11-10 17:26 ` [PATCH net v2 3/3] selftests: net: netlink-dumps: test dump of route larger than skb Stephen Hemminger
2026-11-11 2:23 ` [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Hangbin Liu
3 siblings, 1 reply; 10+ messages in thread
From: Stephen Hemminger @ 2026-11-10 17:26 UTC (permalink / raw)
To: netdev
Cc: dsahern, Stephen Hemminger, Ido Schimmel, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
open list
If a route does not fit in an empty dump skb, fib6_dump_node()
suspends the walker and inet6_dump_fib() returns 1. rtnl_dumpit()
then returns skb->len, which is 0, and the dump ends with NLMSG_DONE
and status 0, silently dropping the route and those after it.
This became reachable when multipath routes started being dumped as a
single message with an RTA_MULTIPATH attribute; a wide enough route no
longer fits.
Return -EMSGSIZE in this case, as IPv4 does. This depends on the
rtnl_dumpit() fix in the previous patch.
Fixes: beb1afac518d ("net: ipv6: Add support to dump multipath routes via RTA_MULTIPATH attribute")
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
net/ipv6/ip6_fib.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index e1d49897d6ab..1025267992f0 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -755,6 +755,12 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
unlock:
rcu_read_unlock();
+ /* The walker stopped before adding anything, so the next route
+ * is too large for this skb. Report that instead of letting an
+ * empty skb look like the end of the dump.
+ */
+ if (err > 0 && !skb->len)
+ err = -EMSGSIZE;
if (err <= 0)
fib6_dump_end(cb);
return err;
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net v2 3/3] selftests: net: netlink-dumps: test dump of route larger than skb
2026-11-10 17:26 [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Stephen Hemminger
2026-11-10 17:26 ` [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit Stephen Hemminger
2026-11-10 17:26 ` [PATCH net v2 2/3] ipv6: return EMSGSIZE when first route in dump " Stephen Hemminger
@ 2026-11-10 17:26 ` Stephen Hemminger
2026-11-12 5:27 ` netdev-bot+sashiko
2026-11-11 2:23 ` [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Hangbin Liu
3 siblings, 1 reply; 10+ messages in thread
From: Stephen Hemminger @ 2026-11-10 17:26 UTC (permalink / raw)
To: netdev
Cc: dsahern, Stephen Hemminger, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
open list:KERNEL SELFTEST FRAMEWORK, open list
Add IPv4 and IPv6 multipath routes that do not fit in the first dump
skb of a fresh socket. The dump must return the route or fail with
-EMSGSIZE, not end with status 0.
IPv6 nexthops cannot use lo, so create a dummy device and generalize
lo_set_up() to if_set_up(). Add the IPv4 routing options to config.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
tools/testing/selftests/net/config | 3 +
tools/testing/selftests/net/netlink-dumps.c | 271 +++++++++++++++++++-
2 files changed, 270 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index d4416f8bdcfd..e2f136274e6d 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -29,6 +29,8 @@ CONFIG_IP6_NF_MATCH_RPFILTER=m
CONFIG_IP6_NF_NAT=m
CONFIG_IP6_NF_RAW=m
CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
CONFIG_IP_NF_FILTER=m
CONFIG_IP_NF_IPTABLES=m
CONFIG_IP_NF_IPTABLES_LEGACY=m
@@ -38,6 +40,7 @@ CONFIG_IP_NF_NAT=m
CONFIG_IP_NF_RAW=m
CONFIG_IP_NF_TARGET_REJECT=m
CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_ROUTE_MULTIPATH=y
CONFIG_IP_SCTP=m
CONFIG_IPV6=y
CONFIG_IPV6_FOU=m
diff --git a/tools/testing/selftests/net/netlink-dumps.c b/tools/testing/selftests/net/netlink-dumps.c
index 4a94a25167e2..43f63f3f2289 100644
--- a/tools/testing/selftests/net/netlink-dumps.c
+++ b/tools/testing/selftests/net/netlink-dumps.c
@@ -263,11 +263,11 @@ TEST(close_with_ref)
usleep(100 * 1000);
}
-static int lo_set_up(int sock, bool up)
+static int if_set_up(int sock, const char *name, bool up)
{
struct ifreq ifr = {};
- strcpy(ifr.ifr_name, "lo");
+ strcpy(ifr.ifr_name, name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr))
return -1;
if (up)
@@ -321,12 +321,12 @@ TEST(dump_with_queued_ntf)
sizeof(addr)), 0);
// A large read makes the kernel attempt large dump skbs
- ASSERT_EQ(lo_set_up(inet_sock, true), 0);
+ ASSERT_EQ(if_set_up(inet_sock, "lo", true), 0);
n = recv(netlink_sock, buf, sizeof(buf), 0);
ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
// Leave one notification queued, then start the dump
- ASSERT_EQ(lo_set_up(inet_sock, false), 0);
+ ASSERT_EQ(if_set_up(inet_sock, "lo", false), 0);
n = send(netlink_sock, &dump_links, sizeof(dump_links), 0);
ASSERT_EQ(n, sizeof(dump_links));
@@ -362,4 +362,267 @@ TEST(dump_with_queued_ntf)
close(inet_sock);
}
+#define BIG_RT_TABLE 100
+#define BIG_RT_IFINDEX 100
+#define BIG_RT4_NH 1500
+#define BIG_RT6_NH 500
+
+static const struct {
+ struct nlmsghdr nlhdr;
+ struct ifinfomsg ifm;
+ struct nlattr name_hdr;
+ char name[8];
+ struct nlattr linkinfo_hdr;
+ struct nlattr kind_hdr;
+ char kind[8];
+} new_dummy = {
+ .nlhdr = {
+ .nlmsg_len = sizeof(new_dummy),
+ .nlmsg_type = RTM_NEWLINK,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
+ NLM_F_EXCL,
+ .nlmsg_seq = 1,
+ },
+ .ifm = {
+ .ifi_family = AF_UNSPEC,
+ .ifi_index = BIG_RT_IFINDEX,
+ },
+ .name_hdr = {
+ .nla_len = 4 + sizeof("dummy0"),
+ .nla_type = IFLA_IFNAME,
+ },
+ .name = "dummy0",
+ .linkinfo_hdr = {
+ .nla_len = 4 + 4 + 8,
+ .nla_type = IFLA_LINKINFO,
+ },
+ .kind_hdr = {
+ .nla_len = 4 + sizeof("dummy"),
+ .nla_type = IFLA_INFO_KIND,
+ },
+ .kind = "dummy",
+};
+
+static struct {
+ struct nlmsghdr nlhdr;
+ struct rtmsg rtm;
+ struct nlattr dst_hdr;
+ __u8 dst[4];
+ struct nlattr table_hdr;
+ __u32 table;
+ struct nlattr mp_hdr;
+ struct rtnexthop nh[BIG_RT4_NH];
+} new_route4 = {
+ .nlhdr = {
+ .nlmsg_len = sizeof(new_route4),
+ .nlmsg_type = RTM_NEWROUTE,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
+ NLM_F_EXCL,
+ .nlmsg_seq = 1,
+ },
+ .rtm = {
+ .rtm_family = AF_INET,
+ .rtm_dst_len = 24,
+ .rtm_protocol = RTPROT_STATIC,
+ .rtm_scope = RT_SCOPE_LINK,
+ .rtm_type = RTN_UNICAST,
+ },
+ .dst_hdr = {
+ .nla_len = 4 + 4,
+ .nla_type = RTA_DST,
+ },
+ .dst = { 198, 51, 100, 0 },
+ .table_hdr = {
+ .nla_len = 4 + 4,
+ .nla_type = RTA_TABLE,
+ },
+ .table = BIG_RT_TABLE,
+ .mp_hdr = {
+ .nla_len = 4 + sizeof(new_route4.nh),
+ .nla_type = RTA_MULTIPATH,
+ },
+};
+
+struct rtnexthop6 {
+ struct rtnexthop rtnh;
+ struct nlattr gw_hdr;
+ __u8 gw[16];
+};
+
+static struct {
+ struct nlmsghdr nlhdr;
+ struct rtmsg rtm;
+ struct nlattr dst_hdr;
+ __u8 dst[16];
+ struct nlattr table_hdr;
+ __u32 table;
+ struct nlattr mp_hdr;
+ struct rtnexthop6 nh[BIG_RT6_NH];
+} new_route6 = {
+ .nlhdr = {
+ .nlmsg_len = sizeof(new_route6),
+ .nlmsg_type = RTM_NEWROUTE,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
+ NLM_F_EXCL,
+ .nlmsg_seq = 1,
+ },
+ .rtm = {
+ .rtm_family = AF_INET6,
+ .rtm_dst_len = 64,
+ .rtm_protocol = RTPROT_STATIC,
+ .rtm_scope = RT_SCOPE_UNIVERSE,
+ .rtm_type = RTN_UNICAST,
+ },
+ .dst_hdr = {
+ .nla_len = 4 + 16,
+ .nla_type = RTA_DST,
+ },
+ .dst = { 0x20, 0x01, 0x0d, 0xb8 }, // 2001:db8::/64
+ .table_hdr = {
+ .nla_len = 4 + 4,
+ .nla_type = RTA_TABLE,
+ },
+ .table = BIG_RT_TABLE,
+ .mp_hdr = {
+ .nla_len = 4 + sizeof(new_route6.nh),
+ .nla_type = RTA_MULTIPATH,
+ },
+};
+
+static struct {
+ struct nlmsghdr nlhdr;
+ struct rtmsg rtm;
+} dump_routes = {
+ .nlhdr = {
+ .nlmsg_len = sizeof(dump_routes),
+ .nlmsg_type = RTM_GETROUTE,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
+ .nlmsg_seq = 2,
+ },
+ .rtm = {
+ .rtm_table = BIG_RT_TABLE,
+ },
+};
+
+static int nl_request_ack(int sock, const void *req, size_t len)
+{
+ struct nlmsghdr *nlh;
+ char buf[4096];
+ ssize_t n;
+
+ if (send(sock, req, len, 0) != (ssize_t)len)
+ return -errno;
+ n = recv(sock, buf, sizeof(buf), 0);
+ if (n < 0)
+ return -errno;
+ nlh = (struct nlmsghdr *)buf;
+ if (!NLMSG_OK(nlh, n) || nlh->nlmsg_type != NLMSG_ERROR)
+ return -EPROTO;
+ return *(int *)NLMSG_DATA(nlh);
+}
+
+// Dump table BIG_RT_TABLE on a fresh socket, so the first dump skb is
+// small, and count routes. The table holds only one route, too large for
+// that skb. Returns the status from NLMSG_DONE or NLMSG_ERROR.
+static int dump_big_table(struct __test_metadata *_metadata, int family,
+ int *routes)
+{
+ int netlink_sock, i, ret = 1;
+ char buf[32768];
+ int one = 1;
+ ssize_t n;
+
+ netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ ASSERT_GE(netlink_sock, 0);
+ ASSERT_EQ(setsockopt(netlink_sock, SOL_NETLINK, NETLINK_GET_STRICT_CHK,
+ &one, sizeof(one)), 0);
+
+ dump_routes.rtm.rtm_family = family;
+ n = send(netlink_sock, &dump_routes, sizeof(dump_routes), 0);
+ ASSERT_EQ(n, sizeof(dump_routes));
+
+ *routes = 0;
+ for (i = 0; i < 100 && ret > 0; i++) {
+ struct nlmsghdr *nlh;
+
+ n = recv(netlink_sock, buf, sizeof(buf), 0);
+ ASSERT_GE(n, 0);
+
+ for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, n);
+ nlh = NLMSG_NEXT(nlh, n)) {
+ if (nlh->nlmsg_type == RTM_NEWROUTE)
+ (*routes)++;
+ if (nlh->nlmsg_type == NLMSG_DONE ||
+ nlh->nlmsg_type == NLMSG_ERROR) {
+ ret = *(int *)NLMSG_DATA(nlh);
+ break;
+ }
+ }
+ }
+ EXPECT_LE(ret, 0);
+
+ close(netlink_sock);
+ return ret;
+}
+
+// A route that doesn't fit in the first dump skb must either be dumped or
+// end the dump with EMSGSIZE. It must not end the dump as if complete.
+TEST(dump_large_route)
+{
+ int netlink_sock, inet_sock, i, ret, routes;
+
+ ret = unshare(CLONE_NEWNET);
+ if (ret && errno == EPERM)
+ SKIP(return, "Needs CAP_SYS_ADMIN for a netns");
+ ASSERT_EQ(ret, 0);
+
+ inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
+ ASSERT_GE(inet_sock, 0);
+ netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ ASSERT_GE(netlink_sock, 0);
+
+ ret = nl_request_ack(netlink_sock, &new_dummy, sizeof(new_dummy));
+ if (ret == -EOPNOTSUPP)
+ SKIP(return, "Needs CONFIG_DUMMY");
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(if_set_up(inet_sock, "lo", true), 0);
+ ASSERT_EQ(if_set_up(inet_sock, "dummy0", true), 0);
+
+ for (i = 0; i < BIG_RT4_NH; i++) {
+ new_route4.nh[i].rtnh_len = sizeof(new_route4.nh[i]);
+ new_route4.nh[i].rtnh_ifindex = 1; // lo
+ }
+ ASSERT_EQ(nl_request_ack(netlink_sock, &new_route4,
+ sizeof(new_route4)), 0);
+
+ for (i = 0; i < BIG_RT6_NH; i++) {
+ struct rtnexthop6 *nh = &new_route6.nh[i];
+
+ nh->rtnh.rtnh_len = sizeof(*nh);
+ nh->rtnh.rtnh_ifindex = BIG_RT_IFINDEX;
+ nh->gw_hdr.nla_len = sizeof(nh->gw_hdr) + sizeof(nh->gw);
+ nh->gw_hdr.nla_type = RTA_GATEWAY;
+ nh->gw[0] = 0xfe; // fe80::<i + 1>
+ nh->gw[1] = 0x80;
+ nh->gw[14] = (i + 1) >> 8;
+ nh->gw[15] = (i + 1) & 0xff;
+ }
+ ASSERT_EQ(nl_request_ack(netlink_sock, &new_route6,
+ sizeof(new_route6)), 0);
+
+ // The ACK reads above used large buffers, which raise the dump
+ // skb size for this socket. Dump on fresh sockets instead.
+ close(netlink_sock);
+
+ ret = dump_big_table(_metadata, AF_INET, &routes);
+ if (ret != -EMSGSIZE)
+ EXPECT_EQ(routes, 1);
+
+ ret = dump_big_table(_metadata, AF_INET6, &routes);
+ if (ret != -EMSGSIZE)
+ EXPECT_EQ(routes, 1);
+
+ close(inet_sock);
+}
+
TEST_HARNESS_MAIN
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry
2026-11-10 17:26 [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Stephen Hemminger
` (2 preceding siblings ...)
2026-11-10 17:26 ` [PATCH net v2 3/3] selftests: net: netlink-dumps: test dump of route larger than skb Stephen Hemminger
@ 2026-11-11 2:23 ` Hangbin Liu
3 siblings, 0 replies; 10+ messages in thread
From: Hangbin Liu @ 2026-11-11 2:23 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: netdev, dsahern
On Tue, Nov 10, 2026 at 09:26:15AM -0800, Stephen Hemminger wrote:
> While analyzing one of the OSS Scanner dump reports for iproute2,
> a bug was uncovered in netlink route dump.
> A route dump whose first entry is too large for the dump skb ends with
> NLMSG_DONE and status 0. The route and everything after it is silently
> dropped; "ip route show table 100" prints nothing and exits 0.
>
> It takes some non real world stuff to do this
> like a 500 way multipath route is enough to trigger this.
>
> The series is broken into three parts:
> 1. Fix regression in rtnl_dumpit().
> Do not hide EMSGSIZE on empty skb case.
> 2. Fix IPv6 FIB dump to propagate error properly
> 3. Add a test for this bug.
>
> v2 - drop Assisted-by
> - correct commit message about "ip route get"
>
> Stephen Hemminger (3):
> rtnetlink: return EMSGSIZE when first dump entry does not fit
> ipv6: return EMSGSIZE when first route in dump does not fit
> selftests: net: netlink-dumps: test dump of route larger than skb
>
> net/core/rtnetlink.c | 5 +-
> net/ipv6/ip6_fib.c | 6 +
> tools/testing/selftests/net/config | 3 +
> tools/testing/selftests/net/netlink-dumps.c | 271 +++++++++++++++++++-
> 4 files changed, 280 insertions(+), 5 deletions(-)
>
> --
> 2.53.0
>
For the series
Tested-by: Hangbin Liu <liuhangbin@kylinos.cn>
Reviewed-by: Hangbin Liu <liuhangbin@kylinos.cn>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit
2026-11-10 17:26 ` [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit Stephen Hemminger
@ 2026-11-12 5:27 ` netdev-bot+sashiko
2026-11-12 15:31 ` Ido Schimmel
1 sibling, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 5:27 UTC (permalink / raw)
To: stephen
Cc: netdev, dsahern, davem, edumazet, kuba, pabeni, horms, kuniyu,
idosch, sd, sdf.kernel, parri.andrea, nickgarlis, willemb,
linux-kernel
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The concern: in rtnl_dumpit() (net/core/rtnetlink.c), this patch
only turns an err == -EMSGSIZE returned with an empty skb into an…
--
rtnetlink: return EMSGSIZE when first dump entry does not fit
This patch changes rtnl_dumpit() for handlers registered with
RTNL_FLAG_DUMP_SPLIT_NLM_DONE. With the patch, an -EMSGSIZE return on an
empty skb is passed back to user space as an error and is no longer
dropped. Before, a first entry too large for the initial NLMSG_GOODSIZE
skb ended the dump with NLMSG_DONE and status 0.
> diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
> index e141340172ce3..2777796a4ed2c 100644
> --- a/net/core/rtnetlink.c
> +++ b/net/core/rtnetlink.c
> @@ -7043,7 +7043,10 @@ static int rtnl_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
> * Some applications which parse netlink manually depend on this.
> */
> if (cb->flags & RTNL_FLAG_DUMP_SPLIT_NLM_DONE) {
> - if (err < 0 && err != -EMSGSIZE)
> + /* -EMSGSIZE with nothing in the skb is an error, as returning
> + * a length of 0 would end the dump as if it was complete.
> + */
> + if (err < 0 && (err != -EMSGSIZE || !skb->len))
> return err;
[Severity: Low]
Does this also cover the IPv6 route dump? inet6_dump_fib() is registered
with RTNL_FLAG_DUMP_SPLIT_NLM_DONE too, but it does not use -EMSGSIZE to
report that a route did not fit.
When the first route does not fit, rt6_dump_route() returns 0, and
fib6_dump_node() turns that into a positive return:
net/ipv6/ip6_fib.c:fib6_dump_node() {
...
res = rt6_dump_route(rt, w->args, w->skip_in_node);
if (res >= 0) {
/* Frame is full, suspend walking */
w->leaf = rt;
...
w->skip_in_node += res;
return 1;
}
...
}
inet6_dump_fib() passes that positive value back up. With err > 0, the
new check above never fires, and rtnl_dumpit() still reaches
"return skb->len" with skb->len == 0.
With only this patch applied, would an oversized IPv6 multipath route
still end the dump with NLMSG_DONE and status 0?
The AF_UNSPEC path has the same dependency. rtnl_dump_all() ends with
"return skb->len ? : ret", so it only reports an error if the IPv6
handler returns -EMSGSIZE.
The next patch in the series, "ipv6: return EMSGSIZE when first route in
dump does not fit", appears to close this gap. It adds the following to
inet6_dump_fib():
if (err > 0 && !skb->len)
err = -EMSGSIZE;
The selftest "selftests: net: netlink-dumps: test dump of route larger
than skb" also checks both AF_INET and AF_INET6.
The commit message here says in general terms that an oversized route
entry silently truncates the dump. Could it say that this patch only
fixes handlers that return -EMSGSIZE, and that the IPv6 case is fixed
by the following patch?
> if (!err)
> cb->data = NULL;
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261110172925.3293312-1-stephen%40networkplumber.org
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net v2 3/3] selftests: net: netlink-dumps: test dump of route larger than skb
2026-11-10 17:26 ` [PATCH net v2 3/3] selftests: net: netlink-dumps: test dump of route larger than skb Stephen Hemminger
@ 2026-11-12 5:27 ` netdev-bot+sashiko
0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 5:27 UTC (permalink / raw)
To: stephen
Cc: netdev, dsahern, davem, edumazet, kuba, pabeni, horms, kuniyu,
idosch, sd, sdf.kernel, parri.andrea, nickgarlis, willemb,
linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] dump_large_route() adds an IPv4 multipath route with
BIG_RT4_NH = 1500 gateway-less nexthops.
- [Low] In tools/testing/selftests/net/netlink-dumps.c, nl_request_ack()
cannot decode an error ACK for either of the two large RTM_NEWROUTE…
- [Low] The comment in dump_large_route() before close(netlink_sock) says
"The ACK reads above used large buffers, which raise the dump skb size…
--
selftests: net: netlink-dumps: test dump of route larger than skb
This commit adds a dump_large_route test. The test installs IPv4 and
IPv6 multipath routes that are too large for the first dump skb of a
fresh socket. It then checks that the dump either returns the route or
fails with -EMSGSIZE.
The commit also creates a dummy device for the IPv6 nexthops,
generalizes lo_set_up() to if_set_up(), and adds the IPv4 routing
options to the config.
> diff --git a/tools/testing/selftests/net/netlink-dumps.c b/tools/testing/selftests/net/netlink-dumps.c
> index 4a94a25167e25..43f63f3f22895 100644
> --- a/tools/testing/selftests/net/netlink-dumps.c
> +++ b/tools/testing/selftests/net/netlink-dumps.c
[ ... ]
> @@ -362,4 +362,267 @@ TEST(dump_with_queued_ntf)
[ ... ]
> +static int nl_request_ack(int sock, const void *req, size_t len)
> +{
> + struct nlmsghdr *nlh;
> + char buf[4096];
> + ssize_t n;
> +
> + if (send(sock, req, len, 0) != (ssize_t)len)
> + return -errno;
> + n = recv(sock, buf, sizeof(buf), 0);
> + if (n < 0)
> + return -errno;
> + nlh = (struct nlmsghdr *)buf;
> + if (!NLMSG_OK(nlh, n) || nlh->nlmsg_type != NLMSG_ERROR)
> + return -EPROTO;
[Severity: Low]
Can nl_request_ack() report the real errno when the kernel rejects one
of the large RTM_NEWROUTE requests?
On error, netlink_ack() copies the whole original request into the
reply unless NETLINK_CAP_ACK is set, and this test never sets it:
net/netlink/af_netlink.c:netlink_ack() {
...
if (err && !test_bit(NETLINK_F_CAP_ACK, &nlk->flags))
payload += nlmsg_len(nlh);
...
}
new_route4 is 48 + 1500 * 8 = 12048 bytes and new_route6 is
60 + 500 * 28 = 14060 bytes, so neither error ACK fits in buf[4096].
recv() truncates the datagram and returns 4096. NLMSG_OK() then fails
because nlmsg_len > n, and the helper returns -EPROTO without reading
the errno at NLMSG_DATA(nlh).
The ASSERT_EQ() calls in dump_large_route() still fail when they
should. But every route-add failure (-ENOBUFS, -EINVAL, -ENETUNREACH,
and so on) gets printed as -71.
Would setting NETLINK_CAP_ACK on the socket fix this? Another option is
to read with MSG_PEEK | MSG_TRUNC into a buffer big enough for the
copied request.
> + return *(int *)NLMSG_DATA(nlh);
> +}
[ ... ]
> +TEST(dump_large_route)
> +{
> + int netlink_sock, inet_sock, i, ret, routes;
[ ... ]
> + for (i = 0; i < BIG_RT4_NH; i++) {
> + new_route4.nh[i].rtnh_len = sizeof(new_route4.nh[i]);
> + new_route4.nh[i].rtnh_ifindex = 1; // lo
> + }
> + ASSERT_EQ(nl_request_ack(netlink_sock, &new_route4,
> + sizeof(new_route4)), 0);
[Severity: Medium]
Could this route add fail spuriously on a machine with fragmented
memory?
With BIG_RT4_NH = 1500, fib_create_info() allocates the fib_info and
all 1500 fib_nh entries as one contiguous block:
net/ipv4/fib_semantics.c:fib_create_info() {
...
fi = kzalloc_flex(*fi, fib_nh, nhs);
...
}
kzalloc_flex() expands to plain kzalloc(), not kvzalloc(). struct
fib_nh is about 112-120 bytes on 64-bit, so the request is about
170-180 KB. That is an order-6 allocation on 4K-page kernels.
If that allocation fails, fib_create_info() returns -ENOBUFS. Because
__GFP_NOWARN is not set, the kernel also logs a page allocation failure
warning in dmesg. The test then fails for reasons unrelated to the dump
behaviour it checks.
The count is this high only because each gateway-less IPv4 nexthop adds
just 8 bytes to the dumped route. Could each IPv4 nexthop get an
RTA_GATEWAY, as the IPv6 nexthops do? That would reach the same dump
size with far fewer kernel-side fib_nh entries.
[ ... ]
> + ASSERT_EQ(nl_request_ack(netlink_sock, &new_route6,
> + sizeof(new_route6)), 0);
> +
> + // The ACK reads above used large buffers, which raise the dump
> + // skb size for this socket. Dump on fresh sockets instead.
> + close(netlink_sock);
[Severity: Low]
Is this comment accurate? The ACK reads in nl_request_ack() use a
4096-byte buffer, so max_recvmsg_len for this socket only reaches 4096.
netlink_dump() uses max_recvmsg_len only when it exceeds
alloc_min_size:
net/netlink/af_netlink.c:netlink_dump() {
...
alloc_min_size = max_t(int, cb->min_dump_alloc, NLMSG_GOODSIZE);
max_recvmsg_len = READ_ONCE(nlk->max_recvmsg_len);
if (alloc_min_size < max_recvmsg_len) {
...
}
On 4K-page kernels NLMSG_GOODSIZE is SKB_WITH_OVERHEAD(4096), so the
dump skb grows by only a few hundred bytes. With 8K or larger pages,
nothing changes. Either way, the 12 KB and 14 KB routes would still not
fit in the first dump skb if this socket were reused.
Opening fresh sockets is fine, but could the comment be reworded? The
buffer that actually raises later dump skb sizes is the 32 KB one in
dump_big_table().
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261110172925.3293312-1-stephen%40networkplumber.org
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net v2 2/3] ipv6: return EMSGSIZE when first route in dump does not fit
2026-11-10 17:26 ` [PATCH net v2 2/3] ipv6: return EMSGSIZE when first route in dump " Stephen Hemminger
@ 2026-11-12 15:31 ` Ido Schimmel
2026-11-12 16:17 ` Stephen Hemminger
0 siblings, 1 reply; 10+ messages in thread
From: Ido Schimmel @ 2026-11-12 15:31 UTC (permalink / raw)
To: Stephen Hemminger
Cc: netdev, dsahern, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, open list
On Tue, Nov 10, 2026 at 09:26:17AM -0800, Stephen Hemminger wrote:
> If a route does not fit in an empty dump skb, fib6_dump_node()
> suspends the walker and inet6_dump_fib() returns 1. rtnl_dumpit()
> then returns skb->len, which is 0, and the dump ends with NLMSG_DONE
> and status 0, silently dropping the route and those after it.
>
> This became reachable when multipath routes started being dumped as a
> single message with an RTA_MULTIPATH attribute; a wide enough route no
> longer fits.
>
> Return -EMSGSIZE in this case, as IPv4 does. This depends on the
> rtnl_dumpit() fix in the previous patch.
>
> Fixes: beb1afac518d ("net: ipv6: Add support to dump multipath routes via RTA_MULTIPATH attribute")
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
FWIW, given that "It takes some non real world stuff to do this", I
think these patches can be routed via net-next.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit
2026-11-10 17:26 ` [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit Stephen Hemminger
2026-11-12 5:27 ` netdev-bot+sashiko
@ 2026-11-12 15:31 ` Ido Schimmel
1 sibling, 0 replies; 10+ messages in thread
From: Ido Schimmel @ 2026-11-12 15:31 UTC (permalink / raw)
To: Stephen Hemminger
Cc: netdev, dsahern, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Kuniyuki Iwashima, Sabrina Dubroca,
Stanislav Fomichev, Andrea Parri, Nikolaos Gkarlis,
Willem de Bruijn, open list
On Tue, Nov 10, 2026 at 09:26:16AM -0800, Stephen Hemminger wrote:
> If a route entry is too large for the dump skb, the dump is silently
> truncated and no error is returned.
>
> For handlers with RTNL_FLAG_DUMP_SPLIT_NLM_DONE, rtnl_dumpit() drops
> -EMSGSIZE and returns skb->len. With an empty skb this is 0, so the
> dump ends with NLMSG_DONE and status 0, and the remaining entries are
> lost.
>
> The first dump skb is allocated from __netlink_dump_start() while
> max_recvmsg_len is still 0, so it is only NLMSG_GOODSIZE. On a 4K page
> kernel that is under 4000 bytes, and a 500 way multipath route does not
> fit:
>
> ip link add dummy0 type dummy
> ip link set dummy0 up
> ip route add 198.51.100.0/24 table 100 \
> $(for i in $(seq 500); do echo -n "nexthop dev dummy0 "; done)
> ip route show table 100
>
> The dump prints nothing and exits 0
>
> Only ignore -EMSGSIZE when the skb has data, so that an entry which
> does not fit in an empty skb reports the error to user space.
>
> Fixes: 5b4b62a169e1 ("rtnetlink: make the "split" NLM_DONE handling generic")
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net v2 2/3] ipv6: return EMSGSIZE when first route in dump does not fit
2026-11-12 15:31 ` Ido Schimmel
@ 2026-11-12 16:17 ` Stephen Hemminger
0 siblings, 0 replies; 10+ messages in thread
From: Stephen Hemminger @ 2026-11-12 16:17 UTC (permalink / raw)
To: Ido Schimmel
Cc: netdev, dsahern, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, open list
On Thu, 12 Nov 2026 17:31:15 +0200
Ido Schimmel <idosch@nvidia.com> wrote:
> >
> > Return -EMSGSIZE in this case, as IPv4 does. This depends on the
> > rtnl_dumpit() fix in the previous patch.
> >
> > Fixes: beb1afac518d ("net: ipv6: Add support to dump multipath routes via RTA_MULTIPATH attribute")
> > Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
>
> Reviewed-by: Ido Schimmel <idosch@nvidia.com>
>
> FWIW, given that "It takes some non real world stuff to do this", I
> think these patches can be routed via net-next.
The exaggerated security argument is that malicious root in network namespace
can do this and hide/crash ip commands and/or routing daemons in other namespace.
Once again the analyzer is stretching pretty far to argue that.
But then again with the current "lets chain $.01 vulnerabilities" world maybe not.
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-12 16:18 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 17:26 [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Stephen Hemminger
2026-11-10 17:26 ` [PATCH net v2 1/3] rtnetlink: return EMSGSIZE when first dump entry does not fit Stephen Hemminger
2026-11-12 5:27 ` netdev-bot+sashiko
2026-11-12 15:31 ` Ido Schimmel
2026-11-10 17:26 ` [PATCH net v2 2/3] ipv6: return EMSGSIZE when first route in dump " Stephen Hemminger
2026-11-12 15:31 ` Ido Schimmel
2026-11-12 16:17 ` Stephen Hemminger
2026-11-10 17:26 ` [PATCH net v2 3/3] selftests: net: netlink-dumps: test dump of route larger than skb Stephen Hemminger
2026-11-12 5:27 ` netdev-bot+sashiko
2026-11-11 2:23 ` [PATCH net v2 0/3] rtnetlink: fix truncation of dump entry Hangbin Liu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox