* [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
* 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 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
* [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
* 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 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
* [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 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 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
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