Subject: [quicwg/base-drafts] amplification attack using Retry and VN triggered by coalesced Initial packets (#2259)

From: Marten Seemann <[email-masked]>
Date: Wed, 26 Dec 2018 08:29:47 -0800

An Initial packet can be made pretty small, if I counted correctly the smallest size for a valid Initial is 18 bytes. That means that if I coalesce 67 of these packets, I can fill up one UDP datagram that fulfills the minimum size requirement of 1200 byte.

A naive server implementation might first split the coalesced packet into those 67 QUIC packets, decide that it wants to do address validation and then send a Retry packet for every one of them. Considering that a token will be at least around 40 bytes long (if the server is using authenticated encryption), and that there's UDP and IP overhead, that's a nice amplification factor in itself already, in addition to the fact that the attacker just converted a single large datagram into a lot of small datagrams.
I'm not sure if this is covered by the text yet. We say that

> Prior to validating the client address, servers MUST NOT send more than three times as many bytes as the number of bytes they have received.

However, since the retries are supposed to be stateless, it's not clear how this would work.

The same applies to Version Negotiation packets. With the coalesced packet described above, a single packet could be used to elicit the sending of 67 VN packets. We currently have some text that a server MAY limit the number of VN packets it sends, but unless we come up with a better defense against this attack, I think we'll need stronger language here.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259

---

From: Nick Banks <[email-masked]>
Date: Thu, 27 Dec 2018 08:05:41 -0800

I agree this could be a problem if an implementation processes each packet completely independently such as the following text requires:

> The receiver of coalesced QUIC packets MUST individually process each QUIC packet and separately acknowledge them, as if they were received as the payload of different UDP datagrams.

I think the best way to handle this would be to add some text that specifically states that if a server sends a Retry or VN in response to a QUIC packet, the rest of the UDP payload should be discarded.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450179731

---

From: ianswett <[email-masked]>
Date: Thu, 27 Dec 2018 08:07:32 -0800

@nibanks Discarding the rest of the UDP payload sounds wise to me.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450180146

---

From: Marten Seemann <[email-masked]>
Date: Thu, 27 Dec 2018 08:17:43 -0800

A client might combine two Initial packets of different versions into one datagram in order to save the round trip penalty associated with VN. This wouldn't work if the server decides to drop the rest of the packet after sending the VN packet.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450182381

---

From: Dmitri Tikhonov <[email-masked]>
Date: Thu, 27 Dec 2018 08:26:18 -0800

So this hypothetical client does not care that the server might create two connections.  Perhaps we should not allow such scenario.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450184249

---

From: Nick Banks <[email-masked]>
Date: Thu, 27 Dec 2018 08:29:07 -0800

I agree with @dtikhonov. I don't think we should allow/encourage that scenario. Send two different UDP datagrams if you really want to do that as a client.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450184849

---

From: Marten Seemann <[email-masked]>
Date: Thu, 27 Dec 2018 08:41:46 -0800

If we want to forbid that behavior, there's no need to coalesce two QUIC packets with the same encryption level. Maybe an easy fix would be prohibit that in general.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450187401

---

From: Nick Banks <[email-masked]>
Date: Thu, 27 Dec 2018 08:48:07 -0800

Depending on how an implementation implements packet coalescing on the send side, I could see an implementation padding a UDP datagram with a padding-only Initial packet that follows the Initial packet with the crypto data.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450188717

---

From: Marten Seemann <[email-masked]>
Date: Thu, 27 Dec 2018 10:19:05 -0800

@nibanks Why would you do that? If an implementation is not capable of using QUIC PADDING for some reason (and I can't think of any reason why this would pose an insurmountable challenge), it can just pad the rest of the UDP packet directly. There's no need to use a second Initial packet.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450204149

---

From: Martin Thomson <[email-masked]>
Date: Sun, 30 Dec 2018 18:12:26 -0800

The fix of "one Retry per UDP datagram" seems about right to me.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450601526

---

From: Marten Seemann <[email-masked]>
Date: Sun, 30 Dec 2018 19:34:14 -0800

@martinthomson Why do we need to allow multiple packets of the same encryption level in one UDP datagram? I can't see a single use case for this, but I see a lot of ways this can be used in malicious ways.
We introduced coalesced packets to reduce HoL blocking on encryption level changes, not to give senders the ability to stuff 67 packets into a single datagram.

Proposed text:
> A sender MUST NOT coalesce multiple packets with the same encryption level into a single datagram. Receiver SHOULD ignore any subsequent packets that have the same encryption level as any of the previous packets in the datagram.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450604861

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 31 Dec 2018 01:11:29 -0800

@marten-seemann I am not sure if having a MUST requirement that cannot be enforced by the peer is a good idea (note: it cannot be enforced by the peer because a middlebox can stitch valid QUIC packets to compose a QUIC datagram).

Therefore my preference goes to what @martinthomson suggests.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450623113

---

From: Marten Seemann <[email-masked]>
Date: Mon, 31 Dec 2018 01:25:07 -0800

@kazuho We already have a MUST requirement for coalesced packets:
> Senders MUST NOT coalesce QUIC packets for different connections into a single UDP datagram. Receivers SHOULD ignore any subsequent packets with a different Destination Connection ID than the first packet in the datagram.

Both requirements cannot be enforced in the sense that the connection is closed. However, they are enforced in the sense that the incorrect packets are dropped, so there's no motivation whatsoever for an endpoint to misbehave.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450624307

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 31 Dec 2018 12:49:54 -0800

@marten-seemann That's true. Thank you for pointing that out.

OTOH, I am still not convinced that we should disallow senders from coalescing QUIC packets of same type.

We need to maintain state while parsing the datagram (to not let each of enclosed packets generate a Retry), regardless of the approach we adopt. Therefore, I do not see benefit in disallowing coalescence, while the downside would be that it makes difficult to stitch a fake QUIC packet in front of the datagram to help PMTUD (as of -17, we do not have "reserved" packet types).

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450689295

---

From: Marten Seemann <[email-masked]>
Date: Mon, 31 Dec 2018 18:42:36 -0800

> We need to maintain state while parsing the datagram (to not let each of enclosed packets generate a Retry)

State is exactly what I'm concerned about. I'd like to avoid allocating state for 67 QUIC packets due to a single datagram. With my proposed requirement, you can at most have 4 QUIC packets per datagram.
Not sending more than a single Retry or a single VN packet per datagram gets trivial if we introduce this requirement, since you already detect duplicate Initials when parsing, not just when handling packets.

> Therefore, I do not see benefit in disallowing coalescence, while the downside would be that it makes difficult to stitch a fake QUIC packet in front of the datagram to help PMTUD (as of -17, we do not have "reserved" packet types).

I haven't implemented PMTUD yet, and as far as I can see, the PMTU section doesn't say anything about coalesced packets. To increase the packet size you're supposed to fill packets with PING or PADDING frames. I assume you don't want to do this, and you want to use coalesced packets instead. What's the advantage of that approach? It seems like you just add a bunch of corner case to your code (since then there's a range of packet sizes you can't reach, because you have to add a whole QUIC header).

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450706233

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 31 Dec 2018 22:09:35 -0800

> State is exactly what I'm concerned about. I'd like to avoid allocating state for 67 QUIC packets due to a single datagram. With my proposed requirement, you can at most have 4 QUIC packets per dat…

I do not get the same numbers.

With @martinthomson's approach, an endpoint needs one boolean when parsing a datagram. The value is flipped when you send a Retry, and prohibits the rest of the packets belonging to the same boolean from issuing another Retry.

With your proposal, an endpoint needs to maintain at least four booleans to detect if any of the packets have been seen more than once in a datagram.

Therefore, I think that @martinthomson's approach requires less state.

> I haven't implemented PMTUD yet, and as far as I can see, the PMTU section doesn't say anything about coalesced packets.

In Kista, we discussed about prepending long header packets (that do not authenticate) that contain enough information for the load balancer to forward ICMP response to the server.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/2259#issuecomment-450711758