ipv4: Fix fib_nlmsg_size() for RTA_VIA nexthops

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: d15662682d ("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>
This commit is contained in:
Zihan Xi
2026-07-30 12:59:26 +00:00
committed by Jakub Kicinski
parent 5d9686af29
commit 4ff9548d84

View 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;
if (nhc->nhc_lwtstate) {
/* RTA_ENCAP_TYPE */
nh_encapsize += lwtunnel_get_encap_size(
nhc->nhc_lwtstate);
/* RTA_ENCAP */
nh_encapsize += nla_total_size(2);
nhsize = fib_nexthop_nlmsg_size(nhc, nhs != 1);
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;