Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP2:Update
squid.26147
CVE-2020-15049.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File CVE-2020-15049.patch of Package squid.26147
Ported from: commit ea12a34d338b962707d5078d6d1fc7c6eb119a22 Author: Alex Rousskov <rousskov@measurement-factory.com> Date: 2020-05-13 14:05:00 +0000 Validate Content-Length value prefix (#629) The new code detects all invalid Content-Length prefixes but the old code was already rejecting most invalid prefixes using strtoll(). The newly covered (and now rejected) invalid characters are * explicit "+" sign; * explicit "-" sign in "-0" values; * isspace(3) characters that are not (relaxed) OWS characters. In most deployment environments, the last set is probably empty because the relaxed OWS set has all the POSIX/C isspace(3) characters but the new line, and the new line is unlikely to sneak in past other checks. Thank you, Amit Klein <amit.klein@safebreach.com>, for elevating the importance of this 2016 TODO (added in commit a1b9ec2). Index: squid-3.5.21/CONTRIBUTORS =================================================================== --- squid-3.5.21.orig/CONTRIBUTORS +++ squid-3.5.21/CONTRIBUTORS @@ -23,6 +23,7 @@ Thank you! Alex Wu <alex_wu2012@hotmail.com> Alin Nastac <mrness@gentoo.org> Alter <alter@alter.org.ua> + Amit Klein <amit.klein@safebreach.com> Amos Jeffries <amosjeffries@squid-cache.org> Amos Jeffries <squid3@treenet.co.nz> Amos <squid3@treenet.co.nz> Index: squid-3.5.21/src/http/Makefile.am =================================================================== --- squid-3.5.21.orig/src/http/Makefile.am +++ squid-3.5.21/src/http/Makefile.am @@ -11,8 +11,12 @@ include $(top_srcdir)/src/TestHeaders.am noinst_LTLIBRARIES = libsquid-http.la libsquid_http_la_SOURCES = \ + ContentLengthInterpreter.cc \ + ContentLengthInterpreter.h \ MethodType.cc \ MethodType.h \ + Parser.h \ + Parser.cc \ ProtocolVersion.h \ StatusCode.cc \ StatusCode.h \ Index: squid-3.5.21/src/http/ContentLengthInterpreter.cc =================================================================== --- /dev/null +++ squid-3.5.21/src/http/ContentLengthInterpreter.cc @@ -0,0 +1,161 @@ +/* + * Copyright (C) 1996-2020 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +/* DEBUG: section 55 HTTP Header */ + +#include "squid.h" +#include "base/CharacterSet.h" +#include "Debug.h" +#include "http/ContentLengthInterpreter.h" +#include "http/Parser.h" +#include "HttpHeaderTools.h" +#include "SquidConfig.h" +#include "SquidString.h" +#include "StrList.h" + +Http::ContentLengthInterpreter::ContentLengthInterpreter(const int aDebugLevel): + value(-1), + headerWideProblem(nullptr), + debugLevel(aDebugLevel), + sawBad(false), + needsSanitizing(false), + sawGood(false) +{ +} + +/// checks whether all characters before the Content-Length number are allowed +/// \returns the start of the digit sequence (or nil on errors) +const char * +Http::ContentLengthInterpreter::findDigits(const char *prefix, const char * const valueEnd) const +{ + // skip leading OWS in RFC 7230's `OWS field-value OWS` + const CharacterSet &whitespace = Http::One::Parser::WhitespaceCharacters(); + while (prefix < valueEnd) { + const auto ch = *prefix; + if (CharacterSet::DIGIT[ch]) + return prefix; // common case: a pre-trimmed field value + if (!whitespace[ch]) + return nullptr; // (trimmed) length does not start with a digit + ++prefix; + } + return nullptr; // empty or whitespace-only value +} + +/// checks whether all characters after the Content-Length are allowed +bool +Http::ContentLengthInterpreter::goodSuffix(const char *suffix, const char * const end) const +{ + // optimize for the common case that does not need delimiters + if (suffix == end) + return true; + + for (const CharacterSet &delimiters = Http::One::Parser::DelimiterCharacters(); + suffix < end; ++suffix) { + if (!delimiters[*suffix]) + return false; + } + // needsSanitizing = true; // TODO: Always remove trailing whitespace? + return true; // including empty suffix +} + +/// handles a single-token Content-Length value +/// rawValue null-termination requirements are those of httpHeaderParseOffset() +bool +Http::ContentLengthInterpreter::checkValue(const char *rawValue, const int valueSize) +{ + Must(!sawBad); + + const auto valueEnd = rawValue + valueSize; + + const auto digits = findDigits(rawValue, valueEnd); + if (!digits) { + debugs(55, debugLevel, "WARNING: Leading garbage or empty value in" << Raw("Content-Length", rawValue, valueSize)); + sawBad = true; + return false; + } + + int64_t latestValue = -1; + char *suffix = nullptr; + + if (!httpHeaderParseOffset(digits, &latestValue, &suffix)) { + debugs(55, DBG_IMPORTANT, "WARNING: Malformed" << Raw("Content-Length", rawValue, valueSize)); + sawBad = true; + return false; + } + + if (latestValue < 0) { + debugs(55, debugLevel, "WARNING: Negative" << Raw("Content-Length", rawValue, valueSize)); + sawBad = true; + return false; + } + + // check for garbage after the number + if (!goodSuffix(suffix, valueEnd)) { + debugs(55, debugLevel, "WARNING: Trailing garbage in" << Raw("Content-Length", rawValue, valueSize)); + sawBad = true; + return false; + } + + if (sawGood) { + /* we have found at least two, possibly identical values */ + + needsSanitizing = true; // replace identical values with a single value + + const bool conflicting = value != latestValue; + if (conflicting) + headerWideProblem = "Conflicting"; // overwrite any lesser problem + else if (!headerWideProblem) // preserve a possibly worse problem + headerWideProblem = "Duplicate"; + + // with relaxed_header_parser, identical values are permitted + sawBad = !Config.onoff.relaxed_header_parser || conflicting; + return false; // conflicting or duplicate + } + + sawGood = true; + value = latestValue; + return true; +} + +/// handles Content-Length: a, b, c +bool +Http::ContentLengthInterpreter::checkList(const String &list) +{ + Must(!sawBad); + + if (!Config.onoff.relaxed_header_parser) { + debugs(55, debugLevel, "WARNING: List-like" << Raw("Content-Length", list.rawBuf(), list.size())); + sawBad = true; + return false; + } + + needsSanitizing = true; // remove extra commas (at least) + + const char *pos = nullptr; + const char *item = nullptr;; + int ilen = -1; + while (strListGetItem(&list, ',', &item, &ilen, &pos)) { + if (!checkValue(item, ilen) && sawBad) + break; + // keep going after a duplicate value to find conflicting ones + } + return false; // no need to keep this list field; it will be sanitized away +} + +bool +Http::ContentLengthInterpreter::checkField(const String &rawValue) +{ + if (sawBad) + return false; // one rotten apple is enough to spoil all of them + + // TODO: Optimize by always parsing the first integer first. + return rawValue.pos(',') ? + checkList(rawValue) : + checkValue(rawValue.rawBuf(), rawValue.size()); +} + Index: squid-3.5.21/src/http/ContentLengthInterpreter.h =================================================================== --- /dev/null +++ squid-3.5.21/src/http/ContentLengthInterpreter.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 1996-2020 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H +#define SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H + +class String; + +namespace Http +{ + +/// Finds the intended Content-Length value while parsing message-header fields. +/// Deals with complications such as value lists and/or repeated fields. +class ContentLengthInterpreter +{ +public: + explicit ContentLengthInterpreter(const int aDebugLevel); + + /// updates history based on the given message-header field + /// \return true iff the field should be added/remembered for future use + bool checkField(const String &field); + + /// intended Content-Length value if sawGood is set and sawBad is not set + /// meaningless otherwise + int64_t value; + + /* for debugging (declared here to minimize padding) */ + const char *headerWideProblem; ///< worst header-wide problem found (or nil) + const int debugLevel; ///< debugging level for certain warnings + + /// whether a malformed Content-Length value was present + bool sawBad; + + /// whether all remembered fields should be removed + /// removed fields ought to be replaced with the intended value (if known) + /// irrelevant if sawBad is set + bool needsSanitizing; + + /// whether a valid field value was present, possibly among problematic ones + /// irrelevant if sawBad is set + bool sawGood; + +protected: + const char *findDigits(const char *prefix, const char *valueEnd) const; + bool goodSuffix(const char *suffix, const char * const end) const; + bool checkValue(const char *start, const int size); + bool checkList(const String &list); +}; + +} // namespace Http + +#endif /* SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H */ + Index: squid-3.5.21/src/http/Parser.h =================================================================== --- /dev/null +++ squid-3.5.21/src/http/Parser.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 1996-2020 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef _SQUID_SRC_HTTP_ONE_PARSER_H +#define _SQUID_SRC_HTTP_ONE_PARSER_H + +#include "anyp/ProtocolVersion.h" +// #include "http/one/forward.h" +#include "http/StatusCode.h" +#include "src/SBuf.h" + +namespace Http { +namespace One { + +// Parser states +enum ParseState { + HTTP_PARSE_NONE, ///< initialized, but nothing usefully parsed yet + HTTP_PARSE_FIRST, ///< HTTP/1 message first-line + HTTP_PARSE_CHUNK_SZ, ///< HTTP/1.1 chunked encoding chunk-size + HTTP_PARSE_CHUNK_EXT, ///< HTTP/1.1 chunked encoding chunk-ext + HTTP_PARSE_CHUNK, ///< HTTP/1.1 chunked encoding chunk-data + HTTP_PARSE_MIME, ///< HTTP/1 mime-header block + HTTP_PARSE_DONE ///< parsed a message header, or reached a terminal syntax error +}; + +/** HTTP/1.x protocol parser + * + * Works on a raw character I/O buffer and tokenizes the content into + * the major CRLF delimited segments of an HTTP/1 procotol message: + * + * \item first-line (request-line / simple-request / status-line) + * \item mime-header 0*( header-name ':' SP field-value CRLF) + */ +class Parser : public RefCountable +{ +public: + typedef SBuf::size_type size_type; + +// Parser() : parseStatusCode(Http::scNone), parsingStage_(HTTP_PARSE_NONE), hackExpectsMime_(false) {} +// virtual ~Parser() {} + + /// Set this parser back to a default state. + /// Will DROP any reference to a buffer (does not free). +// virtual void clear() = 0; + + /// attempt to parse a message from the buffer + /// \retval true if a full message was found and parsed + /// \retval false if incomplete, invalid or no message was found +// virtual bool parse(const SBuf &aBuf) = 0; + + /** Whether the parser is waiting on more data to complete parsing a message. + * Use to distinguish between incomplete data and error results + * when parse() returns false. + */ +// bool needsMoreData() const {return parsingStage_!=HTTP_PARSE_DONE;} + + /// size in bytes of the first line including CRLF terminator +// virtual size_type firstLineSize() const = 0; + + /// size in bytes of the message headers including CRLF terminator(s) + /// but excluding first-line bytes +// size_type headerBlockSize() const {return mimeHeaderBlock_.length();} + + /// size in bytes of HTTP message block, includes first-line and mime headers + /// excludes any body/entity/payload bytes + /// excludes any garbage prefix before the first-line +// size_type messageHeaderSize() const {return firstLineSize() + headerBlockSize();} + + /// buffer containing HTTP mime headers, excluding message first-line. +// SBuf mimeHeader() const {return mimeHeaderBlock_;} + + /// the protocol label for this message +// const AnyP::ProtocolVersion & messageProtocol() const {return msgProtocol_;} + + /** + * Scan the mime header block (badly) for a Host header. + * + * BUG: omits lines when searching for headers with obs-fold or multiple entries. + * + * BUG: limits output to just 1KB when Squid accepts up to 64KB line length. + * + * \return A pointer to a field-value of the first matching field-name, or NULL. + */ +// char *getHostHeaderField(); + + /// the remaining unprocessed section of buffer +// const SBuf &remaining() const {return buf_;} + + /** + * HTTP status code resulting from the parse process. + * to be used on the invalid message handling. + * + * Http::scNone indicates incomplete parse, + * Http::scOkay indicates no error, + * other codes represent a parse error. + */ +// Http::StatusCode parseStatusCode; + + /// Whitespace between regular protocol elements. + /// Seen in RFCs as OWS, RWS, BWS, SP/HTAB but may be "relaxed" by us. + /// See also: DelimiterCharacters(). + static const CharacterSet &WhitespaceCharacters(); + + /// Whitespace between protocol elements in restricted contexts like + /// request line, status line, asctime-date, and credentials + /// Seen in RFCs as SP but may be "relaxed" by us. + /// See also: WhitespaceCharacters(). + /// XXX: Misnamed and overused. + static const CharacterSet &DelimiterCharacters(); + +//protected: + /** + * detect and skip the CRLF or (if tolerant) LF line terminator + * consume from the tokenizer. + * + * throws if non-terminator is detected. + * \retval true only if line terminator found. + * \retval false incomplete or missing line terminator, need more data. + */ +// bool skipLineTerminator(Http1::Tokenizer &tok) const; + + /** + * Scan to find the mime headers block for current message. + * + * \retval true If mime block (or a blocks non-existence) has been + * identified accurately within limit characters. + * mimeHeaderBlock_ has been updated and buf_ consumed. + * + * \retval false An error occurred, or no mime terminator found within limit. + */ +// bool grabMimeBlock(const char *which, const size_t limit); + + /// RFC 7230 section 2.6 - 7 magic octets +// static const SBuf Http1magic; + + /// bytes remaining to be parsed +// SBuf buf_; + + /// what stage the parser is currently up to +// ParseState parsingStage_; + + /// what protocol label has been found in the first line (if any) +// AnyP::ProtocolVersion msgProtocol_; + + /// buffer holding the mime headers (if any) +// SBuf mimeHeaderBlock_; + + /// Whether the invalid HTTP as HTTP/0.9 hack expects a mime header block +// bool hackExpectsMime_; + +//private: +// void cleanMimePrefix(); +// void unfoldMime(); +}; + +/// skips and, if needed, warns about RFC 7230 BWS ("bad" whitespace) +/// \returns true (always; unlike all the skip*() functions) +//bool ParseBws(Tokenizer &tok); + +/// the right debugs() level for logging HTTP violation messages +//int ErrorLevel(); + +} // namespace One +} // namespace Http + +#endif /* _SQUID_SRC_HTTP_ONE_PARSER_H */ + Index: squid-3.5.21/src/HttpHeaderTools.cc =================================================================== --- squid-3.5.21.orig/src/HttpHeaderTools.cc +++ squid-3.5.21/src/HttpHeaderTools.cc @@ -187,13 +187,28 @@ httpHeaderParseInt(const char *start, in } int -httpHeaderParseOffset(const char *start, int64_t * value) +httpHeaderParseOffset(const char *start, int64_t * value, char **endPtr) { + char *end = nullptr; errno = 0; - int64_t res = strtoll(start, NULL, 10); - if (!res && EINVAL == errno) /* maybe not portable? */ + int64_t res = strtoll(start, &end, 10); + + if (errno && !res) { + debugs(66, 7, "failed to parse malformed offset in " << start); + return 0; + } + if (errno == ERANGE && (res == LLONG_MIN || res == LLONG_MAX)) { // no overflow + debugs(66, 7, "failed to parse huge offset in " << start); + return 0; + } + if (start == end) { + debugs(66, 7, "failed to parse empty offset"); return 0; + } *value = res; + if (endPtr) + *endPtr = end; + debugs(66, 7, "offset " << start << " parsed as " << res); return 1; } Index: squid-3.5.21/src/HttpHeaderTools.h =================================================================== --- squid-3.5.21.orig/src/HttpHeaderTools.h +++ squid-3.5.21/src/HttpHeaderTools.h @@ -113,7 +113,13 @@ public: bool quoted; }; -int httpHeaderParseOffset(const char *start, int64_t * off); +/// A strtoll(10) wrapper that checks for strtoll() failures and other problems. +/// XXX: This function is not fully compatible with some HTTP syntax rules. +/// Just like strtoll(), allows whitespace prefix, a sign, and _any_ suffix. +/// Requires at least one digit to be present. +/// Sets "off" and "end" arguments if and only if no problems were found. +/// \return true if and only if no problems were found. +int httpHeaderParseOffset(const char *start, int64_t * off, char **endPtr = nullptr); HttpHeaderFieldInfo *httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs * attrs, int count); void httpHeaderDestroyFieldsInfo(HttpHeaderFieldInfo * info, int count); Index: squid-3.5.21/src/HttpHeader.cc =================================================================== --- squid-3.5.21.orig/src/HttpHeader.cc +++ squid-3.5.21/src/HttpHeader.cc @@ -28,6 +28,7 @@ #include "Store.h" #include "StrList.h" #include "TimeOrTag.h" +#include "http/ContentLengthInterpreter.h" #include <algorithm> @@ -588,7 +589,7 @@ int HttpHeader::parse(const char *header_start, const char *header_end) { const char *field_ptr = header_start; - HttpHeaderEntry *e, *e2; + HttpHeaderEntry *e; int warnOnError = (Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2); PROF_start(HttpHeaderParse); @@ -605,6 +606,7 @@ HttpHeader::parse(const char *header_sta return reset(); } + Http::ContentLengthInterpreter clen(warnOnError); /* common format headers are "<name>:[ws]<value>" lines delimited by <CRLF>. * continuation lines start with a (single) space or tab */ while (field_ptr < header_end) { @@ -690,43 +692,15 @@ HttpHeader::parse(const char *header_sta return reset(); } - // XXX: RFC 7230 Section 3.3.3 item #4 requires sending a 502 error in - // several cases that we do not yet cover. TODO: Rewrite to cover more. - if (e->id == HDR_CONTENT_LENGTH && (e2 = findEntry(e->id)) != NULL) { - if (e->value != e2->value) { - int64_t l1, l2; - debugs(55, warnOnError, "WARNING: found two conflicting content-length headers in {" << - getStringPrefix(header_start, header_end) << "}"); - - if (!Config.onoff.relaxed_header_parser) { - delete e; - PROF_stop(HttpHeaderParse); - return reset(); - } - - if (!httpHeaderParseOffset(e->value.termedBuf(), &l1)) { - debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e->value << "'"); - delete e; - continue; - } else if (!httpHeaderParseOffset(e2->value.termedBuf(), &l2)) { - debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e2->value << "'"); - delById(e2->id); - } else { - if (l1 != l2) - conflictingContentLength_ = true; - delete e; - continue; - } - } else { - debugs(55, warnOnError, "NOTICE: found double content-length header"); - delete e; + if (e->id == HDR_CONTENT_LENGTH && !clen.checkField(e->value)) { + delete e; - if (Config.onoff.relaxed_header_parser) - continue; + if (Config.onoff.relaxed_header_parser) + continue; // clen has printed any necessary warnings - PROF_stop(HttpHeaderParse); - return reset(); - } + PROF_stop(HttpHeaderParse); + clean(); + return 0; } if (e->id == HDR_OTHER && stringHasWhitespace(e->name.termedBuf())) { @@ -743,14 +717,29 @@ HttpHeader::parse(const char *header_sta addEntry(e); } + if (clen.headerWideProblem) { + debugs(55, warnOnError, "WARNING: " << clen.headerWideProblem << + " Content-Length field values in" << + Raw("header", header_start, header_end - header_start)); + } + if (chunked()) { // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding delById(HDR_CONTENT_LENGTH); // RFC 7230 section 3.3.3 #4: ignore Content-Length conflicts with Transfer-Encoding conflictingContentLength_ = false; - } else if (conflictingContentLength_) { - // ensure our callers do not see the conflicting Content-Length value + } else if (clen.sawBad) { + // ensure our callers do not accidentally see bad Content-Length values delById(HDR_CONTENT_LENGTH); + conflictingContentLength_ = true; // TODO: Rename to badContentLength_. + } else if (clen.needsSanitizing) { + // RFC 7230 section 3.3.2: MUST either reject or ... [sanitize]; + // ensure our callers see a clean Content-Length value or none at all + delById(HDR_CONTENT_LENGTH); + if (clen.sawGood) { + putInt64(HDR_CONTENT_LENGTH, clen.value); + debugs(55, 5, "sanitized Content-Length to be " << clen.value); + } } PROF_stop(HttpHeaderParse); Index: squid-3.5.21/src/http/Parser.cc =================================================================== --- /dev/null +++ squid-3.5.21/src/http/Parser.cc @@ -0,0 +1,45 @@ +/* + * Copyright (C) 1996-2020 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" +#include "Debug.h" +#include "http/Parser.h" +#include "base/CharacterSet.h" +#include "mime_header.h" +#include "SquidConfig.h" + +/// characters HTTP permits tolerant parsers to accept as delimiters +static const CharacterSet & +RelaxedDelimiterCharacters() +{ + // RFC 7230 section 3.5 + // tolerant parser MAY accept any of SP, HTAB, VT (%x0B), FF (%x0C), + // or bare CR as whitespace between request-line fields + static const CharacterSet RelaxedDels = + (CharacterSet::SP + + CharacterSet::HTAB + + CharacterSet("VT,FF","\x0B\x0C") + + CharacterSet::CR).rename("relaxed-WSP"); + + return RelaxedDels; +} + +const CharacterSet & +Http::One::Parser::WhitespaceCharacters() +{ + return Config.onoff.relaxed_header_parser ? + RelaxedDelimiterCharacters() : CharacterSet::WSP; +} + +const CharacterSet & +Http::One::Parser::DelimiterCharacters() +{ + return Config.onoff.relaxed_header_parser ? + RelaxedDelimiterCharacters() : CharacterSet::SP; +} +
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor