Description
Executive summary
When TLS Client Hello is split into two TCP segments and segments arrive to TrafficServer in different parts, blind tunneling flow breaks.
Details
Use case
Trafficserver runs as a transparent forward proxy for TLS traffic. A custom plugin uses SSL_CERT_HOOK to determine destination hostname of the TLS connection.
If the destination is accepted, connection is converted into a blind tunnel by calling TSVConnTunnel(sslvc)
and TSVConnReenable(sslvc)
in the hook callback function.
Affected versions
Reproduced with TrafficServer 8.x branch and TrafficServer 9.2.4. Issue should also exist in 10.x branch because affected code paths have not changed.
The issue
When a connection is started with Kyber cipher, the TLS Client Hello packet contains more key material than previous ciphers. With Kyber cipher, the TLS Client Hello packet is 1700-2100 bytes in size.
Since the network MTU is usually 1500, it means that the TLS Client Hello is split into two TCP segments. In some conditions, clients' network connectivity is slow such that the gap between the first and second segment of Client Hello is big enough to trigger the bug.
https://github.com/apache/trafficserver/blob/master/src/iocore/net/SSLNetVConnection.cc#L423 is the place where TLS Client Hello is read. This is a call to read data from socket that is for the incoming TCP connection. There is no guarantee that this call will return both segments of the Client Hello. If data for complete Client Hello is received here, the blind tunnel operation is succesful.
However, if only first segment of Client Hello is received, then the following sequence of events happen:
https://github.com/apache/trafficserver/blob/master/src/iocore/net/SSLNetVConnection.cc#L1365 OpenSSL ssl_accept()
is called. It returns SSL_ERROR_WANT_READ
, which is returned as SSL_HANDSHAKE_WANT_READ
.
https://github.com/apache/trafficserver/blob/master/src/iocore/net/SSLNetVConnection.cc#L661 is the branch taken in this case. Processing goes to update_rbio
at https://github.com/apache/trafficserver/blob/master/src/iocore/net/SSLNetVConnection.cc#L529 with move_to_socket
set to true
.
In update_rbio
, OpenSSL BIO is set to the file descriptor of the connection, and existing handshake buffers are deallocated.
Now, when the second segment of Client Hello arrives, the blind tunnel branch https://github.com/apache/trafficserver/blob/master/src/iocore/net/SSLNetVConnection.cc#L624 is not taken, because this->handShakeReader
is nullptr
after it was deallocated previously in update_rbio
.
To fix the issue, the case of SSL_HANDSHAKE_WANT_READ
should be handled so that the second TCP segment of handshake is read into the existing SSL handshake buffer, and then ssl_accept()
is called with that buffer.
I have tried a couple of workarounds to do this. However, since I don't fully understand the context why current implementation is like this, I am not confident of making big changes, because they might affect other use cases.