Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address change and consent to send #161

Closed
martinthomson opened this issue Jan 17, 2017 · 47 comments
Closed

Address change and consent to send #161

martinthomson opened this issue Jan 17, 2017 · 47 comments
Labels
-transport design An issue that affects the design of the protocol; resolution requires consensus. has-consensus An issue that the Chairs have determined has consensus, by canvassing the mailing list.

Comments

@martinthomson
Copy link
Member

When a client changes IP addresses (for any reason, including NAT rebinding), the server has no way of gaining an assurance that the client has actually seen its packets. That opens an attack where the attacker establishes a connection, then starts spoofing source address. If the server starts to send to that source address without verifying that the client can receive packets, that's potentially a powerful packet amplification attack.

It's not sufficient to look for ACKs or other such things, because no client message includes proof that the client has actually received a packet from the server. A client can just generate ACK and WINDOW_UPDATE frames as necessary, even new requests. The only risk a client runs is that it overestimates the number of packets that the server has sent and sends too many spurious ACK frames, which the server will then recognize and react to.

@martinthomson martinthomson added design An issue that affects the design of the protocol; resolution requires consensus. -transport labels Jan 17, 2017
@marten-seemann
Copy link
Contributor

marten-seemann commented Jan 17, 2017

This would be the equivalent of an Optimistic ACK attack, after completing the crypto handshake. If the server randomly skips packet numbers, it will notice the attack eventually and close the connection.
The question is, will that happen soon enough to make this attack unpractical. In quic-go, we randomly skip one packet out of 500, so just ACKing random packets would make the server send about 800 kB of data before noticing the attack.

@martinthomson
Copy link
Member Author

Packet skipping would seem to be a possible answer, though the client only needs to ack a few packets to keep things moving along. If it is eventually unlucky enough to hit a skipped packet the attack ends. I'd need to do the math, but 800k sounds right for an expected value, which is pretty high when you compare it to the cost of a handshake (around 1400 octets and some crypto for clients).

@mirjak
Copy link
Contributor

mirjak commented Jan 17, 2017

You could maybe send a ping frame with a random number if you see a new IP address...?

@ianswett
Copy link
Contributor

In the case of port or IP address change, skipping a larger number of packets makes it much harder to guess.

@martinthomson
Copy link
Member Author

FWIW, PING doesn't work here. The response to a PING is an ACK, which contains no information from the PING.

@mirjak
Copy link
Contributor

mirjak commented Jan 17, 2017

Yes, I meant to actually extend the ping mechanism here to make it possible to reflect a random number chosen by the ping sender. Having this as an explicit check rather than doing some implicit guess-work might make sense given it's simple and the cost is low.

@ianswett
Copy link
Contributor

The idea of a PING packet with an random number and a PONG in response seems reasonable, though if this is the only use case, I think skipping packet numbers is sufficient, given the 5-tuple is changing anyway.

@martinthomson
Copy link
Member Author

martinthomson commented Jan 18, 2017

Having thought about this some more, skipping packet numbers does allow for some fairly high confidence that there is return routeability. Since you are starting a new congestion state for the new path anyway, ACKs will be critical in driving the congestion window open. I do think that we need to be a lot more aggressive in dropping/skipping packet numbers with high probability (> 0.5) for a short while.

@mbagnulo
Copy link

mbagnulo commented Mar 3, 2017

There is a number of other protocols that have the same issue, including MPTCP, SCTP, MobileIP and the usual solution is what Mirja proposes, a return routability check before sending packets to the new address. Some references where this has been documented include section 5 of RFC6181 for MPTCP, RFC4225 for MIPv6, RFC4218 for Shim6 and RFC5062 for SCTP.
So from the perspective of not making it worse than current protocols, a ping with a random number should be ok. If you add the skipping of packet numbers, then this is stronger.
Probably, it would be worthwhile referring to BCP84 when describing this issue.

@janaiyengar
Copy link
Contributor

