| From 83ffcaca0d5e1ea1d71fb3a1825abe6d26463e8d Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Fri, 28 Aug 2026 22:23:42 +0300 |
| Subject: ipv4: udp: Create exceptions before socket matching |
| |
| From: Ido Schimmel <idosch@nvidia.com> |
| |
| [ Upstream commit 4c3499f79f8c7e8561266bcc220a18538cec0458 ] |
| |
| Currently, when ICMP Fragmentation Needed and Redirect Message packets |
| are locally delivered and quote a UDP packet, a FIB nexthop exception |
| (FNHE) is only created 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 FNHE cache in order to discover the ephemeral port used by a |
| connected UDP socket. |
| |
| Commit 6457378fe796 ("ipv4: use siphash instead of Jenkins in |
| fnhe_hashfun()") and commit 67d6d681e15b ("ipv4: make exception cache |
| less predictible") tried to mitigate such attacks by making it harder |
| for attackers to discover hash collisions in the FNHE 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 a FNHE, even before |
| trying to find a matching socket. Do that by calling ipv4_update_pmtu() |
| and ipv4_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 a FNHE being created or updated in the cache that the |
| attacker can observe. |
| |
| Pass an oif of 0, in a similar fashion to icmp_err(). This is also the |
| oif used by the socket path for sockets that are not bound to a device. |
| |
| Note that this does not allow attackers to create FNHEs that they could |
| not create before, as both helpers can already be reached with little to |
| no validation. For example, by sending an ICMP error that quotes an ICMP |
| 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 ipv4_sk_update_pmtu() / |
| ipv4_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 FNHE cache of the nexthop resolved by the no socket helpers and |
| another in the FNHE cache of the nexthop used by the socket. |
| |
| Fixes: 4895c771c7f0 ("ipv4: Add FIB nexthop exceptions.") |
| 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-3-idosch@nvidia.com |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| net/ipv4/udp.c | 11 +++++++++++ |
| 1 file changed, 11 insertions(+) |
| |
| diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c |
| index 59afce196079a..a50dccfbf4205 100644 |
| --- a/net/ipv4/udp.c |
| +++ b/net/ipv4/udp.c |
| @@ -908,6 +908,15 @@ static struct sock *__udp4_lib_err_encap(struct net *net, |
| return sk; |
| } |
| |
| +static void udp_err_update_exception(struct net *net, struct sk_buff *skb, |
| + int type, int code, u32 info) |
| +{ |
| + if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) |
| + ipv4_update_pmtu(skb, net, info, 0, IPPROTO_UDP); |
| + else if (type == ICMP_REDIRECT) |
| + ipv4_redirect(skb, net, 0, IPPROTO_UDP); |
| +} |
| + |
| /* |
| * This routine is called by the ICMP module when it gets some |
| * sort of error condition. If err < 0 then the socket should |
| @@ -931,6 +940,8 @@ int udp_err(struct sk_buff *skb, u32 info) |
| int harderr; |
| int err; |
| |
| + udp_err_update_exception(net, skb, type, code, info); |
| + |
| uh = (struct udphdr *)(skb->data + (iph->ihl << 2)); |
| sk = __udp4_lib_lookup(net, iph->daddr, uh->dest, |
| iph->saddr, uh->source, skb->dev->ifindex, |
| -- |
| 2.53.0 |
| |