]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ipv4: Fix fib_nlmsg_size() for RTA_VIA nexthops
authorZihan Xi <zihanx@nebusec.ai>
Thu, 30 Jul 2026 12:59:26 +0000 (12:59 +0000)
committerJakub Kicinski <kuba@kernel.org>
Tue, 4 Aug 2026 01:29:50 +0000 (18:29 -0700)
fib_nlmsg_size() still estimates nexthop space as if every gateway is
encoded as an IPv4 RTA_GATEWAY attribute. IPv4 routes can also carry an
IPv6 gateway, which fib_nexthop_info() dumps as RTA_VIA.

As a result, route notifications can allocate an skb that is too small.
fib_dump_info() then fails with -EMSGSIZE and rtmsg_fib() hits the
WARN_ON() that marks such failures as a fib_nlmsg_size() bug. With
panic_on_warn set, this becomes a kernel panic.

Mirror the actual nexthop dump layout in fib_nlmsg_size(): account for
IPv6 nexthop gateways dumped as RTA_VIA, for the no-header rtnexthop
layout used inside RTA_MULTIPATH, and for RTA_FLOW only when it is
actually present.

Fixes: d15662682db2 ("ipv4: Allow ipv6 gateway with ipv4 routes")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/6f53fa797fcaeb26966432ed7ae9bb87c4961f37.1785411220.git.zihanx@nebusec.ai
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/ipv4/fib_semantics.c

index 4f3c0740dde913fcc7c747b8ffae68d3ca4a9f69..78f84ae3ee12099aeea78aac18c6bd9030908af2 100644 (file)
@@ -490,6 +490,34 @@ int ip_fib_check_default(__be32 gw, struct net_device *dev)
        return -1;
 }
 
+static size_t fib_nexthop_nlmsg_size(const struct fib_nh_common *nhc,
+                                    bool skip_oif)
+{
+       size_t nhsize = 0;
+
+       switch (nhc->nhc_gw_family) {
+       case AF_INET:
+               nhsize += nla_total_size(4); /* RTA_GATEWAY */
+               break;
+       case AF_INET6:
+               nhsize += nla_total_size(sizeof(struct rtvia) +
+                                        sizeof(struct in6_addr));
+               break;
+       }
+
+       if (!skip_oif && nhc->nhc_dev)
+               nhsize += nla_total_size(4); /* RTA_OIF */
+
+       if (nhc->nhc_lwtstate) {
+               /* RTA_ENCAP */
+               nhsize += lwtunnel_get_encap_size(nhc->nhc_lwtstate);
+               /* RTA_ENCAP_TYPE */
+               nhsize += nla_total_size(2);
+       }
+
+       return nhsize;
+}
+
 size_t fib_nlmsg_size(struct fib_info *fi)
 {
        size_t payload = NLMSG_ALIGN(sizeof(struct rtmsg))
@@ -507,32 +535,35 @@ size_t fib_nlmsg_size(struct fib_info *fi)
                payload += nla_total_size(4); /* RTA_NH_ID */
 
        if (nhs) {
-               size_t nh_encapsize = 0;
-               /* Also handles the special case nhs == 1 */
-
-               /* each nexthop is packed in an attribute */
-               size_t nhsize = nla_total_size(sizeof(struct rtnexthop));
+               size_t mpsize = 0;
                unsigned int i;
 
-               /* may contain flow and gateway attribute */
-               nhsize += 2 * nla_total_size(4);
-
-               /* grab encap info */
                for (i = 0; i < fib_info_num_path(fi); i++) {
                        struct fib_nh_common *nhc = fib_info_nhc(fi, i);
+                       size_t nhsize;
+
+                       nhsize = fib_nexthop_nlmsg_size(nhc, nhs != 1);
 
-                       if (nhc->nhc_lwtstate) {
-                               /* RTA_ENCAP_TYPE */
-                               nh_encapsize += lwtunnel_get_encap_size(
-                                               nhc->nhc_lwtstate);
-                               /* RTA_ENCAP */
-                               nh_encapsize +=  nla_total_size(2);
+                       if (nhs != 1)
+                               nhsize += NLA_ALIGN(sizeof(struct rtnexthop));
+
+#ifdef CONFIG_IP_ROUTE_CLASSID
+                       if (nhc->nhc_family == AF_INET) {
+                               struct fib_nh *nh;
+
+                               nh = container_of(nhc, struct fib_nh, nh_common);
+                               if (nh->nh_tclassid)
+                                       nhsize += nla_total_size(4);
                        }
+#endif
+                       if (nhs == 1)
+                               payload += nhsize;
+                       else
+                               mpsize += nhsize;
                }
 
-               /* all nexthops are packed in a nested attribute */
-               payload += nla_total_size((nhs * nhsize) + nh_encapsize);
-
+               if (nhs != 1)
+                       payload += nla_total_size(mpsize);
        }
 
        return payload;