There are a few issues here

  1. Amplification attack, where an attacker grows server's cwnd to a large value and then sends a spoofed packet with the victim's IP, causing a large amount of data to be dumped on the victim.
    Solution: When a server observes a change in the client's IP address, it records the current cwnd, clamps the cwnd down to MIN_CWND, and sends a PING frame to the new IP. When a packet sent to the new IP address is acked, the cwnd is restored.

  2. Optimistic ack attack, where the attacker optimistically acks the PING packet sent by the server to the victim's IP, causing the server to again unleash it's large cwnd on the victim.
    Solution: On a change in the client's IP address, the server introduces a randomly sized gap in the packet sequence space. Any new data will now still be sent to the new client IP, but the sequence numbers will be discontiguous from transmissions to the old IP. Any acks for packets in the gap will result in an immediate termination of the connection. A malicious client will now have to either correctly guess the exact size of this gap or read packets going to the victim.

Alternatively, we can add an optional random number to the PING frame, which when present means that the receiver MUST respond immediately with a PING frame carrying the same random number.

  1. There's a slightly tricky issue hidden in here. Say a server observes an address change from IP A to B. What should a server do if it observes another address change while it's in the middle of validating one?
    Solution: There are three possible scenarios.
    (i) If the new IP is different than A, then restart validation of the newest IP.
    (ii) If the new IP is the same as A and the packet is reordered, then accept the packet and continue with the current validation.
    (iii) If the new IP is the same as A and the packet is newer than the one that triggered the last validation, then close the connection, since this doesn't look like a NAT rebinding. (This could be a MITM, who changes the IP address of one client -> server packet, hoping that the server reduces its cwnd or latches on to this new IP, causing a DoS of sorts.)

@martinthomson
Copy link
Member Author

@janaiyengar, on point three, could that just be spontaneous multipath? :p

@mirjak
Copy link
Contributor

mirjak commented Mar 3, 2017

I really support to put a random number in the ping: that's easier and fast than you step 2 above.

@mirjak
Copy link
Contributor

mirjak commented Mar 3, 2017

For the multipath case I would assume that you tell the other beforehand which IP you are going to use...

@martinthomson
Copy link
Member Author

I wouldn't make ping include an optional random number, I would add a new explicit return routeability frame (or two). I also wouldn't necessarily make the value random, just hard to guess (source address token lite).

@mbagnulo
Copy link

mbagnulo commented Mar 3, 2017

About the multipath case, it is not always the case that you know the new address.
Suppose the client moves to a new location behind the NAT.

@mirjak
Copy link
Contributor

mirjak commented Mar 3, 2017

In this case the old address goes away which I wouldn't call multipath anymore

@mbagnulo
Copy link

mbagnulo commented Mar 3, 2017

:-)
Agree, let me rephrase. A new interface is up and the endpoint wants to establish a new subflow using the other interface. (e.g. wifi and 4g) and the new interface is behind a NAT. makes sense?

@ekr
Copy link
Collaborator

ekr commented May 5, 2017

Note on "doesn't have to be random". I think the term you want here is "unpredictable" or "unguessable"

@janaiyengar
Copy link
Contributor

You need two things here:

  1. Server adds an unpredictable jump to packet number on noticing IP change. This can still leave the client open to a large cwnd's worth of data getting dumped on a victim. So,
  2. Server cuts its cwnd to min cwnd (say, 4 packets) until a packet > first_packet_sent_on_peer_address_change is acked. At that point, the IP change is verified, and the server restores the cwnd.

@martinthomson
Copy link
Member Author

martinthomson commented May 7, 2017

@janaiyengar, you are assuming that a jump in packet number is what will carry the unguessable property. I don't think that's sound. In order for the jump to be valuable, it has to be large, which leads to the packet number encoding not being big enough. The suggestion here, which I think is reasonable, is that you have an unguessable value that is sent to the endpoint that moved. Overloading packet number for this is nice in theory, and it might be the right answer in the end, but I wouldn't presume that it is the best answer.

