From b8abeaff50585421d1452fcabc111a76d4d5b484 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 21 Jun 2024 16:46:06 +0200 Subject: [PATCH] Fix ssl-passthrough under fragmented ClientHello (#11424) --- pkg/tcpproxy/tcp.go | 51 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/pkg/tcpproxy/tcp.go b/pkg/tcpproxy/tcp.go index fba4d21be..2aab8ac43 100644 --- a/pkg/tcpproxy/tcp.go +++ b/pkg/tcpproxy/tcp.go @@ -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 { @@ -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, err := conn.Read(data) + // 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 + } + + 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 {