NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
kern/60639: ICMP error rate limit is side channel for UDP port usage
>Number: 60639
>Category: kern
>Synopsis: ICMP error rate limit is side channel for UDP port usage
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Tue Aug 25 04:40:00 +0000 2026
>Originator: Taylor R Campbell
>Release: current, 11, 10, 9, ...
>Organization:
The NetUDP Foundaport, Inc.
>Environment:
>Description:
NetBSD's ICMP error responses are globally rate-limited (to
100 per second by default), and this global rate limit serves
as a side channel for whether error responses have been sent
to _another_ host.
This can be used to discover 4-tuples of `connected' UDP
sockets.
For example, suppose a DNS client on host 192.0.2.42 does
s = socket(AF_INET, SOCK_DGRAM, 0);
connect(s, 8.8.8.8:53);
The kernel will randomly select a port number, say 60666.
This creates a 4-tuple (192.0.2.42:60666, 8.8.8.8:53), which
an adversary wants to discover in order to send a forged DNS
reply to it to poison its cache. The only part of this
4-tuple not known to the adversary is the client port, 60666.
The adversary can't search for the client port by simply
sending UDP packets to various candidates and seeing which
ones return ICMP errors, because the ICMP errors of interest
will only go to 8.8.8.8.
The adversary can, however, about once per second:
1. Send 100 UDP packets with forged source addresses 49152,
49153, 49154, ..., 49251 that will match the 4-tuples
(192.0.2.42:49152, 8.8.8.8:53),
(192.0.2.42:49153, 8.8.8.8:53),
(192.0.2.42:49154, 8.8.8.8:53),
and so on.
2. Wait for them to likely arrive, say 100ms.
3. Send a packet, from their true source address, that always
provokes an ICMP error response -- unless rate-limited
because the 100 packets in step (1) hit the limit.
This way they can test in one second whether for any port
number P of 100 candidates there is a `connected' 4-tuple
(192.0.2.42:P, 8.8.8.8:53): if they get an ICMP error back,
one of the candidates must have been open. And thus they can
scan 100 ports/sec, in optimal conditions.
Under less optimal conditions, e.g. if there are multiple
adversaries scanning at the same time, they could
alternatively send 100 packets with forged source addresses
all for the same 4-tuple (192.0.2.42:49152, 8.8.8.8:53), and
then send 100 packets with true source addresses to provoke
ICMP errors in order to get greater certainty about a single
candidate port number 49152. This approach gives about one
port per second.
References:
- https://www.saddns.net/
- Keyu Man, Zhiyun Qian, Zhongjie Wang, Xiaofeng Zheng,
Youjun Huang, Haixin Duan, `DNS Cache Poisoning Attack
Reloaded: Revolutions with Side Channels', in Proceedings
of ACM Conference on Computer and Communications Security
(CCS`20), November 9--13, 2020.
https://dl.acm.org/doi/pdf/10.1145/3372297.3417280
Reported to us by Michael Blunt.
>How-To-Repeat:
fun afternoon project
>Fix:
Some possible mitigations:
1. Instead of always counting +1 in icmp_error ->
ppsratecheck, we could roll a fair three-sided die (which
is easier to manifest in a computer than in a physical D&D
game) to decide between {0, +1, +2}.
The average count per packet toward the rate limit is the
same, but there is some variance in how fast it hits the
100-packet threshold. (Or, really, any probability
distribution on {0, +1, +2} with p(0) = p(+2), such as
popcount of a uniform random 2-bit string.)
This might be enough to thwart the fast-and-loose version
of the attack, but for the slow-and-reliable one, it's
just a matter of getting 0 or 100 ICMP errors (status quo)
vs getting <30 or >70 ICMP errors (three-sided die
mitigation).
2. Introduce per-peer rate limits.
This has to be done carefully: we can't keep state for an
unbounded number of peers, but as soon as we bound the
number of peers, that is effectively a global limit.
For example, if we keep state for 50 peers, the adversary
just needs to provoke 49 ICMP errors from other addresses,
and then they can probe a 4-tuple for the target address.
3. Tell applications they're doing it wrong.
Recursive resolvers like Unbound usually choose an
independent source port per DNS query, in which case the
window of attack is -- in the worst case for the defender,
or best case for the attacker -- limited to a few seconds.
In that case, this is probably not even worth mitigating!
Applications querying recursive resolvers, though, will
often reuse the same UDP `connection'. (libc does this,
for instance.) When they're talking to 127.0.0.1, that's
fine. When they're talking to 8.8.8.8, that's not fine.
So maybe -- even aside from the privacy impact of
affirmatively telling Google up front about all the web
sites you visit -- it is just a bad idea to use public
recursive resolvers directly. (As a forward for Unbound,
perhaps they're fine.)
Home |
Main Index |
Thread Index |
Old Index