Also, you can't just "restore" the cwnd on a new path. That path might look nothing like the previous path. Unless you are also suggesting that your initial cwnd can safely be anything provided that you react properly. Otherwise, you have to allow for the possibility that the new path could be orders of magnitudes less capable than the previous path. Starting congestion control all over is probably the only sensible thing you can do. That is, you have a different congestion state for every path used.

@igorlord
Copy link
Contributor

Is not the following enough to defend against this?

  • clamping down CWND (which you have to do anyway for a brand new path),
  • a packet number jump (does not need to be huge; a jump several times bigger than MIN_CWND is sufficient), and
  • an impending "QUIC Public Reset" or "ICMP Port Unreach"

@martinthomson
Copy link
Member Author

As stated earlier (here and here for instance), I don't think that a single packet number gap contains enough entropy and it could in fact be a mistake to rely on it for other reasons. ACK efficiency or resistance to delays and reordering being some concrete reasons against leaving intentional gaps. I would rather we build something more concrete than rely on these implicit mechanisms.

As stated on the mailing list, Public Reset and ICMP Unreachable can't or won't necessarily be sent by the entity under attack.

@janaiyengar
Copy link
Contributor

On cwnd: When I said "restore the cwnd", I meant to the initial cwnd on the new path. Whatever cwnd you use on a new IP, the server cuts it down cwnd to min_cwnd (which is 2 x MSS), and then restores the cwnd back to the earlier cwnd on validation.

On packet number jumps: Are you saying that a 4-byte packet number is inadequate entropy? If so then we'll need to revisit Version Negotiation, since a VN packet includes a new server-selected connection ID and the only correlator is the packet number. An attacker could just as easily inject a VN packet with the correct packet number causing the connection establishment to break. (Actually, the problem in VN is worse than here, since the attacker cannot get a single packet wrong when trying to guess a jump. We haven't nailed down rules for when an endpoint receives a VN with a packet number that does not match any sent packets.)

@marten-seemann
Copy link
Contributor

I heard of the idea to serve a single connection from multiple servers, and assigning a range of packet numbers to each server. I haven't thought all the details through, but if this is possible, there will be larger gaps (although you wouldn't need 31-bit packet number space for this).

@martinthomson
Copy link
Member Author

Oh yeah. Though n servers could be given n*m+i. The idea is frightening and intriguing at the same time.

As for triggering an aggressive ACK, I agree that an explicit signal removes the uncertainty, but if the gap needs to be in the order of 2^31, then you can simply step up the ACK schedule if you see a large gap. We could even recommend a minimum gap.

But that all assumes that packet number gaps are the right way to validate consent to receive. I don't think that they are.

@igorlord
Copy link
Contributor

stated on the mailing list, Public Reset and ICMP Unreachable can't or won't necessarily be sent by the entity under attack.

If an entity does not send Public Reset or ICMP Unreachable, and our CWND after a "migration" event was low, the CWND would not open up w/o ACKs, so the server (reflector) would not send anything more to the victim. This is the same effect as not getting a PONG back.

amount of entropy

You need a lot of entropy only when it is cheap for an attacker to keep guessing. In this case, if your MIN_CWND is, say, 2, and you jump the packet number by rand(min=0, max=1023) (uniformly distributed), then with 99.8% probability the attacker's ACK will ack a packet you never sent, and he will kill this connection.

@hardie
Copy link

hardie commented May 12, 2017

So, to confirm my understanding of this method, we expect a client to change connection-id when it knows it is changing IP address, for the linkability reasons we have rehearsed elsewhere. This method would then get used whenever that new connection-id is seen for the first time and when a new IP is presented on the old connection ID, because we presume that this is an indication of NAT rebinding.

Would a change in source port, re-using the same connection ID and IP also trigger this? It seems like that presents a pretty small risk of being an attack.

Which causes me to wonder whether we also ought to exempt other changes that are most likely to be due to either NAT rebinding or IPv6 address selection mechanics. The same v4 /28 or v6 /64 seem very likely to be the same host. Is it worth PINGing there? Or is it not worth the special logic to test the change?

@janaiyengar
Copy link
Contributor

janaiyengar commented May 12, 2017 via email

@janaiyengar
Copy link
Contributor

