| From eac2a626301c72fd3b48160052166f35dbe3ffae Mon Sep 17 00:00:00 2001 | |
| From: Sasha Levin <sashal@kernel.org> | |
| Date: Fri, 28 Aug 2026 22:23:43 +0300 | |
| Subject: ipv6: udp: Create exceptions before socket matching | |
| From: Ido Schimmel <idosch@nvidia.com> | |
| [ Upstream commit ac76cab50e899a7346408b8d3c3a4192c2eefb9f ] | |
| Currently, when ICMPv6 Packet Too Big and Redirect Message packets are | |
| locally delivered and quote a UDP packet, an exception is only created | |
| in the IPv6 exception cache if the kernel can match the UDP packet to an | |
| existing socket. | |
| This behavior allows off-path attackers to conduct a side-channel attack | |
| on the exception cache in order to discover the ephemeral port used by a | |
| connected UDP socket. | |
| Commit 4785305c05b2 ("ipv6: use siphash in rt6_exception_hash()") and | |
| commit a00df2caffed ("ipv6: make exception cache less predictible") tried | |
| to mitigate such attacks by making it harder for attackers to discover | |
| hash collisions in the exception cache and by randomizing the number of | |
| exceptions a hash bucket can hold, respectively. Unfortunately, both of | |
| the mitigations can be bypassed. | |
| Instead, mitigate such attacks by always creating an exception, even | |
| before trying to find a matching socket. Do that by calling | |
| ip6_update_pmtu() and ip6_redirect(), the helpers used when the quoted | |
| packet did not originate from a socket. | |
| This means that guesses (right or wrong) from an off-path attacker will | |
| always result in an exception being created or updated in the cache that | |
| the attacker can observe. | |
| Pass the ifindex of the ingress device and the default uid, in a similar | |
| fashion to icmpv6_err(). Unlike IPv4, an oif of 0 would not match any | |
| nexthop in ip6_redirect_nh_match() and no exception would be created in | |
| response to a Redirect Message. | |
| Note that this does not allow attackers to create exceptions that they | |
| could not create before, as both helpers can already be reached with | |
| little to no validation. For example, by sending an ICMPv6 error that | |
| quotes an ICMPv6 Echo Reply or one that quotes a UDP source port that | |
| matches a wildcard socket. | |
| Also note that in the good case (matched socket) the above scheme comes | |
| at the cost of an extra route lookup, as the no socket helpers perform | |
| their own lookup before the one performed by ip6_sk_update_pmtu() / | |
| ip6_sk_redirect(). When the two resolve to different nexthops, it also | |
| results in two exceptions being created for the same destination IP. One | |
| in the exception cache of the nexthop resolved by the no socket helpers | |
| and another in the exception cache of the nexthop used by the socket. | |
| Fixes: 2b760fcf5cfb ("ipv6: hook up exception table to store dst cache") | |
| Cc: stable@vger.kernel.org | |
| Reported-by: Amit Klein <aksecurity@gmail.com> | |
| Reported-by: Noam Caspi <noam.caspi@mail.huji.ac.il> | |
| Signed-off-by: Ido Schimmel <idosch@nvidia.com> | |
| Reviewed-by: David Ahern <dsahern@kernel.org> | |
| Link: https://patch.msgid.link/20260828192344.2596928-4-idosch@nvidia.com | |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> | |
| Signed-off-by: Sasha Levin <sashal@kernel.org> | |
| --- | |
| net/ipv6/udp.c | 13 +++++++++++++ | |
| 1 file changed, 13 insertions(+) | |
| diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c | |
| index 15e032194eccc..b73cf02ac1b6e 100644 | |
| --- a/net/ipv6/udp.c | |
| +++ b/net/ipv6/udp.c | |
| @@ -690,6 +690,17 @@ static struct sock *__udp6_lib_err_encap(struct net *net, | |
| return sk; | |
| } | |
| +static void udpv6_err_update_exception(struct net *net, struct sk_buff *skb, | |
| + u8 type, __be32 info) | |
| +{ | |
| + if (type == ICMPV6_PKT_TOOBIG) | |
| + ip6_update_pmtu(skb, net, info, skb->dev->ifindex, 0, | |
| + sock_net_uid(net, NULL)); | |
| + else if (type == NDISC_REDIRECT) | |
| + ip6_redirect(skb, net, skb->dev->ifindex, 0, | |
| + sock_net_uid(net, NULL)); | |
| +} | |
| + | |
| static int udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, | |
| u8 type, u8 code, int offset, __be32 info) | |
| { | |
| @@ -703,6 +714,8 @@ static int udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, | |
| int harderr; | |
| int err; | |
| + udpv6_err_update_exception(net, skb, type, info); | |
| + | |
| daddr = seg6_get_daddr(skb, opt) ? : &hdr->daddr; | |
| saddr = &hdr->saddr; | |
| sk = __udp6_lib_lookup(net, daddr, uh->dest, saddr, uh->source, | |
| -- | |
| 2.53.0 | |