[PATCH net] net: ipv6: reject collect_md tunnel route without inet6_dev

From: Yingjie Wang

Date: Sat Nov 07 2026 - 14:04:12 EST


ip6_tnl_xmit() selects an outer source address for a collect_md tunnel
with an unspecified metadata source by dereferencing
ip6_dst_idev(dst)->dev.

A route using a nexthop object can remain after its output device loses
its inet6_dev when its MTU is lowered below IPV6_MIN_MTU. The route still
has no error, so ip6_route_output() returns it with rt6i_idev == NULL.

An unprivileged user with CAP_NET_ADMIN in a private user and network
namespace can create this state and send an encapsulated packet through
an external ip6tnl device. The unchecked dereference then causes a NULL
pointer dereference in ip6_tnl_xmit(). With panic_on_oops enabled, this
panics the host.

A similar missing inet6_dev check was fixed in
commit 1b9b5323725e ("netfilter: ip6t_rpfilter: reject routes
without inet6_dev").

However, the collect_md transmit path in ip6_tnl_xmit()
still dereferences ip6_dst_idev(dst)->dev without checking
whether the route has an associated inet6_dev.

Check the route's inet6_dev before using its device. The unmodified
net/main KASAN kernel reproduced the NULL dereference, and the same
reproducer on the fixed kernel rejected the packet without a KASAN report,
oops, or panic. The fixed kernel also passed the ip6_tunnel.sh selftest.

Fixes: 8d79266bc48c ("ip6_tunnel: add collect_md mode to IPv6 tunnels")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yingjie Wang <1075151112@xxxxxx>
---
net/ipv6/ip6_tunnel.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 64a184bca467..547c23c3121b 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1187,10 +1187,14 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
dst = NULL;
goto tx_err_link_failure;
}
- if (t->parms.collect_md && ipv6_addr_any(&fl6->saddr) &&
- ipv6_dev_get_saddr(net, ip6_dst_idev(dst)->dev,
- &fl6->daddr, 0, &fl6->saddr))
- goto tx_err_link_failure;
+ if (t->parms.collect_md && ipv6_addr_any(&fl6->saddr)) {
+ struct inet6_dev *idev = ip6_dst_idev(dst);
+
+ if (!idev ||
+ ipv6_dev_get_saddr(net, idev->dev, &fl6->daddr,
+ 0, &fl6->saddr))
+ goto tx_err_link_failure;
+ }
ndst = dst;
}


base-commit: 30c71476d842b094651bd7712f6c8ee8716147d0
--
2.43.0