That said, I'm definitely leaning towards the PING-PONG thing myself. It's simpler to describe, implement, and not get wrong.

@janaiyengar
Copy link
Contributor

Ted -- your summary is correct. I think we probably should require (MUST) it on an IP address change, but port number changes are probably ok. If we decide to go with a PING-PONG frame (which is where I'm leaning), then the cost of sending this frame isn't that high, so it's fine to be liberal in deciding when to send it.

Reducing the cwnd however has costs, and I would be more cautious there. Doing so on IP address changes seems like the safest thing to do. If we wanted to be slightly more performance-sensitive, we could do something like allow /24s without cwnd reduction, but still send a PING-PONG or something like it.

@martinthomson
Copy link
Member Author

IP might not be OK. I think that a CGN will randomize in an unpredictable fashion, but if they don't it would be possible to do a reflection attack on hosts that share a CGN. I agree that's narrow, and probably not very compelling, but nonetheless it suggests that in general we can't consider a change in port alone to be perfectly safe. Probably safe with caveats might be OK, and we might allow a higher initial value for that, but I don't see any real harm in starting the congestion control over again.

@hardie
Copy link

hardie commented May 22, 2017 via email

@martinthomson
Copy link
Member Author

Yes, this is generically applicable to NAT.

I'd be interested in that data. You might be seeing less enterprise traffic, so I'd keep that as a caveat. Because when you have a whitelist for port-only changes you get some potential issues there. Unless you think that there is no reason why one enterprise user might be motivated to attack another host on the same network.

@hardie
Copy link

hardie commented May 23, 2017 via email

@martinthomson
Copy link
Member Author

Yes, if we assume that the attacker's resources are limited to those that exist behind the same NAT/firewall, it's pretty tricky to mount that attack.

In the end, I would prefer to have some simple blanket rules, followed by an explanation of some cases in which certain rules might be lifted. Same-port migrations might be one of those cases.

@hardie
Copy link

hardie commented May 23, 2017 via email

@martinthomson
Copy link
Member Author

If the PONG doesn't come back, I would say that the path is unusable.

I can see the case for restoration in the case of a port-only change, on the assumption that the path between the remote endpoint and the NAT is unlikely to change significantly in terms of characteristics. But an IP change could be onto a path with substantially different characteristics.

Here's my proposal:

If the IP address or port of a peer changes, reset the congestion window and initiate <insert path validation method here>. Once the validation completes, if the IP address of the peer did not change, the congestion window MAY be increased to the value it had prior to migration. If the path validation method fails completely after <some time>, mark the path as unusable, which might cause the connection to fail.

Question: do we have to consider the possibility that the prior congestion window might decay due to inactivity and account for that here, or is it better to continue to run the old controller, feed all the new signals into it and switch it in?

@hardie
Copy link

hardie commented May 24, 2017 via email

@martinthomson
Copy link
Member Author

I guess that I'm in the "minimize the change that a congestion window gets re-opened inappropriately", absent more evidence that doing so wouldn't be too bad.

[...] you mean that you'd feed the signals into both the new controller created on IP/port change and the controller that existed before the change, swapping the latter in when the path validation succeeded?

Yes. There might need to be some tweaks to that if we consider the possibility that the previous path could be reused, but that's essentially it.

@mikkelfj
Copy link
Contributor

mikkelfj commented May 25, 2017 via email

@mirjak
Copy link
Contributor

mirjak commented May 25, 2017

I think "the congestion window MAY be increased to the value it had prior
to migration; this increase should occur only when it is likely that the
change in IP address or port did not result in a path change" is about right. MAY indicates that it is rather not recommended by default but as you have already a guess about the available bandwidth on the path, you can do some smarter probing like chirping where you send packets with different inter packet gaps. Or we don't say anything about this and leave it to the congestion controller because every congestion controller will anyway implement a different way to increase the window.

Another question: do we need to decrease to the minimal congestion window or the initial congestion window?

@martinthomson
Copy link
Member Author

Based on the discussion at the interim, I think that we have a different model in mind now:

If there is a path change, reset the congestion controller. Absent other information, assume that any change in remote address is indicative of a path change. You might consider a port-only change to be indicative of hitting the same path. I don't think that this is a universally safe assumption, because it assumes a great deal about what exists on the other side of the NAT, but we might simply rely on the congestion controller being properly responsive to deal with cases where it genuinely isn't a new path.

If there is a change in the remote address, make sure to obtain proof that the other endpoint can receive your packets at their new address. Until this is successful, reduce the number of packets you are willing to send on that path to <some low rate that might be incidentally related to the initial congestion window>. If this validation fails, terminate the connection.

For the purposes of validation, we will define a new frame that has a randomized payload. An endpoint would be required to send a copy of that payload back in another frame. An endpoint can initiate this validation process at any time and for any reason (though it might have to deal with the resulting ENHANCE_YOUR_CALM if it sends it too much).

Note that I said to terminate the connection on this last bit because if validation of the path fails, either you have such a terrible loss issue that you don't want to be using the path, or the other side is spoofing this new source address.

martinthomson added a commit that referenced this issue Aug 15, 2017
This has been much-discussed, and it's a relatively isolated change, so I did it.

This modifies PING to have an optional payload and adds a PONG frame to echo
the PING.  An empty PING generates an ACK; a PING with a payload demands a
PONG.

Generating an unguessable PING is the basis of mid-connection address
validation.  If the PING is sent on the new path, and the PONG comes back, then
the remote address is probably OK to use.

I've taken the discussion in the issue into consideration here.  There's a lot
of potential nuance to capture in terms of how an endpoint might reduce and
restore send rates, but I've done what I can to thread the gap between allowing
unbounded sending along new and untested paths and allowing connections to get
back to doing business.

It's annoying that this makes PING and PONG so disparate.  I think that we have
a re-ordering of frames in our near future to correct minor infidelities like
this.  I didn't want to do that here and pollute this PR though.

Closes #161.
martinthomson added a commit that referenced this issue Oct 16, 2017
This has been much-discussed, and it's a relatively isolated change, so I did it.

This modifies PING to have an optional payload and adds a PONG frame to echo
the PING.  An empty PING generates an ACK; a PING with a payload demands a
PONG.

Generating an unguessable PING is the basis of mid-connection address
validation.  If the PING is sent on the new path, and the PONG comes back, then
the remote address is probably OK to use.

I've taken the discussion in the issue into consideration here.  There's a lot
of potential nuance to capture in terms of how an endpoint might reduce and
restore send rates, but I've done what I can to thread the gap between allowing
unbounded sending along new and untested paths and allowing connections to get
back to doing business.

It's annoying that this makes PING and PONG so disparate.  I think that we have
a re-ordering of frames in our near future to correct minor infidelities like
this.  I didn't want to do that here and pollute this PR though.

Closes #161.
martinthomson added a commit that referenced this issue Oct 17, 2017
This has been much-discussed, and it's a relatively isolated change, so I did it.

This modifies PING to have an optional payload and adds a PONG frame to echo
the PING.  An empty PING generates an ACK; a PING with a payload demands a
PONG.

Generating an unguessable PING is the basis of mid-connection address
validation.  If the PING is sent on the new path, and the PONG comes back, then
the remote address is probably OK to use.

I've taken the discussion in the issue into consideration here.  There's a lot
of potential nuance to capture in terms of how an endpoint might reduce and
restore send rates, but I've done what I can to thread the gap between allowing
unbounded sending along new and untested paths and allowing connections to get
back to doing business.

It's annoying that this makes PING and PONG so disparate.  I think that we have
a re-ordering of frames in our near future to correct minor infidelities like
this.  I didn't want to do that here and pollute this PR though.

Closes #161.
@mnot mnot added the has-consensus An issue that the Chairs have determined has consensus, by canvassing the mailing list. label Mar 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
-transport design An issue that affects the design of the protocol; resolution requires consensus. has-consensus An issue that the Chairs have determined has consensus, by canvassing the mailing list.
Projects
None yet
Development

No branches or pull requests