!39 fix CVE-2021-28651 CVE-2021-28652 CVE-2021-28662 CVE-2021-31806 CVE-2021-31808 CVE-2021-33620
From: @haochenstar Reviewed-by: @wangxp006 Signed-off-by: @wangxp006
This commit is contained in:
commit
5db7d5ba16
195
backport-0001-CVE-2021-28652.patch
Normal file
195
backport-0001-CVE-2021-28652.patch
Normal file
@ -0,0 +1,195 @@
|
||||
From 417da4006cf5c97d44e74431b816fc58fec9e270 Mon Sep 17 00:00:00 2001
|
||||
From: Eduard Bagdasaryan <eduard.bagdasaryan@measurement-factory.com>
|
||||
Date: Mon, 18 Mar 2019 17:48:21 +0000
|
||||
Subject: [PATCH] Fix incremental parsing of chunked quoted extensions (#310)
|
||||
|
||||
Before this change, incremental parsing of quoted chunked extensions
|
||||
was broken for two reasons:
|
||||
|
||||
* Http::One::Parser::skipLineTerminator() unexpectedly threw after
|
||||
partially received quoted chunk extension value.
|
||||
|
||||
* When Http::One::Tokenizer was unable to parse a quoted extension,
|
||||
it incorrectly restored the input buffer to the beginning of the
|
||||
extension value (instead of the extension itself), thus making
|
||||
further incremental parsing iterations impossible.
|
||||
|
||||
IMO, the reason for this problem was that Http::One::Tokenizer::qdText()
|
||||
could not distinguish two cases (returning false in both):
|
||||
|
||||
* the end of the quoted string not yet reached
|
||||
|
||||
* an input error, e.g., wrong/unexpected character
|
||||
|
||||
A possible approach could be to improve Http::One::Tokenizer, making it
|
||||
aware about "needs more data" state. However, to be acceptable,
|
||||
these improvements should be done in the base Parser::Tokenizer
|
||||
class instead. These changes seem to be non-trivial and could be
|
||||
done separately and later.
|
||||
|
||||
Another approach, used here, is to simplify the complex and error-prone
|
||||
chunked extensions parsing algorithm, fixing incremental parsing bugs
|
||||
and still parse incrementally in almost all cases. The performance
|
||||
regression could be expected only in relatively rare cases of partially
|
||||
received or malformed extensions.
|
||||
|
||||
Also:
|
||||
* fixed parsing of partial use-original-body extension values
|
||||
* do not treat an invalid use-original-body as an unknown extension
|
||||
* optimization: parse use-original-body extension only in ICAP context
|
||||
(i.e., where it is expected)
|
||||
* improvement: added a new API to TeChunkedParser to specify known
|
||||
chunked extensions list
|
||||
---
|
||||
src/Debug.h | 4 ++++
|
||||
src/parser/Makefile.am | 1 +
|
||||
src/parser/Tokenizer.cc | 40 ++++++++++++++++++++++++++++++++++++++++
|
||||
src/parser/Tokenizer.h | 13 +++++++++++++
|
||||
src/parser/forward.h | 22 ++++++++++++++++++++++
|
||||
5 files changed, 80 insertions(+)
|
||||
create mode 100644 src/parser/forward.h
|
||||
|
||||
diff --git a/src/Debug.h b/src/Debug.h
|
||||
index 7fb1ed5..8c24175 100644
|
||||
--- a/src/Debug.h
|
||||
+++ b/src/Debug.h
|
||||
@@ -99,6 +99,10 @@ public:
|
||||
|
||||
/// configures the active debugging context to write syslog ALERT
|
||||
static void ForceAlert();
|
||||
+
|
||||
+ /// prefixes each grouped debugs() line after the first one in the group
|
||||
+ static std::ostream& Extra(std::ostream &os) { return os << "\n "; }
|
||||
+
|
||||
private:
|
||||
static Context *Current; ///< deepest active context; nil outside debugs()
|
||||
};
|
||||
diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am
|
||||
index aef3235..c08d1d5 100644
|
||||
--- a/src/parser/Makefile.am
|
||||
+++ b/src/parser/Makefile.am
|
||||
@@ -13,6 +13,7 @@ noinst_LTLIBRARIES = libparser.la
|
||||
libparser_la_SOURCES = \
|
||||
BinaryTokenizer.h \
|
||||
BinaryTokenizer.cc \
|
||||
+ forward.h \
|
||||
Tokenizer.h \
|
||||
Tokenizer.cc
|
||||
|
||||
diff --git a/src/parser/Tokenizer.cc b/src/parser/Tokenizer.cc
|
||||
index 99f8eb3..0b44e40 100644
|
||||
--- a/src/parser/Tokenizer.cc
|
||||
+++ b/src/parser/Tokenizer.cc
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
#include "squid.h"
|
||||
#include "Debug.h"
|
||||
+#include "parser/forward.h"
|
||||
#include "parser/Tokenizer.h"
|
||||
+#include "sbuf/Stream.h"
|
||||
|
||||
#include <cerrno>
|
||||
#if HAVE_CTYPE_H
|
||||
@@ -96,6 +98,23 @@ Parser::Tokenizer::prefix(SBuf &returnedToken, const CharacterSet &tokenChars, c
|
||||
return true;
|
||||
}
|
||||
|
||||
+SBuf
|
||||
+Parser::Tokenizer::prefix(const char *description, const CharacterSet &tokenChars, const SBuf::size_type limit)
|
||||
+{
|
||||
+ if (atEnd())
|
||||
+ throw InsufficientInput();
|
||||
+
|
||||
+ SBuf result;
|
||||
+
|
||||
+ if (!prefix(result, tokenChars, limit))
|
||||
+ throw TexcHere(ToSBuf("cannot parse ", description));
|
||||
+
|
||||
+ if (atEnd())
|
||||
+ throw InsufficientInput();
|
||||
+
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
bool
|
||||
Parser::Tokenizer::suffix(SBuf &returnedToken, const CharacterSet &tokenChars, const SBuf::size_type limit)
|
||||
{
|
||||
@@ -283,3 +302,24 @@ Parser::Tokenizer::int64(int64_t & result, int base, bool allowSign, const SBuf:
|
||||
return success(s - range.rawContent());
|
||||
}
|
||||
|
||||
+int64_t
|
||||
+Parser::Tokenizer::udec64(const char *description, const SBuf::size_type limit)
|
||||
+{
|
||||
+ if (atEnd())
|
||||
+ throw InsufficientInput();
|
||||
+
|
||||
+ int64_t result = 0;
|
||||
+
|
||||
+ // Since we only support unsigned decimals, a parsing failure with a
|
||||
+ // non-empty input always implies invalid/malformed input (or a buggy
|
||||
+ // limit=0 caller). TODO: Support signed and non-decimal integers by
|
||||
+ // refactoring int64() to detect insufficient input.
|
||||
+ if (!int64(result, 10, false, limit))
|
||||
+ throw TexcHere(ToSBuf("cannot parse ", description));
|
||||
+
|
||||
+ if (atEnd())
|
||||
+ throw InsufficientInput(); // more digits may be coming
|
||||
+
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
diff --git a/src/parser/Tokenizer.h b/src/parser/Tokenizer.h
|
||||
index f04fd3e..6ae8162 100644
|
||||
--- a/src/parser/Tokenizer.h
|
||||
+++ b/src/parser/Tokenizer.h
|
||||
@@ -143,6 +143,19 @@ public:
|
||||
*/
|
||||
bool int64(int64_t &result, int base = 0, bool allowSign = true, SBuf::size_type limit = SBuf::npos);
|
||||
|
||||
+ /*
|
||||
+ * The methods below mimic their counterparts documented above, but they
|
||||
+ * throw on errors, including InsufficientInput. The field description
|
||||
+ * parameter is used for error reporting and debugging.
|
||||
+ */
|
||||
+
|
||||
+ /// prefix() wrapper but throws InsufficientInput if input contains
|
||||
+ /// nothing but the prefix (i.e. if the prefix is not "terminated")
|
||||
+ SBuf prefix(const char *description, const CharacterSet &tokenChars, SBuf::size_type limit = SBuf::npos);
|
||||
+
|
||||
+ /// int64() wrapper but limited to unsigned decimal integers (for now)
|
||||
+ int64_t udec64(const char *description, SBuf::size_type limit = SBuf::npos);
|
||||
+
|
||||
protected:
|
||||
SBuf consume(const SBuf::size_type n);
|
||||
SBuf::size_type success(const SBuf::size_type n);
|
||||
diff --git a/src/parser/forward.h b/src/parser/forward.h
|
||||
new file mode 100644
|
||||
index 0000000..5a95b7a
|
||||
--- /dev/null
|
||||
+++ b/src/parser/forward.h
|
||||
@@ -0,0 +1,22 @@
|
||||
+/*
|
||||
+ * Copyright (C) 1996-2019 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_PARSER_FORWARD_H
|
||||
+#define SQUID_PARSER_FORWARD_H
|
||||
+
|
||||
+namespace Parser {
|
||||
+class Tokenizer;
|
||||
+class BinaryTokenizer;
|
||||
+
|
||||
+// TODO: Move this declaration (to parser/Elements.h) if we need more like it.
|
||||
+/// thrown by modern "incremental" parsers when they need more data
|
||||
+class InsufficientInput {};
|
||||
+} // namespace Parser
|
||||
+
|
||||
+#endif /* SQUID_PARSER_FORWARD_H */
|
||||
+
|
||||
--
|
||||
2.23.0
|
||||
631
backport-0002-CVE-2021-28652.patch
Normal file
631
backport-0002-CVE-2021-28652.patch
Normal file
@ -0,0 +1,631 @@
|
||||
commit 0003e3518dc95e4b5ab46b5140af79b22253048e
|
||||
Author: Amos Jeffries <yadij@users.noreply.github.com>
|
||||
Date: 2021-04-30 05:15:44 +0000
|
||||
|
||||
Bug 5106: Broken cache manager URL parsing (#788)
|
||||
|
||||
Use already parsed request-target URL in cache manager and
|
||||
update CacheManager to Tokanizer based URL parse
|
||||
|
||||
Removing use of sscan() and regex string processing which have
|
||||
proven to be problematic on many levels. Most particularly with
|
||||
regards to tolerance of normally harmless garbage syntax in URLs
|
||||
received.
|
||||
|
||||
Support for generic URI schemes is added possibly resolving some
|
||||
issues reported with ftp:// URL and manager access via ftp_port
|
||||
sockets.
|
||||
|
||||
Truly generic support for /squid-internal-mgr/ path prefix is
|
||||
added, fixing some user confusion about its use on cache_object:
|
||||
scheme URLs.
|
||||
|
||||
TODO: support for single-name parameters and URL #fragments
|
||||
are left to future updates. As is refactoring the QueryParams
|
||||
data storage to avoid SBuf data copying.
|
||||
|
||||
diff --git a/src/CacheManager.h b/src/CacheManager.h
|
||||
index 78a69f799..74705c58a 100644
|
||||
--- a/src/CacheManager.h
|
||||
+++ b/src/CacheManager.h
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef SQUID_CACHEMANAGER_H
|
||||
#define SQUID_CACHEMANAGER_H
|
||||
|
||||
+#include "anyp/forward.h"
|
||||
#include "comm/forward.h"
|
||||
#include "mgr/Action.h"
|
||||
#include "mgr/ActionProfile.h"
|
||||
@@ -50,7 +51,7 @@ public:
|
||||
protected:
|
||||
CacheManager() {} ///< use Instance() instead
|
||||
|
||||
- Mgr::CommandPointer ParseUrl(const char *url);
|
||||
+ Mgr::CommandPointer ParseUrl(const AnyP::Uri &);
|
||||
void ParseHeaders(const HttpRequest * request, Mgr::ActionParams ¶ms);
|
||||
int CheckPassword(const Mgr::Command &cmd);
|
||||
char *PasswdGet(Mgr::ActionPasswordList *, const char *);
|
||||
diff --git a/src/cache_manager.cc b/src/cache_manager.cc
|
||||
index 9fe9bbb89..8055ece6b 100644
|
||||
--- a/src/cache_manager.cc
|
||||
+++ b/src/cache_manager.cc
|
||||
@@ -26,7 +26,9 @@
|
||||
#include "mgr/Forwarder.h"
|
||||
#include "mgr/FunAction.h"
|
||||
#include "mgr/QueryParams.h"
|
||||
+#include "parser/Tokenizer.h"
|
||||
#include "protos.h"
|
||||
+#include "sbuf/Stream.h"
|
||||
#include "sbuf/StringConvert.h"
|
||||
#include "SquidConfig.h"
|
||||
#include "SquidTime.h"
|
||||
@@ -147,82 +149,87 @@ CacheManager::createRequestedAction(const Mgr::ActionParams ¶ms)
|
||||
return cmd->profile->creator->create(cmd);
|
||||
}
|
||||
|
||||
+static const CharacterSet &
|
||||
+MgrFieldChars(const AnyP::ProtocolType &protocol)
|
||||
+{
|
||||
+ // Deprecated cache_object:// scheme used '@' to delimit passwords
|
||||
+ if (protocol == AnyP::PROTO_CACHE_OBJECT) {
|
||||
+ static const CharacterSet fieldChars = CharacterSet("cache-object-field", "@?#").complement();
|
||||
+ return fieldChars;
|
||||
+ }
|
||||
+
|
||||
+ static const CharacterSet actionChars = CharacterSet("mgr-field", "?#").complement();
|
||||
+ return actionChars;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
- \ingroup CacheManagerInternal
|
||||
* define whether the URL is a cache-manager URL and parse the action
|
||||
* requested by the user. Checks via CacheManager::ActionProtection() that the
|
||||
* item is accessible by the user.
|
||||
- \retval CacheManager::cachemgrStateData state object for the following handling
|
||||
- \retval NULL if the action can't be found or can't be accessed by the user
|
||||
+ *
|
||||
+ * Syntax:
|
||||
+ *
|
||||
+ * scheme "://" authority [ '/squid-internal-mgr' ] path-absolute [ '@' unreserved ] '?' query-string
|
||||
+ *
|
||||
+ * see RFC 3986 for definitions of scheme, authority, path-absolute, query-string
|
||||
+ *
|
||||
+ * \returns Mgr::Command object with action to perform and parameters it might use
|
||||
*/
|
||||
Mgr::Command::Pointer
|
||||
-CacheManager::ParseUrl(const char *url)
|
||||
+CacheManager::ParseUrl(const AnyP::Uri &uri)
|
||||
{
|
||||
- int t;
|
||||
- LOCAL_ARRAY(char, host, MAX_URL);
|
||||
- LOCAL_ARRAY(char, request, MAX_URL);
|
||||
- LOCAL_ARRAY(char, password, MAX_URL);
|
||||
- LOCAL_ARRAY(char, params, MAX_URL);
|
||||
- host[0] = 0;
|
||||
- request[0] = 0;
|
||||
- password[0] = 0;
|
||||
- params[0] = 0;
|
||||
- int pos = -1;
|
||||
- int len = strlen(url);
|
||||
- Must(len > 0);
|
||||
- t = sscanf(url, "cache_object://%[^/]/%[^@?]%n@%[^?]?%s", host, request, &pos, password, params);
|
||||
- if (t < 3) {
|
||||
- t = sscanf(url, "cache_object://%[^/]/%[^?]%n?%s", host, request, &pos, params);
|
||||
- }
|
||||
- if (t < 1) {
|
||||
- t = sscanf(url, "http://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
|
||||
- }
|
||||
- if (t < 1) {
|
||||
- t = sscanf(url, "https://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
|
||||
- }
|
||||
- if (t < 2) {
|
||||
- if (strncmp("cache_object://",url,15)==0)
|
||||
- xstrncpy(request, "menu", MAX_URL);
|
||||
- else
|
||||
- xstrncpy(request, "index", MAX_URL);
|
||||
- }
|
||||
+ Parser::Tokenizer tok(uri.path());
|
||||
|
||||
-#if _SQUID_OS2_
|
||||
- if (t == 2 && request[0] == '\0') {
|
||||
- /*
|
||||
- * emx's sscanf insists of returning 2 because it sets request
|
||||
- * to null
|
||||
- */
|
||||
- if (strncmp("cache_object://",url,15)==0)
|
||||
- xstrncpy(request, "menu", MAX_URL);
|
||||
- else
|
||||
- xstrncpy(request, "index", MAX_URL);
|
||||
- }
|
||||
-#endif
|
||||
+ static const SBuf internalMagicPrefix("/squid-internal-mgr/");
|
||||
+ if (!tok.skip(internalMagicPrefix) && !tok.skip('/'))
|
||||
+ throw TextException("invalid URL path", Here());
|
||||
|
||||
- debugs(16, 3, HERE << "MGR request: t=" << t << ", host='" << host << "', request='" << request << "', pos=" << pos <<
|
||||
- ", password='" << password << "', params='" << params << "'");
|
||||
+ Mgr::Command::Pointer cmd = new Mgr::Command();
|
||||
+ cmd->params.httpUri = SBufToString(uri.absolute());
|
||||
|
||||
- Mgr::ActionProfile::Pointer profile = findAction(request);
|
||||
- if (!profile) {
|
||||
- debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' not found");
|
||||
- return NULL;
|
||||
+ const auto &fieldChars = MgrFieldChars(uri.getScheme());
|
||||
+
|
||||
+ SBuf action;
|
||||
+ if (!tok.prefix(action, fieldChars)) {
|
||||
+ if (uri.getScheme() == AnyP::PROTO_CACHE_OBJECT) {
|
||||
+ static const SBuf menuReport("menu");
|
||||
+ action = menuReport;
|
||||
+ } else {
|
||||
+ static const SBuf indexReport("index");
|
||||
+ action = indexReport;
|
||||
+ }
|
||||
}
|
||||
+ cmd->params.actionName = SBufToString(action);
|
||||
+
|
||||
+ const auto profile = findAction(action.c_str());
|
||||
+ if (!profile)
|
||||
+ throw TextException(ToSBuf("action '", action, "' not found"), Here());
|
||||
|
||||
const char *prot = ActionProtection(profile);
|
||||
- if (!strcmp(prot, "disabled") || !strcmp(prot, "hidden")) {
|
||||
- debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' is " << prot);
|
||||
- return NULL;
|
||||
+ if (!strcmp(prot, "disabled") || !strcmp(prot, "hidden"))
|
||||
+ throw TextException(ToSBuf("action '", action, "' is ", prot), Here());
|
||||
+ cmd->profile = profile;
|
||||
+
|
||||
+ SBuf passwd;
|
||||
+ if (uri.getScheme() == AnyP::PROTO_CACHE_OBJECT && tok.skip('@')) {
|
||||
+ (void)tok.prefix(passwd, fieldChars);
|
||||
+ cmd->params.password = SBufToString(passwd);
|
||||
}
|
||||
|
||||
- Mgr::Command::Pointer cmd = new Mgr::Command;
|
||||
- if (!Mgr::QueryParams::Parse(params, cmd->params.queryParams))
|
||||
- return NULL;
|
||||
- cmd->profile = profile;
|
||||
- cmd->params.httpUri = url;
|
||||
- cmd->params.userName = String();
|
||||
- cmd->params.password = password;
|
||||
- cmd->params.actionName = request;
|
||||
+ // TODO: fix when AnyP::Uri::parse() separates path?query#fragment
|
||||
+ SBuf params;
|
||||
+ if (tok.skip('?')) {
|
||||
+ params = tok.remaining();
|
||||
+ Mgr::QueryParams::Parse(tok, cmd->params.queryParams);
|
||||
+ }
|
||||
+
|
||||
+ if (!tok.skip('#') && !tok.atEnd())
|
||||
+ throw TextException("invalid characters in URL", Here());
|
||||
+ // else ignore #fragment (if any)
|
||||
+
|
||||
+ debugs(16, 3, "MGR request: host=" << uri.host() << ", action=" << action <<
|
||||
+ ", password=" << passwd << ", params=" << params);
|
||||
+
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@@ -305,11 +312,15 @@ CacheManager::CheckPassword(const Mgr::Command &cmd)
|
||||
void
|
||||
CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request, StoreEntry * entry)
|
||||
{
|
||||
- debugs(16, 3, "CacheManager::Start: '" << entry->url() << "'" );
|
||||
+ debugs(16, 3, "request-url= '" << request->url << "', entry-url='" << entry->url() << "'");
|
||||
|
||||
- Mgr::Command::Pointer cmd = ParseUrl(entry->url());
|
||||
- if (!cmd) {
|
||||
- ErrorState *err = new ErrorState(ERR_INVALID_URL, Http::scNotFound, request);
|
||||
+ Mgr::Command::Pointer cmd;
|
||||
+ try {
|
||||
+ cmd = ParseUrl(request->url);
|
||||
+
|
||||
+ } catch (...) {
|
||||
+ debugs(16, 2, "request URL error: " << CurrentException);
|
||||
+ const auto err = new ErrorState(ERR_INVALID_URL, Http::scNotFound, request);
|
||||
err->url = xstrdup(entry->url());
|
||||
errorAppendEntry(entry, err);
|
||||
entry->expires = squid_curtime;
|
||||
@@ -473,4 +484,3 @@ CacheManager::GetInstance()
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
-
|
||||
diff --git a/src/mgr/QueryParams.cc b/src/mgr/QueryParams.cc
|
||||
index 831694245..a53dee1c7 100644
|
||||
--- a/src/mgr/QueryParams.cc
|
||||
+++ b/src/mgr/QueryParams.cc
|
||||
@@ -14,6 +14,10 @@
|
||||
#include "mgr/IntParam.h"
|
||||
#include "mgr/QueryParams.h"
|
||||
#include "mgr/StringParam.h"
|
||||
+#include "parser/Tokenizer.h"
|
||||
+#include "sbuf/StringConvert.h"
|
||||
+
|
||||
+#include <limits>
|
||||
|
||||
Mgr::QueryParam::Pointer
|
||||
Mgr::QueryParams::get(const String& name) const
|
||||
@@ -65,61 +69,76 @@ Mgr::QueryParams::find(const String& name) const
|
||||
return iter;
|
||||
}
|
||||
|
||||
-bool
|
||||
-Mgr::QueryParams::ParseParam(const String& paramStr, Param& param)
|
||||
+/**
|
||||
+ * Parses the value part of a "param=value" URL section.
|
||||
+ * Value can be a comma-separated list of integers or an opaque string.
|
||||
+ *
|
||||
+ * value = *pchar | ( 1*DIGIT *( ',' 1*DIGIT ) )
|
||||
+ *
|
||||
+ * \note opaque string may be a list with a non-integer (e.g., "1,2,3,z")
|
||||
+ */
|
||||
+Mgr::QueryParam::Pointer
|
||||
+ParseParamValue(const SBuf &rawValue)
|
||||
{
|
||||
- bool parsed = false;
|
||||
- regmatch_t pmatch[3];
|
||||
- regex_t intExpr;
|
||||
- regcomp(&intExpr, "^([a-z][a-z0-9_]*)=([0-9]+((,[0-9]+))*)$", REG_EXTENDED | REG_ICASE);
|
||||
- regex_t stringExpr;
|
||||
- regcomp(&stringExpr, "^([a-z][a-z0-9_]*)=([^&= ]+)$", REG_EXTENDED | REG_ICASE);
|
||||
- if (regexec(&intExpr, paramStr.termedBuf(), 3, pmatch, 0) == 0) {
|
||||
- param.first = paramStr.substr(pmatch[1].rm_so, pmatch[1].rm_eo);
|
||||
- std::vector<int> array;
|
||||
- int n = pmatch[2].rm_so;
|
||||
- for (int i = n; i < pmatch[2].rm_eo; ++i) {
|
||||
- if (paramStr[i] == ',') {
|
||||
- array.push_back(atoi(paramStr.substr(n, i).termedBuf()));
|
||||
- n = i + 1;
|
||||
- }
|
||||
- }
|
||||
- if (n < pmatch[2].rm_eo)
|
||||
- array.push_back(atoi(paramStr.substr(n, pmatch[2].rm_eo).termedBuf()));
|
||||
- param.second = new IntParam(array);
|
||||
- parsed = true;
|
||||
- } else if (regexec(&stringExpr, paramStr.termedBuf(), 3, pmatch, 0) == 0) {
|
||||
- param.first = paramStr.substr(pmatch[1].rm_so, pmatch[1].rm_eo);
|
||||
- param.second = new StringParam(paramStr.substr(pmatch[2].rm_so, pmatch[2].rm_eo));
|
||||
- parsed = true;
|
||||
+ static const CharacterSet comma("comma", ",");
|
||||
+
|
||||
+ Parser::Tokenizer tok(rawValue);
|
||||
+ std::vector<int> array;
|
||||
+ int64_t intVal = 0;
|
||||
+ while (tok.int64(intVal, 10, false)) {
|
||||
+ Must(intVal >= std::numeric_limits<int>::min());
|
||||
+ Must(intVal <= std::numeric_limits<int>::max());
|
||||
+ array.emplace_back(intVal);
|
||||
+ // integer list has comma between values.
|
||||
+ // Require at least one potential DIGIT after the skipped ','
|
||||
+ if (tok.remaining().length() > 1)
|
||||
+ (void)tok.skipOne(comma);
|
||||
}
|
||||
- regfree(&stringExpr);
|
||||
- regfree(&intExpr);
|
||||
- return parsed;
|
||||
+
|
||||
+ if (tok.atEnd())
|
||||
+ return new Mgr::IntParam(array);
|
||||
+ else
|
||||
+ return new Mgr::StringParam(SBufToString(rawValue));
|
||||
}
|
||||
|
||||
-bool
|
||||
-Mgr::QueryParams::Parse(const String& aParamsStr, QueryParams& aParams)
|
||||
+/**
|
||||
+ * Syntax:
|
||||
+ * query = [ param *( '&' param ) ]
|
||||
+ * param = name '=' value
|
||||
+ * name = [a-zA-Z0-9]+
|
||||
+ * value = *pchar | ( 1*DIGIT *( ',' 1*DIGIT ) )
|
||||
+ */
|
||||
+void
|
||||
+Mgr::QueryParams::Parse(Parser::Tokenizer &tok, QueryParams &aParams)
|
||||
{
|
||||
- if (aParamsStr.size() != 0) {
|
||||
- Param param;
|
||||
- size_t n = 0;
|
||||
- size_t len = aParamsStr.size();
|
||||
- for (size_t i = n; i < len; ++i) {
|
||||
- if (aParamsStr[i] == '&') {
|
||||
- if (!ParseParam(aParamsStr.substr(n, i), param))
|
||||
- return false;
|
||||
- aParams.params.push_back(param);
|
||||
- n = i + 1;
|
||||
- }
|
||||
- }
|
||||
- if (n < len) {
|
||||
- if (!ParseParam(aParamsStr.substr(n, len), param))
|
||||
- return false;
|
||||
- aParams.params.push_back(param);
|
||||
- }
|
||||
+ static const CharacterSet nameChars = CharacterSet("param-name", "_") + CharacterSet::ALPHA + CharacterSet::DIGIT;
|
||||
+ static const CharacterSet valueChars = CharacterSet("param-value", "&= #").complement();
|
||||
+ static const CharacterSet delimChars("param-delim", "&");
|
||||
+
|
||||
+ while (!tok.atEnd()) {
|
||||
+
|
||||
+ // TODO: remove '#' processing when AnyP::Uri splits 'query#fragment' properly
|
||||
+ // #fragment handled by caller. Do not throw.
|
||||
+ if (tok.remaining()[0] == '#')
|
||||
+ return;
|
||||
+
|
||||
+ if (tok.skipAll(delimChars))
|
||||
+ continue;
|
||||
+
|
||||
+ SBuf nameStr;
|
||||
+ if (!tok.prefix(nameStr, nameChars))
|
||||
+ throw TextException("invalid query parameter name", Here());
|
||||
+ if (!tok.skip('='))
|
||||
+ throw TextException("missing parameter value", Here());
|
||||
+
|
||||
+ SBuf valueStr;
|
||||
+ if (!tok.prefix(valueStr, valueChars))
|
||||
+ throw TextException("missing or malformed parameter value", Here());
|
||||
+
|
||||
+ const auto name = SBufToString(nameStr);
|
||||
+ const auto value = ParseParamValue(valueStr);
|
||||
+ aParams.params.emplace_back(name, value);
|
||||
}
|
||||
- return true;
|
||||
}
|
||||
|
||||
Mgr::QueryParam::Pointer
|
||||
@@ -138,4 +157,3 @@ Mgr::QueryParams::CreateParam(QueryParam::Type aType)
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
-
|
||||
diff --git a/src/mgr/QueryParams.h b/src/mgr/QueryParams.h
|
||||
index bb8f40308..450c20f86 100644
|
||||
--- a/src/mgr/QueryParams.h
|
||||
+++ b/src/mgr/QueryParams.h
|
||||
@@ -13,9 +13,11 @@
|
||||
|
||||
#include "ipc/forward.h"
|
||||
#include "mgr/QueryParam.h"
|
||||
+#include "parser/Tokenizer.h"
|
||||
#include "SquidString.h"
|
||||
-#include <vector>
|
||||
+
|
||||
#include <utility>
|
||||
+#include <vector>
|
||||
|
||||
namespace Mgr
|
||||
{
|
||||
@@ -32,15 +34,13 @@ public:
|
||||
void pack(Ipc::TypedMsgHdr& msg) const; ///< store params into msg
|
||||
void unpack(const Ipc::TypedMsgHdr& msg); ///< load params from msg
|
||||
/// parses the query string parameters
|
||||
- static bool Parse(const String& aParamsStr, QueryParams& aParams);
|
||||
+ static void Parse(Parser::Tokenizer &, QueryParams &);
|
||||
|
||||
private:
|
||||
/// find query parameter by name
|
||||
Params::const_iterator find(const String& name) const;
|
||||
/// creates a parameter of the specified type
|
||||
static QueryParam::Pointer CreateParam(QueryParam::Type aType);
|
||||
- /// parses string like "param=value"; returns true if success
|
||||
- static bool ParseParam(const String& paramStr, Param& param);
|
||||
|
||||
private:
|
||||
Params params;
|
||||
diff --git a/src/tests/stub_libmgr.cc b/src/tests/stub_libmgr.cc
|
||||
index f8be88a58..cd3ffc2de 100644
|
||||
--- a/src/tests/stub_libmgr.cc
|
||||
+++ b/src/tests/stub_libmgr.cc
|
||||
@@ -174,11 +174,10 @@ void Mgr::IoAction::dump(StoreEntry* entry) STUB
|
||||
Mgr::QueryParam::Pointer Mgr::QueryParams::get(const String& name) const STUB_RETVAL(Mgr::QueryParam::Pointer(NULL))
|
||||
void Mgr::QueryParams::pack(Ipc::TypedMsgHdr& msg) const STUB
|
||||
void Mgr::QueryParams::unpack(const Ipc::TypedMsgHdr& msg) STUB
|
||||
-bool Mgr::QueryParams::Parse(const String& aParamsStr, QueryParams& aParams) STUB_RETVAL(false)
|
||||
+void Mgr::QueryParams::Parse(Parser::Tokenizer &, QueryParams &) STUB
|
||||
//private:
|
||||
//Params::const_iterator Mgr::QueryParams::find(const String& name) const STUB_RETVAL(new Mgr::Params::const_iterator(*this))
|
||||
Mgr::QueryParam::Pointer Mgr::QueryParams::CreateParam(QueryParam::Type aType) STUB_RETVAL(Mgr::QueryParam::Pointer(NULL))
|
||||
-bool Mgr::QueryParams::ParseParam(const String& paramStr, Param& param) STUB_RETVAL(false)
|
||||
|
||||
#include "mgr/Registration.h"
|
||||
//void Mgr::RegisterAction(char const * action, char const * desc, OBJH * handler, int pw_req_flag, int atomic);
|
||||
diff --git a/src/tests/testCacheManager.cc b/src/tests/testCacheManager.cc
|
||||
index f02396176..7d6631aae 100644
|
||||
--- a/src/tests/testCacheManager.cc
|
||||
+++ b/src/tests/testCacheManager.cc
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include "squid.h"
|
||||
+#include "anyp/Uri.h"
|
||||
#include "CacheManager.h"
|
||||
#include "mgr/Action.h"
|
||||
#include "Store.h"
|
||||
@@ -17,11 +18,19 @@
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION( testCacheManager );
|
||||
|
||||
+/// Provides test code access to CacheManager internal symbols
|
||||
+class CacheManagerInternals : public CacheManager
|
||||
+{
|
||||
+public:
|
||||
+ void ParseUrl(const AnyP::Uri &u) { CacheManager::ParseUrl(u); }
|
||||
+};
|
||||
+
|
||||
/* init memory pools */
|
||||
|
||||
void testCacheManager::setUp()
|
||||
{
|
||||
Mem::Init();
|
||||
+ AnyP::UriScheme::Init();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -66,3 +75,146 @@ testCacheManager::testRegister()
|
||||
CPPUNIT_ASSERT_EQUAL(1,(int)sentry->flags);
|
||||
}
|
||||
|
||||
+void
|
||||
+testCacheManager::testParseUrl()
|
||||
+{
|
||||
+ auto *mgr = static_cast<CacheManagerInternals *>(CacheManager::GetInstance());
|
||||
+ CPPUNIT_ASSERT(mgr != nullptr);
|
||||
+
|
||||
+ std::vector<AnyP::ProtocolType> validSchemes = {
|
||||
+ AnyP::PROTO_CACHE_OBJECT,
|
||||
+ AnyP::PROTO_HTTP,
|
||||
+ AnyP::PROTO_HTTPS,
|
||||
+ AnyP::PROTO_FTP
|
||||
+ };
|
||||
+
|
||||
+ AnyP::Uri mgrUrl;
|
||||
+ mgrUrl.host("localhost");
|
||||
+ mgrUrl.port(3128);
|
||||
+
|
||||
+ const std::vector<const char *> magicPrefixes = {
|
||||
+ "/",
|
||||
+ "/squid-internal-mgr/"
|
||||
+ };
|
||||
+
|
||||
+ const std::vector<const char *> validActions = {
|
||||
+ "",
|
||||
+ "menu"
|
||||
+ };
|
||||
+
|
||||
+ const std::vector<const char *> invalidActions = {
|
||||
+ "INVALID" // any unregistered name
|
||||
+ };
|
||||
+
|
||||
+ const std::vector<const char *> validParams = {
|
||||
+ "",
|
||||
+ "?",
|
||||
+ "?&",
|
||||
+ "?&&&&&&&&&&&&",
|
||||
+ "?foo=bar",
|
||||
+ "?0123456789=bar",
|
||||
+ "?foo=bar&",
|
||||
+ "?foo=bar&&&&",
|
||||
+ "?&foo=bar",
|
||||
+ "?&&&&foo=bar",
|
||||
+ "?&foo=bar&",
|
||||
+ "?&&&&foo=bar&&&&",
|
||||
+ "?foo=?_weird?~`:[]stuff&bar=okay&&&&&&",
|
||||
+ "?intlist=1",
|
||||
+ "?intlist=1,2,3,4,5",
|
||||
+ "?string=1a",
|
||||
+ "?string=1,2,3,4,z",
|
||||
+ "?string=1,2,3,4,[0]",
|
||||
+ "?intlist=1,2,3,4,5&string=1,2,3,4,y"
|
||||
+ };
|
||||
+
|
||||
+ const std::vector<const char *> invalidParams = {
|
||||
+ "?/",
|
||||
+ "?foo",
|
||||
+ "?/foo",
|
||||
+ "?foo/",
|
||||
+ "?foo=",
|
||||
+ "?foo=&",
|
||||
+ "?=foo",
|
||||
+ "? foo=bar",
|
||||
+ "? &",
|
||||
+ "?& ",
|
||||
+ "?=&",
|
||||
+ "?&=",
|
||||
+ "? &&&",
|
||||
+ "?& &&",
|
||||
+ "?&& &",
|
||||
+ "?=&&&",
|
||||
+ "?&=&&",
|
||||
+ "?&&=&"
|
||||
+ };
|
||||
+
|
||||
+ const std::vector<const char *> validFragments = {
|
||||
+ "",
|
||||
+ "#",
|
||||
+ "##",
|
||||
+ "#?a=b",
|
||||
+ "#fragment"
|
||||
+ };
|
||||
+
|
||||
+ for (const auto &scheme : validSchemes) {
|
||||
+ mgrUrl.setScheme(scheme);
|
||||
+
|
||||
+ for (const auto *magic : magicPrefixes) {
|
||||
+
|
||||
+ // all schemes except cache_object require magic path prefix bytes
|
||||
+ if (scheme != AnyP::PROTO_CACHE_OBJECT && strlen(magic) <= 2)
|
||||
+ continue;
|
||||
+
|
||||
+ /* Check the parser accepts all the valid cases */
|
||||
+
|
||||
+ for (const auto *action : validActions) {
|
||||
+ for (const auto *param : validParams) {
|
||||
+ for (const auto *frag : validFragments) {
|
||||
+ try {
|
||||
+ SBuf bits;
|
||||
+ bits.append(magic);
|
||||
+ bits.append(action);
|
||||
+ bits.append(param);
|
||||
+ bits.append(frag);
|
||||
+ mgrUrl.path(bits);
|
||||
+
|
||||
+ (void)mgr->ParseUrl(mgrUrl);
|
||||
+ } catch (...) {
|
||||
+ std::cerr << std::endl
|
||||
+ << "FAIL: " << mgrUrl
|
||||
+ << Debug::Extra << "error: " << CurrentException << std::endl;
|
||||
+ CPPUNIT_FAIL("rejected a valid URL");
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Check that invalid parameters are rejected */
|
||||
+
|
||||
+ for (const auto *action : validActions) {
|
||||
+ for (const auto *param : invalidParams) {
|
||||
+ for (const auto *frag : validFragments) {
|
||||
+ try {
|
||||
+ SBuf bits;
|
||||
+ bits.append(magic);
|
||||
+ bits.append(action);
|
||||
+ bits.append(param);
|
||||
+ bits.append(frag);
|
||||
+ mgrUrl.path(bits);
|
||||
+
|
||||
+ (void)mgr->ParseUrl(mgrUrl);
|
||||
+
|
||||
+ std::cerr << std::endl
|
||||
+ << "FAIL: " << mgrUrl
|
||||
+ << Debug::Extra << "error: should be rejected due to '" << param << "'" << std::endl;
|
||||
+ } catch (const TextException &e) {
|
||||
+ continue; // success. caught bad input
|
||||
+ }
|
||||
+ CPPUNIT_FAIL("failed to reject an invalid URL");
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/tests/testCacheManager.h b/src/tests/testCacheManager.h
|
||||
index 6d32d69e5..fee15846a 100644
|
||||
--- a/src/tests/testCacheManager.h
|
||||
+++ b/src/tests/testCacheManager.h
|
||||
@@ -20,6 +20,7 @@ class testCacheManager : public CPPUNIT_NS::TestFixture
|
||||
CPPUNIT_TEST_SUITE( testCacheManager );
|
||||
CPPUNIT_TEST( testCreate );
|
||||
CPPUNIT_TEST( testRegister );
|
||||
+ CPPUNIT_TEST( testParseUrl );
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -28,6 +29,7 @@ public:
|
||||
protected:
|
||||
void testCreate();
|
||||
void testRegister();
|
||||
+ void testParseUrl();
|
||||
};
|
||||
|
||||
#endif
|
||||
23
backport-CVE-2021-28651.patch
Normal file
23
backport-CVE-2021-28651.patch
Normal file
@ -0,0 +1,23 @@
|
||||
From 47a085ff06598b64817875769022b8707a0af7db Mon Sep 17 00:00:00 2001
|
||||
From: Amos Jeffries <yadij@users.noreply.github.com>
|
||||
Date: Wed, 24 Feb 2021 00:53:21 +0000
|
||||
Subject: [PATCH] Bug 5104: Memory leak in RFC 2169 response parsing (#778)
|
||||
|
||||
A temporary parsing buffer was not being released when
|
||||
parsing completed.
|
||||
---
|
||||
src/urn.cc | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/src/urn.cc b/src/urn.cc
|
||||
index 69c29b75f4e..72ab801a906 100644
|
||||
--- a/src/urn.cc
|
||||
+++ b/src/urn.cc
|
||||
@@ -425,6 +425,7 @@ urnParseReply(const char *inbuf, const HttpRequestMethod& m)
|
||||
}
|
||||
|
||||
debugs(52, 3, "urnParseReply: Found " << i << " URLs");
|
||||
+ xfree(buf);
|
||||
return list;
|
||||
}
|
||||
|
||||
22
backport-CVE-2021-28662.patch
Normal file
22
backport-CVE-2021-28662.patch
Normal file
@ -0,0 +1,22 @@
|
||||
From 051824924c709bd6162a378f746fb859454c674e Mon Sep 17 00:00:00 2001
|
||||
From: Alex Rousskov <rousskov@measurement-factory.com>
|
||||
Date: Tue, 16 Mar 2021 11:45:11 -0400
|
||||
Subject: [PATCH] Merge pull request from GHSA-jjq6-mh2h-g39h
|
||||
|
||||
---
|
||||
src/http/RegisteredHeaders.cc | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/http/RegisteredHeaders.cc b/src/http/RegisteredHeaders.cc
|
||||
index a4f96db2b78..84f177af2d8 100644
|
||||
--- a/src/http/RegisteredHeaders.cc
|
||||
+++ b/src/http/RegisteredHeaders.cc
|
||||
@@ -37,7 +37,7 @@ HeaderTableRecord::HeaderTableRecord(const char *n, HdrType theId, HdrFieldType
|
||||
const HeaderTableRecord&
|
||||
HeaderLookupTable_t::lookup (const char *buf, const std::size_t len) const {
|
||||
const HeaderTableRecord *r = HttpHeaderHashTable::lookup(buf, len);
|
||||
- if (!r)
|
||||
+ if (!r || r->id == Http::HdrType::OTHER)
|
||||
return BadHdr;
|
||||
return *r;
|
||||
}
|
||||
235
backport-CVE-2021-31806-CVE-2021-31808.patch
Normal file
235
backport-CVE-2021-31806-CVE-2021-31808.patch
Normal file
@ -0,0 +1,235 @@
|
||||
From 7024fb734a59409889e53df2257b3fc817809fb4 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Rousskov <rousskov@measurement-factory.com>
|
||||
Date: Wed, 31 Mar 2021 02:44:42 +0000
|
||||
Subject: [PATCH] Handle more Range requests (#790)
|
||||
|
||||
Also removed some effectively unused code.
|
||||
---
|
||||
src/HttpHdrRange.cc | 17 -------------
|
||||
src/HttpHeaderRange.h | 5 ++--
|
||||
src/client_side.cc | 4 ++--
|
||||
src/client_side_request.cc | 27 ++++++++++++++++++---
|
||||
src/client_side_request.h | 7 +++++-
|
||||
src/http/Stream.cc | 49 ++++++--------------------------------
|
||||
6 files changed, 41 insertions(+), 68 deletions(-)
|
||||
|
||||
diff --git a/src/HttpHdrRange.cc b/src/HttpHdrRange.cc
|
||||
index e7179dc..5849144 100644
|
||||
--- a/src/HttpHdrRange.cc
|
||||
+++ b/src/HttpHdrRange.cc
|
||||
@@ -526,23 +526,6 @@ HttpHdrRange::offsetLimitExceeded(const int64_t limit) const
|
||||
return true;
|
||||
}
|
||||
|
||||
-bool
|
||||
-HttpHdrRange::contains(const HttpHdrRangeSpec& r) const
|
||||
-{
|
||||
- assert(r.length >= 0);
|
||||
- HttpHdrRangeSpec::HttpRange rrange(r.offset, r.offset + r.length);
|
||||
-
|
||||
- for (const_iterator i = begin(); i != end(); ++i) {
|
||||
- HttpHdrRangeSpec::HttpRange irange((*i)->offset, (*i)->offset + (*i)->length);
|
||||
- HttpHdrRangeSpec::HttpRange intersection = rrange.intersection(irange);
|
||||
-
|
||||
- if (intersection.start == irange.start && intersection.size() == irange.size())
|
||||
- return true;
|
||||
- }
|
||||
-
|
||||
- return false;
|
||||
-}
|
||||
-
|
||||
const HttpHdrRangeSpec *
|
||||
HttpHdrRangeIter::currentSpec() const
|
||||
{
|
||||
diff --git a/src/HttpHeaderRange.h b/src/HttpHeaderRange.h
|
||||
index 2103b64..352e749 100644
|
||||
--- a/src/HttpHeaderRange.h
|
||||
+++ b/src/HttpHeaderRange.h
|
||||
@@ -78,7 +78,6 @@ public:
|
||||
int64_t firstOffset() const;
|
||||
int64_t lowestOffset(int64_t) const;
|
||||
bool offsetLimitExceeded(const int64_t limit) const;
|
||||
- bool contains(const HttpHdrRangeSpec& r) const;
|
||||
std::vector<HttpHdrRangeSpec *> specs;
|
||||
|
||||
private:
|
||||
@@ -100,9 +99,9 @@ public:
|
||||
void updateSpec();
|
||||
int64_t debt() const;
|
||||
void debt(int64_t);
|
||||
- int64_t debt_size; /* bytes left to send from the current spec */
|
||||
+ int64_t debt_size = 0; /* bytes left to send from the current spec */
|
||||
String boundary; /* boundary for multipart responses */
|
||||
- bool valid;
|
||||
+ bool valid = false;
|
||||
};
|
||||
|
||||
#endif /* SQUID_HTTPHEADERRANGE_H */
|
||||
diff --git a/src/client_side.cc b/src/client_side.cc
|
||||
index 120c39c..9516166 100644
|
||||
--- a/src/client_side.cc
|
||||
+++ b/src/client_side.cc
|
||||
@@ -724,8 +724,8 @@ clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String
|
||||
* warning: assumes that HTTP headers for individual ranges at the
|
||||
* time of the actuall assembly will be exactly the same as
|
||||
* the headers when clientMRangeCLen() is called */
|
||||
-int
|
||||
-ClientHttpRequest::mRangeCLen()
|
||||
+int64_t
|
||||
+ClientHttpRequest::mRangeCLen() const
|
||||
{
|
||||
int64_t clen = 0;
|
||||
MemBuf mb;
|
||||
diff --git a/src/client_side_request.cc b/src/client_side_request.cc
|
||||
index a37f8d4..3c83e9e 100644
|
||||
--- a/src/client_side_request.cc
|
||||
+++ b/src/client_side_request.cc
|
||||
@@ -1094,9 +1094,6 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
|
||||
* iter up at this point.
|
||||
*/
|
||||
node->readBuffer.offset = request->range->lowestOffset(0);
|
||||
- http->range_iter.pos = request->range->begin();
|
||||
- http->range_iter.end = request->range->end();
|
||||
- http->range_iter.valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1954,6 +1951,30 @@ ClientHttpRequest::setErrorUri(const char *aUri)
|
||||
#include "client_side_request.cci"
|
||||
#endif
|
||||
|
||||
+// XXX: This should not be a _request_ method. Move range_iter elsewhere.
|
||||
+int64_t
|
||||
+ClientHttpRequest::prepPartialResponseGeneration()
|
||||
+{
|
||||
+ assert(request);
|
||||
+ assert(request->range);
|
||||
+
|
||||
+ range_iter.pos = request->range->begin();
|
||||
+ range_iter.end = request->range->end();
|
||||
+ range_iter.debt_size = 0;
|
||||
+ const auto multipart = request->range->specs.size() > 1;
|
||||
+ if (multipart)
|
||||
+ range_iter.boundary = rangeBoundaryStr();
|
||||
+ range_iter.valid = true; // TODO: Remove.
|
||||
+ range_iter.updateSpec(); // TODO: Refactor to initialize rather than update.
|
||||
+
|
||||
+ assert(range_iter.pos != range_iter.end);
|
||||
+ const auto &firstRange = *range_iter.pos;
|
||||
+ assert(firstRange);
|
||||
+ out.offset = firstRange->offset;
|
||||
+
|
||||
+ return multipart ? mRangeCLen() : firstRange->length;
|
||||
+}
|
||||
+
|
||||
#if USE_ADAPTATION
|
||||
/// Initiate an asynchronous adaptation transaction which will call us back.
|
||||
void
|
||||
diff --git a/src/client_side_request.h b/src/client_side_request.h
|
||||
index 704802b..7ab3262 100644
|
||||
--- a/src/client_side_request.h
|
||||
+++ b/src/client_side_request.h
|
||||
@@ -131,7 +131,7 @@ public:
|
||||
|
||||
dlink_node active;
|
||||
dlink_list client_stream;
|
||||
- int mRangeCLen();
|
||||
+ int64_t mRangeCLen() const;
|
||||
|
||||
ClientRequestContext *calloutContext;
|
||||
void doCallouts();
|
||||
@@ -148,6 +148,11 @@ public:
|
||||
/// neither the current request nor the parsed request URI are known
|
||||
void setErrorUri(const char *errorUri);
|
||||
|
||||
+ /// Prepares to satisfy a Range request with a generated HTTP 206 response.
|
||||
+ /// Initializes range_iter state to allow raw range_iter access.
|
||||
+ /// \returns Content-Length value for the future response; never negative
|
||||
+ int64_t prepPartialResponseGeneration();
|
||||
+
|
||||
/// Build an error reply. For use with the callouts.
|
||||
void calloutsError(const err_type error, const int errDetail);
|
||||
|
||||
diff --git a/src/http/Stream.cc b/src/http/Stream.cc
|
||||
index 1370862..ff44496 100644
|
||||
--- a/src/http/Stream.cc
|
||||
+++ b/src/http/Stream.cc
|
||||
@@ -444,59 +444,27 @@ Http::Stream::buildRangeHeader(HttpReply *rep)
|
||||
} else {
|
||||
/* XXX: TODO: Review, this unconditional set may be wrong. */
|
||||
rep->sline.set(rep->sline.version, Http::scPartialContent);
|
||||
- // web server responded with a valid, but unexpected range.
|
||||
- // will (try-to) forward as-is.
|
||||
- //TODO: we should cope with multirange request/responses
|
||||
- // TODO: review, since rep->content_range is always nil here.
|
||||
- bool replyMatchRequest = contentRange != nullptr ?
|
||||
- request->range->contains(contentRange->spec) :
|
||||
- true;
|
||||
+
|
||||
+ // before range_iter accesses
|
||||
+ const auto actual_clen = http->prepPartialResponseGeneration();
|
||||
+
|
||||
const int spec_count = http->request->range->specs.size();
|
||||
- int64_t actual_clen = -1;
|
||||
|
||||
debugs(33, 3, "range spec count: " << spec_count <<
|
||||
" virgin clen: " << rep->content_length);
|
||||
assert(spec_count > 0);
|
||||
/* append appropriate header(s) */
|
||||
if (spec_count == 1) {
|
||||
- if (!replyMatchRequest) {
|
||||
- hdr->putContRange(contentRange);
|
||||
- actual_clen = rep->content_length;
|
||||
- //http->range_iter.pos = rep->content_range->spec.begin();
|
||||
- (*http->range_iter.pos)->offset = contentRange->spec.offset;
|
||||
- (*http->range_iter.pos)->length = contentRange->spec.length;
|
||||
-
|
||||
- } else {
|
||||
- HttpHdrRange::iterator pos = http->request->range->begin();
|
||||
- assert(*pos);
|
||||
- /* append Content-Range */
|
||||
-
|
||||
- if (!contentRange) {
|
||||
- /* No content range, so this was a full object we are
|
||||
- * sending parts of.
|
||||
- */
|
||||
- httpHeaderAddContRange(hdr, **pos, rep->content_length);
|
||||
- }
|
||||
-
|
||||
- /* set new Content-Length to the actual number of bytes
|
||||
- * transmitted in the message-body */
|
||||
- actual_clen = (*pos)->length;
|
||||
- }
|
||||
+ const auto singleSpec = *http->request->range->begin();
|
||||
+ assert(singleSpec);
|
||||
+ httpHeaderAddContRange(hdr, *singleSpec, rep->content_length);
|
||||
} else {
|
||||
/* multipart! */
|
||||
- /* generate boundary string */
|
||||
- http->range_iter.boundary = http->rangeBoundaryStr();
|
||||
/* delete old Content-Type, add ours */
|
||||
hdr->delById(Http::HdrType::CONTENT_TYPE);
|
||||
httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE,
|
||||
"multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"",
|
||||
SQUIDSTRINGPRINT(http->range_iter.boundary));
|
||||
- /* Content-Length is not required in multipart responses
|
||||
- * but it is always nice to have one */
|
||||
- actual_clen = http->mRangeCLen();
|
||||
-
|
||||
- /* http->out needs to start where we want data at */
|
||||
- http->out.offset = http->range_iter.currentSpec()->offset;
|
||||
}
|
||||
|
||||
/* replace Content-Length header */
|
||||
@@ -504,9 +472,6 @@ Http::Stream::buildRangeHeader(HttpReply *rep)
|
||||
hdr->delById(Http::HdrType::CONTENT_LENGTH);
|
||||
hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen);
|
||||
debugs(33, 3, "actual content length: " << actual_clen);
|
||||
-
|
||||
- /* And start the range iter off */
|
||||
- http->range_iter.updateSpec();
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.23.0
|
||||
111
backport-CVE-2021-33620.patch
Normal file
111
backport-CVE-2021-33620.patch
Normal file
@ -0,0 +1,111 @@
|
||||
From 6c9c44d0e9cf7b72bb233360c5308aa063af3d69 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Rousskov <rousskov@measurement-factory.com>
|
||||
Date: Fri, 2 Apr 2021 07:46:20 +0000
|
||||
Subject: [PATCH] Handle more partial responses (#791)
|
||||
|
||||
---
|
||||
src/HttpHdrContRange.cc | 14 ++++++++++++--
|
||||
src/HttpHeaderRange.h | 7 +++++--
|
||||
src/clients/Client.cc | 7 +++++--
|
||||
src/http/Stream.cc | 9 +++++++--
|
||||
4 files changed, 29 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/src/HttpHdrContRange.cc b/src/HttpHdrContRange.cc
|
||||
index 8270e0f11aa..79a507fb84c 100644
|
||||
--- a/src/HttpHdrContRange.cc
|
||||
+++ b/src/HttpHdrContRange.cc
|
||||
@@ -161,9 +161,13 @@ httpHdrContRangeParseInit(HttpHdrContRange * range, const char *str)
|
||||
|
||||
++p;
|
||||
|
||||
- if (*p == '*')
|
||||
+ if (*p == '*') {
|
||||
+ if (!known_spec(range->spec.offset)) {
|
||||
+ debugs(68, 2, "invalid (*/*) content-range-spec near: '" << str << "'");
|
||||
+ return 0;
|
||||
+ }
|
||||
range->elength = range_spec_unknown;
|
||||
- else if (!httpHeaderParseOffset(p, &range->elength))
|
||||
+ } else if (!httpHeaderParseOffset(p, &range->elength))
|
||||
return 0;
|
||||
else if (range->elength <= 0) {
|
||||
/* Additional paranoidal check for BUG2155 - entity-length MUST be > 0 */
|
||||
@@ -174,6 +178,12 @@ httpHdrContRangeParseInit(HttpHdrContRange * range, const char *str)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ // reject unsatisfied-range and such; we only use well-defined ranges today
|
||||
+ if (!known_spec(range->spec.offset) || !known_spec(range->spec.length)) {
|
||||
+ debugs(68, 2, "unwanted content-range-spec near: '" << str << "'");
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
debugs(68, 8, "parsed content-range field: " <<
|
||||
(long int) range->spec.offset << "-" <<
|
||||
(long int) range->spec.offset + range->spec.length - 1 << " / " <<
|
||||
diff --git a/src/HttpHeaderRange.h b/src/HttpHeaderRange.h
|
||||
index 6d93e72b2b8..bf54c8562ba 100644
|
||||
--- a/src/HttpHeaderRange.h
|
||||
+++ b/src/HttpHeaderRange.h
|
||||
@@ -18,8 +18,11 @@
|
||||
class HttpReply;
|
||||
class Packable;
|
||||
|
||||
-/* http byte-range-spec */
|
||||
-
|
||||
+// TODO: Refactor to disambiguate and provide message-specific APIs.
|
||||
+/// either byte-range-spec (in a request Range header)
|
||||
+/// or suffix-byte-range-spec (in a request Range header)
|
||||
+/// or byte-range part of byte-range-resp (in a response Content-Range header)
|
||||
+/// or "*" part of unsatisfied-range (in a response Content-Range header)
|
||||
class HttpHdrRangeSpec
|
||||
{
|
||||
MEMPROXY_CLASS(HttpHdrRangeSpec);
|
||||
diff --git a/src/clients/Client.cc b/src/clients/Client.cc
|
||||
index dfb13d053c3..2a2f0e8f878 100644
|
||||
--- a/src/clients/Client.cc
|
||||
+++ b/src/clients/Client.cc
|
||||
@@ -520,8 +520,11 @@ Client::haveParsedReplyHeaders()
|
||||
maybePurgeOthers();
|
||||
|
||||
// adaptation may overwrite old offset computed using the virgin response
|
||||
- const bool partial = theFinalReply->contentRange();
|
||||
- currentOffset = partial ? theFinalReply->contentRange()->spec.offset : 0;
|
||||
+ currentOffset = 0;
|
||||
+ if (const auto cr = theFinalReply->contentRange()) {
|
||||
+ if (cr->spec.offset != HttpHdrRangeSpec::UnknownPosition)
|
||||
+ currentOffset = cr->spec.offset;
|
||||
+ }
|
||||
}
|
||||
|
||||
/// whether to prevent caching of an otherwise cachable response
|
||||
diff --git a/src/http/Stream.cc b/src/http/Stream.cc
|
||||
index 9e346b9d99d..d685a22306e 100644
|
||||
--- a/src/http/Stream.cc
|
||||
+++ b/src/http/Stream.cc
|
||||
@@ -171,12 +171,13 @@ Http::Stream::getNextRangeOffset() const
|
||||
return start;
|
||||
}
|
||||
|
||||
- } else if (reply && reply->contentRange()) {
|
||||
+ } else if (const auto cr = reply ? reply->contentRange() : nullptr) {
|
||||
/* request does not have ranges, but reply does */
|
||||
/** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
|
||||
* becomes HttpHdrRange rather than HttpHdrRangeSpec.
|
||||
*/
|
||||
- return http->out.offset + reply->contentRange()->spec.offset;
|
||||
+ if (cr->spec.offset != HttpHdrRangeSpec::UnknownPosition)
|
||||
+ return http->out.offset + cr->spec.offset;
|
||||
}
|
||||
|
||||
return http->out.offset;
|
||||
@@ -240,6 +241,10 @@ Http::Stream::socketState()
|
||||
|
||||
// did we get at least what we expected, based on range specs?
|
||||
|
||||
+ // this Content-Range does not tell us how many bytes to expect
|
||||
+ if (bytesExpected == HttpHdrRangeSpec::UnknownPosition)
|
||||
+ return STREAM_NONE;
|
||||
+
|
||||
if (bytesSent == bytesExpected) // got everything
|
||||
return STREAM_COMPLETE;
|
||||
14
squid.spec
14
squid.spec
@ -2,7 +2,7 @@
|
||||
|
||||
Name: squid
|
||||
Version: 4.9
|
||||
Release: 7
|
||||
Release: 8
|
||||
Summary: The Squid proxy caching server
|
||||
Epoch: 7
|
||||
License: GPLv2+ and (LGPLv2+ and MIT and BSD and Public Domain)
|
||||
@ -34,6 +34,12 @@ Patch13:CVE-2020-15810.patch
|
||||
Patch14:CVE-2020-15811.patch
|
||||
Patch15:CVE-2020-24606.patch
|
||||
Patch16:backport-CVE-2020-25097.patch
|
||||
Patch17:backport-CVE-2021-28651.patch
|
||||
Patch18:backport-0001-CVE-2021-28652.patch
|
||||
Patch19:backport-0002-CVE-2021-28652.patch
|
||||
Patch20:backport-CVE-2021-28662.patch
|
||||
Patch21:backport-CVE-2021-31806-CVE-2021-31808.patch
|
||||
Patch22:backport-CVE-2021-33620.patch
|
||||
|
||||
Buildroot: %{_tmppath}/squid-4.9-1-root-%(%{__id_u} -n)
|
||||
Requires: bash >= 2.0
|
||||
@ -212,6 +218,12 @@ fi
|
||||
chgrp squid /var/cache/samba/winbindd_privileged >/dev/null 2>&1 || :
|
||||
|
||||
%changelog
|
||||
* Wed Jun 16 2021 xihaochen<xihaochen@huawei.com> - 4.9-8
|
||||
- Type:cves
|
||||
- ID:CVE-2021-28651 CVE-2021-28652 CVE-2021-28662 CVE-2021-31806 CVE-2021-31808 CVE-2021-33620
|
||||
- SUG:NA
|
||||
- DESC:fix CVE-2021-28651 CVE-2021-28652 CVE-2021-28662 CVE-2021-31806 CVE-2021-31808 CVE-2021-33620
|
||||
|
||||
* Wed Mar 31 2021 gaihuiying <gaihuiying1@huawei.com> - 4.9-7
|
||||
- Type:cves
|
||||
- ID:NA
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user