|
|
07d5a6 |
From abdf3942a848b3de8c4fcdbccf15139b1ed0d9c2 Mon Sep 17 00:00:00 2001
|
|
|
07d5a6 |
From: Lubos Uhliarik <luhliari@redhat.com>
|
|
|
07d5a6 |
Date: Mon, 3 Aug 2020 16:48:15 +0200
|
|
|
07d5a6 |
Subject: [PATCH] Fix for CVE-2020-15049
|
|
|
07d5a6 |
|
|
|
07d5a6 |
---
|
|
|
07d5a6 |
src/HttpHeader.cc | 85 ++++++------
|
|
|
07d5a6 |
src/HttpHeaderTools.cc | 27 ++++
|
|
|
07d5a6 |
src/HttpHeaderTools.h | 8 +-
|
|
|
07d5a6 |
src/http/ContentLengthInterpreter.cc | 190 +++++++++++++++++++++++++++
|
|
|
07d5a6 |
src/http/ContentLengthInterpreter.h | 66 ++++++++++
|
|
|
07d5a6 |
src/http/Makefile.am | 2 +
|
|
|
07d5a6 |
src/http/Makefile.in | 4 +-
|
|
|
07d5a6 |
7 files changed, 337 insertions(+), 45 deletions(-)
|
|
|
07d5a6 |
create mode 100644 src/http/ContentLengthInterpreter.cc
|
|
|
07d5a6 |
create mode 100644 src/http/ContentLengthInterpreter.h
|
|
|
07d5a6 |
|
|
|
07d5a6 |
diff --git a/src/HttpHeader.cc b/src/HttpHeader.cc
|
|
|
07d5a6 |
index 7e8c77e..ef60c02 100644
|
|
|
07d5a6 |
--- a/src/HttpHeader.cc
|
|
|
07d5a6 |
+++ b/src/HttpHeader.cc
|
|
|
07d5a6 |
@@ -11,6 +11,7 @@
|
|
|
07d5a6 |
#include "squid.h"
|
|
|
07d5a6 |
#include "base64.h"
|
|
|
07d5a6 |
#include "globals.h"
|
|
|
07d5a6 |
+#include "http/ContentLengthInterpreter.h"
|
|
|
07d5a6 |
#include "HttpHdrCc.h"
|
|
|
07d5a6 |
#include "HttpHdrContRange.h"
|
|
|
07d5a6 |
#include "HttpHdrSc.h"
|
|
|
07d5a6 |
@@ -588,7 +589,6 @@ int
|
|
|
07d5a6 |
HttpHeader::parse(const char *header_start, const char *header_end)
|
|
|
07d5a6 |
{
|
|
|
07d5a6 |
const char *field_ptr = header_start;
|
|
|
07d5a6 |
- HttpHeaderEntry *e, *e2;
|
|
|
07d5a6 |
int warnOnError = (Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2);
|
|
|
07d5a6 |
|
|
|
07d5a6 |
PROF_start(HttpHeaderParse);
|
|
|
07d5a6 |
@@ -605,6 +605,7 @@ HttpHeader::parse(const char *header_start, const char *header_end)
|
|
|
07d5a6 |
return reset();
|
|
|
07d5a6 |
}
|
|
|
07d5a6 |
|
|
|
07d5a6 |
+ Http::ContentLengthInterpreter clen(warnOnError);
|
|
|
07d5a6 |
/* common format headers are "<name>:[ws]<value>" lines delimited by <CRLF>.
|
|
|
07d5a6 |
* continuation lines start with a (single) space or tab */
|
|
|
07d5a6 |
while (field_ptr < header_end) {
|
|
|
07d5a6 |
@@ -681,6 +682,7 @@ HttpHeader::parse(const char *header_start, const char *header_end)
|
|
|
07d5a6 |
break; /* terminating blank line */
|
|
|
07d5a6 |
}
|
|
|
07d5a6 |
|
|
|
07d5a6 |
+ HttpHeaderEntry *e;
|
|
|
07d5a6 |
if ((e = HttpHeaderEntry::parse(field_start, field_end)) == NULL) {
|
|
|
07d5a6 |
debugs(55, warnOnError, "WARNING: unparseable HTTP header field {" <<
|
|
|
07d5a6 |
getStringPrefix(field_start, field_end) << "}");
|
|
|
07d5a6 |
@@ -693,45 +695,19 @@ HttpHeader::parse(const char *header_start, const char *header_end)
|
|
|
07d5a6 |
return reset();
|
|
|
07d5a6 |
}
|
|
|
07d5a6 |
|
|
|
07d5a6 |
- // XXX: RFC 7230 Section 3.3.3 item #4 requires sending a 502 error in
|
|
|
07d5a6 |
- // several cases that we do not yet cover. TODO: Rewrite to cover more.
|
|
|
07d5a6 |
- if (e->id == HDR_CONTENT_LENGTH && (e2 = findEntry(e->id)) != NULL) {
|
|
|
07d5a6 |
- if (e->value != e2->value) {
|
|
|
07d5a6 |
- int64_t l1, l2;
|
|
|
07d5a6 |
- debugs(55, warnOnError, "WARNING: found two conflicting content-length headers in {" <<
|
|
|
07d5a6 |
- getStringPrefix(header_start, header_end) << "}");
|
|
|
07d5a6 |
-
|
|
|
07d5a6 |
- if (!Config.onoff.relaxed_header_parser) {
|
|
|
07d5a6 |
- delete e;
|
|
|
07d5a6 |
- PROF_stop(HttpHeaderParse);
|
|
|
07d5a6 |
- return reset();
|
|
|
07d5a6 |
- }
|
|
|
07d5a6 |
|
|
|
07d5a6 |
- if (!httpHeaderParseOffset(e->value.termedBuf(), &l1)) {
|
|
|
07d5a6 |
- debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e->value << "'");
|
|
|
07d5a6 |
- delete e;
|
|
|
07d5a6 |
- continue;
|
|
|
07d5a6 |
- } else if (!httpHeaderParseOffset(e2->value.termedBuf(), &l2)) {
|
|
|
07d5a6 |
- debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e2->value << "'");
|
|
|
07d5a6 |
- delById(e2->id);
|
|
|
07d5a6 |
- } else {
|
|
|
07d5a6 |
- if (l1 != l2)
|
|
|
07d5a6 |
- conflictingContentLength_ = true;
|
|
|
07d5a6 |
- delete e;
|
|
|
07d5a6 |
- continue;
|
|
|
07d5a6 |
- }
|
|
|
07d5a6 |
- } else {
|
|
|
07d5a6 |
- debugs(55, warnOnError, "NOTICE: found double content-length header");
|
|
|
07d5a6 |
- delete e;
|
|
|
07d5a6 |
+ if (e->id == HDR_CONTENT_LENGTH && !clen.checkField(e->value)) {
|
|
|
07d5a6 |
+ delete e;
|
|
|
07d5a6 |
|
|
|
07d5a6 |
- if (Config.onoff.relaxed_header_parser)
|
|
|
07d5a6 |
- continue;
|
|
|
07d5a6 |
+ if (Config.onoff.relaxed_header_parser)
|
|
|
07d5a6 |
+ continue; // clen has printed any necessary warnings
|
|
|
07d5a6 |
|
|
|
07d5a6 |
- PROF_stop(HttpHeaderParse);
|
|
|
07d5a6 |
- return reset();
|
|
|
07d5a6 |
- }
|
|
|
07d5a6 |
+ PROF_stop(HttpHeaderParse);
|
|
|
07d5a6 |
+ clean();
|
|
|
07d5a6 |
+ return 0;
|
|
|
07d5a6 |
}
|
|
|
07d5a6 |
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
if (e->id == HDR_OTHER && stringHasWhitespace(e->name.termedBuf())) {
|
|
|
07d5a6 |
debugs(55, warnOnError, "WARNING: found whitespace in HTTP header name {" <<
|
|
|
07d5a6 |
getStringPrefix(field_start, field_end) << "}");
|
|
|
07d5a6 |
@@ -746,6 +722,32 @@ HttpHeader::parse(const char *header_start, const char *header_end)
|
|
|
07d5a6 |
addEntry(e);
|
|
|
07d5a6 |
}
|
|
|
07d5a6 |
|
|
|
07d5a6 |
+ if (clen.headerWideProblem) {
|
|
|
07d5a6 |
+ debugs(55, warnOnError, "WARNING: " << clen.headerWideProblem <<
|
|
|
07d5a6 |
+ " Content-Length field values in" <<
|
|
|
07d5a6 |
+ Raw("header", header_start, (size_t)(header_end - header_start)));
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ if (chunked()) {
|
|
|
07d5a6 |
+ // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
|
|
|
07d5a6 |
+ // RFC 7230 section 3.3.3 #3: Transfer-Encoding overwrites Content-Length
|
|
|
07d5a6 |
+ delById(HDR_CONTENT_LENGTH);
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ // and clen state becomes irrelevant
|
|
|
07d5a6 |
+ } else if (clen.sawBad) {
|
|
|
07d5a6 |
+ // ensure our callers do not accidentally see bad Content-Length values
|
|
|
07d5a6 |
+ delById(HDR_CONTENT_LENGTH);
|
|
|
07d5a6 |
+ conflictingContentLength_ = true; // TODO: Rename to badContentLength_.
|
|
|
07d5a6 |
+ } else if (clen.needsSanitizing) {
|
|
|
07d5a6 |
+ // RFC 7230 section 3.3.2: MUST either reject or ... [sanitize];
|
|
|
07d5a6 |
+ // ensure our callers see a clean Content-Length value or none at all
|
|
|
07d5a6 |
+ delById(HDR_CONTENT_LENGTH);
|
|
|
07d5a6 |
+ if (clen.sawGood) {
|
|
|
07d5a6 |
+ putInt64(HDR_CONTENT_LENGTH, clen.value);
|
|
|
07d5a6 |
+ debugs(55, 5, "sanitized Content-Length to be " << clen.value);
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
if (chunked()) {
|
|
|
07d5a6 |
// RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
|
|
|
07d5a6 |
delById(HDR_CONTENT_LENGTH);
|
|
|
07d5a6 |
@@ -1722,6 +1724,7 @@ HttpHeaderEntry::getInt() const
|
|
|
07d5a6 |
assert_eid (id);
|
|
|
07d5a6 |
assert (Headers[id].type == ftInt);
|
|
|
07d5a6 |
int val = -1;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
int ok = httpHeaderParseInt(value.termedBuf(), &val;;
|
|
|
07d5a6 |
httpHeaderNoteParsedEntry(id, value, !ok);
|
|
|
07d5a6 |
/* XXX: Should we check ok - ie
|
|
|
07d5a6 |
@@ -1733,15 +1736,11 @@ HttpHeaderEntry::getInt() const
|
|
|
07d5a6 |
int64_t
|
|
|
07d5a6 |
HttpHeaderEntry::getInt64() const
|
|
|
07d5a6 |
{
|
|
|
07d5a6 |
- assert_eid (id);
|
|
|
07d5a6 |
- assert (Headers[id].type == ftInt64);
|
|
|
07d5a6 |
int64_t val = -1;
|
|
|
07d5a6 |
- int ok = httpHeaderParseOffset(value.termedBuf(), &val;;
|
|
|
07d5a6 |
- httpHeaderNoteParsedEntry(id, value, !ok);
|
|
|
07d5a6 |
- /* XXX: Should we check ok - ie
|
|
|
07d5a6 |
- * return ok ? -1 : value;
|
|
|
07d5a6 |
- */
|
|
|
07d5a6 |
- return val;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ const bool ok = httpHeaderParseOffset(value.termedBuf(), &val;;
|
|
|
07d5a6 |
+ httpHeaderNoteParsedEntry(id, value, ok);
|
|
|
07d5a6 |
+ return val; // remains -1 if !ok (XXX: bad method API)
|
|
|
07d5a6 |
}
|
|
|
07d5a6 |
|
|
|
07d5a6 |
static void
|
|
|
07d5a6 |
diff --git a/src/HttpHeaderTools.cc b/src/HttpHeaderTools.cc
|
|
|
07d5a6 |
index d8c29d8..02087cd 100644
|
|
|
07d5a6 |
--- a/src/HttpHeaderTools.cc
|
|
|
07d5a6 |
+++ b/src/HttpHeaderTools.cc
|
|
|
07d5a6 |
@@ -188,6 +188,33 @@ httpHeaderParseInt(const char *start, int *value)
|
|
|
07d5a6 |
return 1;
|
|
|
07d5a6 |
}
|
|
|
07d5a6 |
|
|
|
07d5a6 |
+bool
|
|
|
07d5a6 |
+httpHeaderParseOffset(const char *start, int64_t *value, char **endPtr)
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ char *end = nullptr;
|
|
|
07d5a6 |
+ errno = 0;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ const int64_t res = strtoll(start, &end, 10);
|
|
|
07d5a6 |
+ if (errno && !res) {
|
|
|
07d5a6 |
+ debugs(66, 7, "failed to parse malformed offset in " << start);
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+ if (errno == ERANGE && (res == LLONG_MIN || res == LLONG_MAX)) { // no overflow
|
|
|
07d5a6 |
+ debugs(66, 7, "failed to parse huge offset in " << start);
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+ if (start == end) {
|
|
|
07d5a6 |
+ debugs(66, 7, "failed to parse empty offset");
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+ *value = res;
|
|
|
07d5a6 |
+ if (endPtr)
|
|
|
07d5a6 |
+ *endPtr = end;
|
|
|
07d5a6 |
+ debugs(66, 7, "offset " << start << " parsed as " << res);
|
|
|
07d5a6 |
+ return true;
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
int
|
|
|
07d5a6 |
httpHeaderParseOffset(const char *start, int64_t * value)
|
|
|
07d5a6 |
{
|
|
|
07d5a6 |
diff --git a/src/HttpHeaderTools.h b/src/HttpHeaderTools.h
|
|
|
07d5a6 |
index 509d940..2d97ad4 100644
|
|
|
07d5a6 |
--- a/src/HttpHeaderTools.h
|
|
|
07d5a6 |
+++ b/src/HttpHeaderTools.h
|
|
|
07d5a6 |
@@ -113,7 +113,13 @@ public:
|
|
|
07d5a6 |
bool quoted;
|
|
|
07d5a6 |
};
|
|
|
07d5a6 |
|
|
|
07d5a6 |
-int httpHeaderParseOffset(const char *start, int64_t * off);
|
|
|
07d5a6 |
+/// A strtoll(10) wrapper that checks for strtoll() failures and other problems.
|
|
|
07d5a6 |
+/// XXX: This function is not fully compatible with some HTTP syntax rules.
|
|
|
07d5a6 |
+/// Just like strtoll(), allows whitespace prefix, a sign, and _any_ suffix.
|
|
|
07d5a6 |
+/// Requires at least one digit to be present.
|
|
|
07d5a6 |
+/// Sets "off" and "end" arguments if and only if no problems were found.
|
|
|
07d5a6 |
+/// \return true if and only if no problems were found.
|
|
|
07d5a6 |
+bool httpHeaderParseOffset(const char *start, int64_t *offPtr, char **endPtr = nullptr);
|
|
|
07d5a6 |
|
|
|
07d5a6 |
HttpHeaderFieldInfo *httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs * attrs, int count);
|
|
|
07d5a6 |
void httpHeaderDestroyFieldsInfo(HttpHeaderFieldInfo * info, int count);
|
|
|
07d5a6 |
diff --git a/src/http/ContentLengthInterpreter.cc b/src/http/ContentLengthInterpreter.cc
|
|
|
07d5a6 |
new file mode 100644
|
|
|
07d5a6 |
index 0000000..1d40f4a
|
|
|
07d5a6 |
--- /dev/null
|
|
|
07d5a6 |
+++ b/src/http/ContentLengthInterpreter.cc
|
|
|
07d5a6 |
@@ -0,0 +1,190 @@
|
|
|
07d5a6 |
+/*
|
|
|
07d5a6 |
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
|
|
|
07d5a6 |
+ *
|
|
|
07d5a6 |
+ * Squid software is distributed under GPLv2+ license and includes
|
|
|
07d5a6 |
+ * contributions from numerous individuals and organizations.
|
|
|
07d5a6 |
+ * Please see the COPYING and CONTRIBUTORS files for details.
|
|
|
07d5a6 |
+ */
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+/* DEBUG: section 55 HTTP Header */
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+#include "squid.h"
|
|
|
07d5a6 |
+#include "base/CharacterSet.h"
|
|
|
07d5a6 |
+#include "Debug.h"
|
|
|
07d5a6 |
+#include "http/ContentLengthInterpreter.h"
|
|
|
07d5a6 |
+#include "HttpHeaderTools.h"
|
|
|
07d5a6 |
+#include "SquidConfig.h"
|
|
|
07d5a6 |
+#include "SquidString.h"
|
|
|
07d5a6 |
+#include "StrList.h"
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::ContentLengthInterpreter(const int aDebugLevel):
|
|
|
07d5a6 |
+ value(-1),
|
|
|
07d5a6 |
+ headerWideProblem(nullptr),
|
|
|
07d5a6 |
+ debugLevel(aDebugLevel),
|
|
|
07d5a6 |
+ sawBad(false),
|
|
|
07d5a6 |
+ needsSanitizing(false),
|
|
|
07d5a6 |
+ sawGood(false)
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+/// characters HTTP permits tolerant parsers to accept as delimiters
|
|
|
07d5a6 |
+static const CharacterSet &
|
|
|
07d5a6 |
+RelaxedDelimiterCharacters()
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ // RFC 7230 section 3.5
|
|
|
07d5a6 |
+ // tolerant parser MAY accept any of SP, HTAB, VT (%x0B), FF (%x0C),
|
|
|
07d5a6 |
+ // or bare CR as whitespace between request-line fields
|
|
|
07d5a6 |
+ static const CharacterSet RelaxedDels =
|
|
|
07d5a6 |
+ (CharacterSet::SP +
|
|
|
07d5a6 |
+ CharacterSet::HTAB +
|
|
|
07d5a6 |
+ CharacterSet("VT,FF","\x0B\x0C") +
|
|
|
07d5a6 |
+ CharacterSet::CR).rename("relaxed-WSP");
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ return RelaxedDels;
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+const CharacterSet &
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::WhitespaceCharacters()
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ return Config.onoff.relaxed_header_parser ?
|
|
|
07d5a6 |
+ RelaxedDelimiterCharacters() : CharacterSet::WSP;
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+const CharacterSet &
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::DelimiterCharacters()
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ return Config.onoff.relaxed_header_parser ?
|
|
|
07d5a6 |
+ RelaxedDelimiterCharacters() : CharacterSet::SP;
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+/// checks whether all characters before the Content-Length number are allowed
|
|
|
07d5a6 |
+/// \returns the start of the digit sequence (or nil on errors)
|
|
|
07d5a6 |
+const char *
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::findDigits(const char *prefix, const char * const valueEnd) const
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ // skip leading OWS in RFC 7230's `OWS field-value OWS`
|
|
|
07d5a6 |
+ const CharacterSet &whitespace = WhitespaceCharacters();
|
|
|
07d5a6 |
+ while (prefix < valueEnd) {
|
|
|
07d5a6 |
+ const auto ch = *prefix;
|
|
|
07d5a6 |
+ if (CharacterSet::DIGIT[ch])
|
|
|
07d5a6 |
+ return prefix; // common case: a pre-trimmed field value
|
|
|
07d5a6 |
+ if (!whitespace[ch])
|
|
|
07d5a6 |
+ return nullptr; // (trimmed) length does not start with a digit
|
|
|
07d5a6 |
+ ++prefix;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+ return nullptr; // empty or whitespace-only value
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+/// checks whether all characters after the Content-Length are allowed
|
|
|
07d5a6 |
+bool
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::goodSuffix(const char *suffix, const char * const end) const
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ // optimize for the common case that does not need delimiters
|
|
|
07d5a6 |
+ if (suffix == end)
|
|
|
07d5a6 |
+ return true;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ for (const CharacterSet &delimiters = DelimiterCharacters();
|
|
|
07d5a6 |
+ suffix < end; ++suffix) {
|
|
|
07d5a6 |
+ if (!delimiters[*suffix])
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+ // needsSanitizing = true; // TODO: Always remove trailing whitespace?
|
|
|
07d5a6 |
+ return true; // including empty suffix
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+/// handles a single-token Content-Length value
|
|
|
07d5a6 |
+/// rawValue null-termination requirements are those of httpHeaderParseOffset()
|
|
|
07d5a6 |
+bool
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::checkValue(const char *rawValue, const int valueSize)
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ Must(!sawBad);
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ const auto valueEnd = rawValue + valueSize;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ const auto digits = findDigits(rawValue, valueEnd);
|
|
|
07d5a6 |
+ if (!digits) {
|
|
|
07d5a6 |
+ debugs(55, debugLevel, "WARNING: Leading garbage or empty value in" << Raw("Content-Length", rawValue, valueSize));
|
|
|
07d5a6 |
+ sawBad = true;
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ int64_t latestValue = -1;
|
|
|
07d5a6 |
+ char *suffix = nullptr;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ if (!httpHeaderParseOffset(digits, &latestValue, &suffix)) {
|
|
|
07d5a6 |
+ debugs(55, DBG_IMPORTANT, "WARNING: Malformed" << Raw("Content-Length", rawValue, valueSize));
|
|
|
07d5a6 |
+ sawBad = true;
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ if (latestValue < 0) {
|
|
|
07d5a6 |
+ debugs(55, debugLevel, "WARNING: Negative" << Raw("Content-Length", rawValue, valueSize));
|
|
|
07d5a6 |
+ sawBad = true;
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ // check for garbage after the number
|
|
|
07d5a6 |
+ if (!goodSuffix(suffix, valueEnd)) {
|
|
|
07d5a6 |
+ debugs(55, debugLevel, "WARNING: Trailing garbage in" << Raw("Content-Length", rawValue, valueSize));
|
|
|
07d5a6 |
+ sawBad = true;
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ if (sawGood) {
|
|
|
07d5a6 |
+ /* we have found at least two, possibly identical values */
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ needsSanitizing = true; // replace identical values with a single value
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ const bool conflicting = value != latestValue;
|
|
|
07d5a6 |
+ if (conflicting)
|
|
|
07d5a6 |
+ headerWideProblem = "Conflicting"; // overwrite any lesser problem
|
|
|
07d5a6 |
+ else if (!headerWideProblem) // preserve a possibly worse problem
|
|
|
07d5a6 |
+ headerWideProblem = "Duplicate";
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ // with relaxed_header_parser, identical values are permitted
|
|
|
07d5a6 |
+ sawBad = !Config.onoff.relaxed_header_parser || conflicting;
|
|
|
07d5a6 |
+ return false; // conflicting or duplicate
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ sawGood = true;
|
|
|
07d5a6 |
+ value = latestValue;
|
|
|
07d5a6 |
+ return true;
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+/// handles Content-Length: a, b, c
|
|
|
07d5a6 |
+bool
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::checkList(const String &list)
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ Must(!sawBad);
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ if (!Config.onoff.relaxed_header_parser) {
|
|
|
07d5a6 |
+ debugs(55, debugLevel, "WARNING: List-like" << Raw("Content-Length", list.rawBuf(), list.size()));
|
|
|
07d5a6 |
+ sawBad = true;
|
|
|
07d5a6 |
+ return false;
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ needsSanitizing = true; // remove extra commas (at least)
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ const char *pos = nullptr;
|
|
|
07d5a6 |
+ const char *item = nullptr;;
|
|
|
07d5a6 |
+ int ilen = -1;
|
|
|
07d5a6 |
+ while (strListGetItem(&list, ',', &item, &ilen, &pos)) {
|
|
|
07d5a6 |
+ if (!checkValue(item, ilen) && sawBad)
|
|
|
07d5a6 |
+ break;
|
|
|
07d5a6 |
+ // keep going after a duplicate value to find conflicting ones
|
|
|
07d5a6 |
+ }
|
|
|
07d5a6 |
+ return false; // no need to keep this list field; it will be sanitized away
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+bool
|
|
|
07d5a6 |
+Http::ContentLengthInterpreter::checkField(const String &rawValue)
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+ if (sawBad)
|
|
|
07d5a6 |
+ return false; // one rotten apple is enough to spoil all of them
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ // TODO: Optimize by always parsing the first integer first.
|
|
|
07d5a6 |
+ return rawValue.pos(',') ?
|
|
|
07d5a6 |
+ checkList(rawValue) :
|
|
|
07d5a6 |
+ checkValue(rawValue.rawBuf(), rawValue.size());
|
|
|
07d5a6 |
+}
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
diff --git a/src/http/ContentLengthInterpreter.h b/src/http/ContentLengthInterpreter.h
|
|
|
07d5a6 |
new file mode 100644
|
|
|
07d5a6 |
index 0000000..ba7080c
|
|
|
07d5a6 |
--- /dev/null
|
|
|
07d5a6 |
+++ b/src/http/ContentLengthInterpreter.h
|
|
|
07d5a6 |
@@ -0,0 +1,66 @@
|
|
|
07d5a6 |
+/*
|
|
|
07d5a6 |
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
|
|
|
07d5a6 |
+ *
|
|
|
07d5a6 |
+ * Squid software is distributed under GPLv2+ license and includes
|
|
|
07d5a6 |
+ * contributions from numerous individuals and organizations.
|
|
|
07d5a6 |
+ * Please see the COPYING and CONTRIBUTORS files for details.
|
|
|
07d5a6 |
+ */
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+#ifndef SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H
|
|
|
07d5a6 |
+#define SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+class String;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+namespace Http
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+/// Finds the intended Content-Length value while parsing message-header fields.
|
|
|
07d5a6 |
+/// Deals with complications such as value lists and/or repeated fields.
|
|
|
07d5a6 |
+class ContentLengthInterpreter
|
|
|
07d5a6 |
+{
|
|
|
07d5a6 |
+public:
|
|
|
07d5a6 |
+ explicit ContentLengthInterpreter(const int aDebugLevel);
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ /// updates history based on the given message-header field
|
|
|
07d5a6 |
+ /// \return true iff the field should be added/remembered for future use
|
|
|
07d5a6 |
+ bool checkField(const String &field);
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ /// intended Content-Length value if sawGood is set and sawBad is not set
|
|
|
07d5a6 |
+ /// meaningless otherwise
|
|
|
07d5a6 |
+ int64_t value;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ /* for debugging (declared here to minimize padding) */
|
|
|
07d5a6 |
+ const char *headerWideProblem; ///< worst header-wide problem found (or nil)
|
|
|
07d5a6 |
+ const int debugLevel; ///< debugging level for certain warnings
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ /// whether a malformed Content-Length value was present
|
|
|
07d5a6 |
+ bool sawBad;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ /// whether all remembered fields should be removed
|
|
|
07d5a6 |
+ /// removed fields ought to be replaced with the intended value (if known)
|
|
|
07d5a6 |
+ /// irrelevant if sawBad is set
|
|
|
07d5a6 |
+ bool needsSanitizing;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ /// whether a valid field value was present, possibly among problematic ones
|
|
|
07d5a6 |
+ /// irrelevant if sawBad is set
|
|
|
07d5a6 |
+ bool sawGood;
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ /// Whitespace between protocol elements in restricted contexts like
|
|
|
07d5a6 |
+ /// request line, status line, asctime-date, and credentials
|
|
|
07d5a6 |
+ /// Seen in RFCs as SP but may be "relaxed" by us.
|
|
|
07d5a6 |
+ /// See also: WhitespaceCharacters().
|
|
|
07d5a6 |
+ /// XXX: Misnamed and overused.
|
|
|
07d5a6 |
+ static const CharacterSet &DelimiterCharacters();
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+ static const CharacterSet &WhitespaceCharacters();
|
|
|
07d5a6 |
+protected:
|
|
|
07d5a6 |
+ const char *findDigits(const char *prefix, const char *valueEnd) const;
|
|
|
07d5a6 |
+ bool goodSuffix(const char *suffix, const char * const end) const;
|
|
|
07d5a6 |
+ bool checkValue(const char *start, const int size);
|
|
|
07d5a6 |
+ bool checkList(const String &list);
|
|
|
07d5a6 |
+};
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+} // namespace Http
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
+#endif /* SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H */
|
|
|
07d5a6 |
+
|
|
|
07d5a6 |
diff --git a/src/http/Makefile.am b/src/http/Makefile.am
|
|
|
07d5a6 |
index 7887ef0..78b503e 100644
|
|
|
07d5a6 |
--- a/src/http/Makefile.am
|
|
|
07d5a6 |
+++ b/src/http/Makefile.am
|
|
|
07d5a6 |
@@ -11,6 +11,8 @@ include $(top_srcdir)/src/TestHeaders.am
|
|
|
07d5a6 |
noinst_LTLIBRARIES = libsquid-http.la
|
|
|
07d5a6 |
|
|
|
07d5a6 |
libsquid_http_la_SOURCES = \
|
|
|
07d5a6 |
+ ContentLengthInterpreter.cc \
|
|
|
07d5a6 |
+ ContentLengthInterpreter.h \
|
|
|
07d5a6 |
MethodType.cc \
|
|
|
07d5a6 |
MethodType.h \
|
|
|
07d5a6 |
ProtocolVersion.h \
|
|
|
07d5a6 |
diff --git a/src/http/Makefile.in b/src/http/Makefile.in
|
|
|
07d5a6 |
index f5b62fb..c7891ae 100644
|
|
|
07d5a6 |
--- a/src/http/Makefile.in
|
|
|
07d5a6 |
+++ b/src/http/Makefile.in
|
|
|
07d5a6 |
@@ -160,7 +160,7 @@ CONFIG_CLEAN_VPATH_FILES =
|
|
|
07d5a6 |
LTLIBRARIES = $(noinst_LTLIBRARIES)
|
|
|
07d5a6 |
libsquid_http_la_LIBADD =
|
|
|
07d5a6 |
am_libsquid_http_la_OBJECTS = MethodType.lo StatusCode.lo \
|
|
|
07d5a6 |
- StatusLine.lo
|
|
|
07d5a6 |
+ StatusLine.lo ContentLengthInterpreter.lo
|
|
|
07d5a6 |
libsquid_http_la_OBJECTS = $(am_libsquid_http_la_OBJECTS)
|
|
|
07d5a6 |
AM_V_lt = $(am__v_lt_@AM_V@)
|
|
|
07d5a6 |
am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
|
|
|
07d5a6 |
@@ -694,6 +694,8 @@ COMPAT_LIB = $(top_builddir)/compat/libcompat-squid.la $(LIBPROFILER)
|
|
|
07d5a6 |
subst_perlshell = sed -e 's,[@]PERL[@],$(PERL),g' <$(srcdir)/$@.pl.in >$@ || ($(RM) -f $@ ; exit 1)
|
|
|
07d5a6 |
noinst_LTLIBRARIES = libsquid-http.la
|
|
|
07d5a6 |
libsquid_http_la_SOURCES = \
|
|
|
07d5a6 |
+ ContentLengthInterpreter.cc \
|
|
|
07d5a6 |
+ ContentLengthInterpreter.h \
|
|
|
07d5a6 |
MethodType.cc \
|
|
|
07d5a6 |
MethodType.h \
|
|
|
07d5a6 |
ProtocolVersion.h \
|
|
|
07d5a6 |
--
|
|
|
07d5a6 |
2.21.0
|
|
|
07d5a6 |
|