diff --git a/src/parser/BinaryTokenizer.h b/src/parser/BinaryTokenizer.h index a5faa44e246..89611b3058b 100644 --- a/src/parser/BinaryTokenizer.h +++ b/src/parser/BinaryTokenizer.h @@ -60,15 +60,24 @@ class BinaryTokenizer /// this method avoids append overheads during incremental parsing void reinput(const SBuf &data, const bool expectMore) { data_ = data; expectMore_ = expectMore; } + /// adds more data bytes to parse + void append(const SBuf &data) { data_.append(data); } + /// make progress: future parsing failures will not rollback beyond this point void commit(); + /// the number of bytes that were parsed but not committed yet + auto uncommitted() const { assert(parsed_ >= syncPoint_); return parsed_ - syncPoint_; } + /// resume [incremental] parsing from the last commit point void rollback(); /// no more bytes to parse or skip bool atEnd() const; + /// whether all available bytes have been parsed and no more bytes are expected + bool exhausted() const { return atEnd() && !expectingMore(); } + /// parse a single-byte unsigned integer uint8_t uint8(const char *description); @@ -110,6 +119,11 @@ class BinaryTokenizer /// debugging helper for parsed multi-field structures void got(uint64_t size, const char *description) const; + /// whether more data bytes may arrive in the future + bool expectingMore() const { return expectMore_; } + /// allow or prohibit arriving more data bytes in the future + void expectMore(const bool em) { expectMore_ = em; } + const BinaryTokenizerContext *context; ///< debugging: thing being parsed protected: diff --git a/src/security/Handshake.cc b/src/security/Handshake.cc index 1b7f095d8c9..26c61cb2e6a 100644 --- a/src/security/Handshake.cc +++ b/src/security/Handshake.cc @@ -10,6 +10,7 @@ #include "squid.h" #include "base/IoManip.h" +#include "parser/forward.h" #include "sbuf/Stream.h" #include "security/Handshake.h" #if USE_OPENSSL @@ -231,7 +232,6 @@ void Security::HandshakeParser::parseVersion2Record() { const Sslv2Record record(tkRecords); - tkRecords.commit(); details->tlsVersion = AnyP::ProtocolVersion(AnyP::PROTO_SSL, 2, 0); parseVersion2HandshakeMessage(record.fragment); state = atHelloReceived; @@ -264,7 +264,6 @@ void Security::HandshakeParser::parseModernRecord() { const TLSPlaintext record(tkRecords); - tkRecords.commit(); details->tlsVersion = record.version; @@ -273,49 +272,64 @@ Security::HandshakeParser::parseModernRecord() // RFC 5246: MUST NOT send zero-length [non-application] fragments Must(record.fragment.length() || record.type == ContentType::ctApplicationData); - if (currentContentType != record.type) { - parseMessages(); - Must(tkMessages.atEnd()); // no currentContentType leftovers - fragments = record.fragment; - currentContentType = record.type; - } else { - fragments.append(record.fragment); + if (currentContentType != record.type && tkMessages.uncommitted() > 0) { + // We could not parse these leftovers before. Since every serialized TLS construct has a + // known size (constant or sent-in-advance), parsing these bytes now cannot succeed either. + throw TextException(ToSBuf("truncated TLS message;", + " leftovers size=", tkMessages.uncommitted(), + " type=", currentContentType), Here()); } - if (tkRecords.atEnd() && !done) + currentContentType = record.type; + + tkMessages.expectMore(!tkRecords.exhausted()); + tkMessages.append(record.fragment); + + try { parseMessages(); + } catch (const Parser::BinaryTokenizer::InsufficientInput &) { + debugs(83, 3, "need more records"); + } } -/// parses one or more "higher-level protocol" frames of currentContentType +/// Incrementally parses all sequential currentContentType TLS fragments. +/// Successfully stops parsing earlier if `done` becomes set. void Security::HandshakeParser::parseMessages() { - tkMessages.reset(fragments, false); - for (; !tkMessages.atEnd(); tkMessages.commit()) { - switch (currentContentType) { - case ContentType::ctChangeCipherSpec: - parseChangeCipherCpecMessage(); - continue; - case ContentType::ctAlert: - parseAlertMessage(); - continue; - case ContentType::ctHandshake: - parseHandshakeMessage(); - continue; - case ContentType::ctApplicationData: - parseApplicationDataMessage(); - continue; - } - skipMessage("unknown ContentType msg [fragment]"); + switch (currentContentType) { + case ContentType::ctChangeCipherSpec: + return parseNonEmptyMessages(&HandshakeParser::parseChangeCipherSpecMessage); + case ContentType::ctAlert: + return parseNonEmptyMessages(&HandshakeParser::parseAlertMessage); + case ContentType::ctHandshake: + return parseNonEmptyMessages(&HandshakeParser::parseHandshakeMessage); + case ContentType::ctApplicationData: + return skipPossiblyEmptyMessages("app data [fragment]"); } + return skipPossiblyEmptyMessages("unknown ContentType msg [fragment]"); } +/// Incrementally parses all sequential currentContentType messages using the given TLS message parser. +/// Each message is assumed to be serialized using at least one byte. +/// At least one message is expected per sequence. +/// Successfully stops parsing earlier if `done` becomes set. void -Security::HandshakeParser::parseChangeCipherCpecMessage() +Security::HandshakeParser::parseNonEmptyMessages(const ParseMethod messageParser) +{ + tkMessages.rollback(); + do { + (this->*messageParser)(); + tkMessages.commit(); + } while (!done && !tkMessages.exhausted()); +} + +void +Security::HandshakeParser::parseChangeCipherSpecMessage() { Must(currentContentType == ContentType::ctChangeCipherSpec); // We are currently ignoring Change Cipher Spec Protocol messages. - skipMessage("ChangeCipherSpec msg [fragment]"); + tkMessages.skip(1, "ChangeCipherSpec msg [fragment]"); // In TLS v1.2 and earlier, ChangeCipherSpec is sent after Hello (when // tlsSupportedVersion is already known) and indicates session resumption. @@ -378,13 +392,6 @@ Security::HandshakeParser::parseHandshakeMessage() static_cast(message.msg_type) << " handshake message"); } -void -Security::HandshakeParser::parseApplicationDataMessage() -{ - Must(currentContentType == ContentType::ctApplicationData); - skipMessage("app data [fragment]"); -} - void Security::HandshakeParser::parseVersion2HandshakeMessage(const SBuf &raw) { @@ -629,12 +636,16 @@ Security::HandshakeParser::parseSupportedVersionsExtension(const SBuf &extension } void -Security::HandshakeParser::skipMessage(const char *description) +Security::HandshakeParser::skipPossiblyEmptyMessages(const char *description) { - // tkMessages/fragments can only contain messages of the same ContentType. + Assure(tkMessages.uncommitted() == 0); + // tkMessages can only contain messages of the same ContentType. // To skip a message, we can and should skip everything we have [left]. If - // we have partial messages, debugging will mislead about their boundaries. + // we buffered a partial message, we will need to read/skip multiple times. tkMessages.skip(tkMessages.leftovers().length(), description); + tkMessages.commit(); + if (tkMessages.expectingMore()) + throw Parser::InsufficientInput(); } bool @@ -646,8 +657,7 @@ Security::HandshakeParser::parseHello(const SBuf &data) // data contains everything read so far, but we may read more later tkRecords.reinput(data, true); - tkRecords.rollback(); - while (!done) + for (tkRecords.rollback(); !done; tkRecords.commit()) parseRecord(); debugs(83, 7, "success; got: " << done); // we are done; tkRecords may have leftovers we are not interested in diff --git a/src/security/Handshake.h b/src/security/Handshake.h index fe21ccda08f..88f59709a56 100644 --- a/src/security/Handshake.h +++ b/src/security/Handshake.h @@ -84,17 +84,19 @@ class HandshakeParser MessageSource messageSource; private: + using ParseMethod = void (HandshakeParser::*)(); + bool isSslv2Record(const SBuf &raw) const; void parseRecord(); void parseModernRecord(); void parseVersion2Record(); void parseMessages(); + void parseNonEmptyMessages(ParseMethod); - void parseChangeCipherCpecMessage(); + void parseChangeCipherSpecMessage(); void parseAlertMessage(); void parseHandshakeMessage(); - void parseApplicationDataMessage(); - void skipMessage(const char *msgType); + void skipPossiblyEmptyMessages(const char *msgType); bool parseRecordVersion2Try(); void parseVersion2HandshakeMessage(const SBuf &raw); @@ -115,9 +117,6 @@ class HandshakeParser const char *done; ///< not nil if we got what we were looking for - /// concatenated TLSPlaintext.fragments of TLSPlaintext.type - SBuf fragments; - /// TLS record layer (parsing uninterpreted data) Parser::BinaryTokenizer tkRecords;