Subject: [quicwg/base-drafts] DoS using Version Negotiation Packets (#135)

From: Marten Seemann <[email-masked]>
Date: Mon, 09 Jan 2017 20:41:46 -0800

I think I’ve found a DoS vulnerability in the current draft.

Let’s say an attacker sends a first packet to a QUIC server (server A), but picks a version that the server doesn’t support. Furthermore, it spoofs the sender address of this packet to the address of another QUIC server (server B).
The packet sent by the client will look like this:
1 byte Flags: 0x9 & Packet Number lengths bits
8 byte Connection-ID
4 byte Version Number
1, 2, 3 or 4 byte Packet Number
AEAD data

Server A will discover that it doesn’t support the QUIC version that the client suggested, and reply with a Version Negotiation Packet, which will get sent to server B. The version negotiation packet will look like this:
1 byte Flags: 0x9
8 byte Connection-ID
4 byte 1st Version Number
4 byte 1st Version Number
....

If server B doesn’t support the version sent as 1st Version Number by server A, it will not be able to distinguish this packet from a packet sent from a client (according to [Section 5.1.2](https://quicwg.github.io/base-drafts/draft-ietf-quic-transport.html#handling-packets-from-different-versions) it MUST NOT read anything from the packet except for the Flags byte, the Connection ID and the first Version Number). Furthermore, according to [Section 6.1](https://quicwg.github.io/base-drafts/draft-ietf-quic-transport.html#version-negotiation) the server MUST reply to server B with a Version Negotiation Packet.
Assuming that server A doesn’t support the version sent as 1st Version Number by server B, A and B will continue sending each other Version Negotiation packets indefinitely. Note that the attacker only sent a single 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/135

---

From: Marten Seemann <[email-masked]>
Date: Mon, 09 Jan 2017 20:45:03 -0800

We could fix this by removing the Connection ID from Version Negotiation Packets (#133).

Then, receiving a packet that has the Version Flag set, we could require
* the server to check that the Connection ID Flag (0x8) is set
* the client to check that the Connection ID Flag is not set

-- 
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/135#issuecomment-271485345

---

From: Martin Thomson <[email-masked]>
Date: Mon, 09 Jan 2017 21:53:35 -0800

Interesting configuration. I agree that it could be an issue, though it's a pretty small amount of data.  I expect that one or other of the servers will eventually blacklist the other, but you are right that it could be indefinite.

Removing connection ID from the server packet would seem to fix the issue.  Note that we have to add the connection ID flag to the set of things that can't change between versions if that happens.

-- 
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/135#issuecomment-271492932

---

From: Marten Seemann <[email-masked]>
Date: Mon, 09 Jan 2017 22:41:32 -0800

That's true. I don't think there's any other way than using one of the Flag bits to indicate the perspective of the sender. The Connection ID flag seemed to be the most obvious choice, since we were thinking about removing it from Version Negotiation Packets anyway.

Another option would be to reuse the Public Reset bit (0x2) for that purpose. A Public Reset would then be identified by `Flag & 0x3 == 0x2`, i.e. a packet is a Public Reset if and only if the Public Reset Flag is set AND the Version Flag is unset. For packets with the Version Flag set the server would set 0x2 and the client would not set 0x2 (or vice versa).
I'm not sure if that's a cleaner solution than using the Connection ID flag, however, it would allow more flexibility regarding the Connection ID in future versions.

-- 
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/135#issuecomment-271498838

---

From: hardie <[email-masked]>
Date: Thu, 12 Jan 2017 10:46:32 -0800

Is there any risk that removing the connection ID from Version Negotiation Packets would remove a disambiguation signal that a client is relying on?  The only condition I can come up with would be one where a client was running something like happy eyeballs using two different starting Versions and then needed to disambiguate responses using the connection id sent.  That seems unlikely and unwise, but it makes me wonder if there are more reasonable cases where it could occur.

-- 
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/135#issuecomment-272246902

---

From: Martin Thomson <[email-masked]>
Date: Thu, 12 Jan 2017 16:42:31 -0800

The consequence is that clients that attempt to create multiple connections on the *same N-tuple* will be unable to identify which connection triggered version negotiation.  That leads to a bunch of things, none of which seem particularly dire:

* version negotiation packets from the server need to be given to all connections that were created on that N-tuple at the client (which should be one in most cases)
* servers can't send different sets of versions to clients on the same N-tuple (otherwise client's could end up switching their version more than once)
* clients have to ignore version negotiation packets on established connections (for the above reason, they should probably only look at the first)
* clients have to ignore version negotiation packets that include their selected version

BTW, I like the idea of trying multiple concurrent versions on the same N-tuple.  Version negotiation is a bit of a weak point in the protocol and that could make things move more quickly.

The original thought I had for this was server-to-server connections, where you might want to have multiple connections on the same N-tuple for other reasons.  For instance, maybe you had run out of streams and wanted to keep the congestion controller state active while replacing the connection.

-- 
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/135#issuecomment-272329086

---

From: hardie <[email-masked]>
Date: Thu, 12 Jan 2017 16:54:29 -0800

I'm a bit worried by importing happy eyeballs into QUIC for version negotiation, since the flows might end up at different back ends to anycast service.  That creates a coordination problem for point 2  you have above.  If you really want that, can we open a new issue for that in particular?

-- 
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/135#issuecomment-272330915

---

From: Martin Thomson <[email-masked]>
Date: Thu, 12 Jan 2017 18:06:26 -0800

To be clear, I'm not going to propose that we define that.  I just found the notion appealing, that's all.  Happy to open an issue if you think that it's worth chasing down.

-- 
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/135#issuecomment-272341831

---

From: Ryan Hamilton <[email-masked]>
Date: Fri, 13 Jan 2017 16:50:08 -0800

Instead of removing connection ID from the version negotiation packet (which would be problematic for clients which might run multiple connections from the same ip:port) let's add an explicit "client vs sever" value just after the public flags byte for all packets with the version bit set.

-- 
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/135#issuecomment-272586511

---

From: Martin Thomson <[email-masked]>
Date: Sun, 15 Jan 2017 13:20:53 -0800

It's only problematic if the servers need to run multiple different version on the N-tuple.  Otherwise it's very simple: send the version negotiation packet to every connection and some will ignore it.

-- 
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/135#issuecomment-272724652

---

From: Ryan Hamilton <[email-masked]>
Date: Sun, 15 Jan 2017 13:25:53 -0800

I may not be understanding you. Let's say the client supports only a single version and attempts to connect to two servers. Let's say that one of the servers supports the client's version and the other does not. The second server will send a version negotiation packet. If that packet is delivered to all the client connections, won't they will both be torn down? That seems undesirable. 

-- 
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/135#issuecomment-272724998

---

From: Martin Thomson <[email-masked]>
Date: Sun, 15 Jan 2017 14:05:49 -0800

If these are two different servers, then they will be on different N-tuples and the version negotiation packets can be attributed to the right connection by the client without relying on the connection ID.

This is only a problem if the two servers are on the *same* N-tuple AND they support different versions.  My point would be "don't do that then".

-- 
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/135#issuecomment-272728560

---

From: Ryan Hamilton <[email-masked]>
Date: Sun, 15 Jan 2017 16:02:09 -0800

Except that QUIC connections are identified/sharded by connection ID, not by 4-tuple.

-- 
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/135#issuecomment-272748742

---

From: Martin Thomson <[email-masked]>
Date: Sun, 15 Jan 2017 19:47:35 -0800

Presumably they are also identified/sharded by IP and port.  Otherwise connection ID would have to be globally unique, a purpose for which they are not nearly large enough.

-- 
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/135#issuecomment-272767888

---

From: martinduke <[email-masked]>
Date: Fri, 20 Jan 2017 13:45:55 -0800

We're operating without a multipath draft that would clearly explain how this works, but I don't see why they have to be any more unique than over the IP addresses present at a particular host.

In the unfortunate event that a second client tried to connect with a server with a connection ID already on that server, decryption would fail and the server would silently drop the packet. A good reason, I think, for subsequent ClientHellos to have a different connection ID.

-- 
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/135#issuecomment-274188207

---

From: Martin Thomson <[email-masked]>
Date: Mon, 23 Jan 2017 21:12:14 -0800

Potential fix:  clients are required to create a packet of a certain minimum size.  As long as the space of potential sizes for version negotiation packets from the server doesn't overlap with the sizes that a client can generate (that is, set the values such that min(client initial) > max(server version negotiation)), you can avoid generating a version negotiation packet in response to some packets.

Also solved by #167, particularly if we bake in a client/server bit.

-- 
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/135#issuecomment-274711668

---

From: Martin Thomson <[email-masked]>
Date: Mon, 23 Jan 2017 21:18:21 -0800

We could put a client/server bit into each frame, though this is a cost we wouldn't want to pay on every packet, just during the handshake.

@marten-seemann suggested we cannibalize the public reset flag, which is never set on a packet that also has a version.

Another option is including a magic number in the version negotiation 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/135#issuecomment-274712365

---

From: Mark Nottingham <[email-masked]>
Date: Mon, 23 Jan 2017 21:22:30 -0800

Discussed in Tokyo; agreement that breaking the symmetry in the packets is important. Consensus to put a token in the client 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/135#issuecomment-274712836

---

From: Ryan Hamilton <[email-masked]>
Date: Mon, 23 Jan 2017 21:23:51 -0800

I'm surprised to see the token in the client packets (which are common and large) and not the version negotiation packet (which are rare and small). But I'm not in the room :)

-- 
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/135#issuecomment-274712989

---

From: ianswett <[email-masked]>
Date: Tue, 24 Jan 2017 05:11:53 -0800

Good point @RyanAtGoogle .  I believe the consensus was to append 4 bytes(ie: "QUIC") to the 4 bytes of client version when the version negotiation bit was set true.

I think it was widely agreed that these 4 bytes should NOT be in every client 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/135#issuecomment-274798875

---

From: Ryan Hamilton <[email-masked]>
Date: Tue, 24 Jan 2017 07:09:57 -0800

If a server wants to negotiate Q035 or Q034, is the idea that the payload would be:
  QUICQ034Q035
I guess a server needs to recognize that QUIC is not a valid QUIC version so that it can break the loop? This sounds workable. (Though stealing the public reset bit for this case sounds even simpler)

-- 
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/135#issuecomment-274830750

---

From: Ryan Hamilton <[email-masked]>
Date: Tue, 24 Jan 2017 13:36:29 -0800

Updating this after an offline discussion with @ianswett. The crux of the issue here, if I understand correctly, is that we want packets to be declare (somehow) if they were sent by a client or by a server. For "regular" packets, we have the auth hash which solves this problem. So that leaves the two special packets: Version Negotiation and Public Reset packets. Public Reset packets can only be sent by the client, so that leaves us with just the Version Negotiation packet.

So if we renamed the "public reset" bit to be the "special packet" bit (as @marten-seemann  suggested), then we could have the following bit patterns:

00 - regular packet - no version present (could be sent by either endpoint, the hash will differentiate)
01 - regular packet - with the version present (sent by the client)
10 - special packet - version negotiation (sent by the server)
11 - special packet - public reset (sent by the server)

This seems like a nice solution to the problem which does not require the client to devote 4-bytes to a static value in every 0-RTT 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/135#issuecomment-274946492