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