Skip to content

Commit

Permalink
Fix ssl-passthrough under fragmented ClientHello (kubernetes#11424)
Browse files Browse the repository at this point in the history
  • Loading branch information
alshain committed Jun 21, 2024
1 parent 5784be2 commit b8abeaf
Showing 1 changed file with 48 additions and 3 deletions.
51 changes: 48 additions & 3 deletions pkg/tcpproxy/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type TCPProxy struct {
Default *TCPServer
}

var tlsHeaderLength = 5
var tlsMaxMessageLength = 16384

// Get returns the TCPServer to use for a given host.
func (p *TCPProxy) Get(host string) *TCPServer {
if p.ServerList == nil {
Expand All @@ -59,15 +62,57 @@ func (p *TCPProxy) Get(host string) *TCPServer {
// and open a connection to the passthrough server.
func (p *TCPProxy) Handle(conn net.Conn) {
defer conn.Close()
// See: https://www.ibm.com/docs/en/ztpf/1.1.0.15?topic=sessions-ssl-record-format
data := make([]byte, 16384)
// It appears that the ClientHello must fit into *one* TLSPlaintext message:
// When a client first connects to a server, it is REQUIRED to send the
// ClientHello as its first TLS message.
// Source: https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2

// length: The length (in bytes) of the following
// TLSPlaintext.fragment. The length MUST NOT exceed 2^14 bytes. An
// endpoint that receives a record that exceeds this length MUST
// terminate the connection with a "record_overflow" alert.
// Source: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1
// bytes 0 : content type
// bytes 1-2: legacy version
// bytes 3-4: length
// bytes 5+ : message
// https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_record
// Thus, we need to allocate 5 + 16384 bytes
data := make([]byte, tlsHeaderLength+tlsMaxMessageLength)

length := 0
ensureBytesRead := func(n int) error {
if n > len(data) {
return fmt.Errorf("attempted to read more bytes than buffer size: %d vs %d", n, len(data))
}
for {
fragmentLength, err := conn.Read(data[length:])
length += fragmentLength

if err != nil {
return err
}

length, err := conn.Read(data)
if length >= n {
return nil
}
}
}
err := ensureBytesRead(tlsHeaderLength)
if err != nil {
klog.V(4).ErrorS(err, "Error reading data from the connection")
return
}

// otherwise, ensureBytesRead may produce an index out of bounds
clientHelloLength := min(int(data[3])<<8+int(data[4]), tlsMaxMessageLength)

err = ensureBytesRead(tlsHeaderLength + clientHelloLength)
if err != nil {
klog.V(4).ErrorS(err, "Error reading ClientHello of length %d from the connection", clientHelloLength)
return
}

proxy := p.Default
hostname, err := parser.GetHostname(data)
if err == nil {
Expand Down

0 comments on commit b8abeaf

Please sign in to comment.