| 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 | |