diff --git a/backport-0001-CVE-2021-28652.patch b/backport-0001-CVE-2021-28652.patch new file mode 100644 index 0000000000000000000000000000000000000000..6a52d068b1fb4cb98e18f2584703e3ac7dfd1e80 --- /dev/null +++ b/backport-0001-CVE-2021-28652.patch @@ -0,0 +1,195 @@ +From 417da4006cf5c97d44e74431b816fc58fec9e270 Mon Sep 17 00:00:00 2001 +From: Eduard Bagdasaryan +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 + #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 diff --git a/backport-0002-CVE-2021-28652.patch b/backport-0002-CVE-2021-28652.patch new file mode 100644 index 0000000000000000000000000000000000000000..7c61c69816953e00e6bade37f4a1e17f9e750802 --- /dev/null +++ b/backport-0002-CVE-2021-28652.patch @@ -0,0 +1,631 @@ +commit 0003e3518dc95e4b5ab46b5140af79b22253048e +Author: Amos Jeffries +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 + + 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 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 array; ++ int64_t intVal = 0; ++ while (tok.int64(intVal, 10, false)) { ++ Must(intVal >= std::numeric_limits::min()); ++ Must(intVal <= std::numeric_limits::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 ++ + #include ++#include + + 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(CacheManager::GetInstance()); ++ CPPUNIT_ASSERT(mgr != nullptr); ++ ++ std::vector 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 magicPrefixes = { ++ "/", ++ "/squid-internal-mgr/" ++ }; ++ ++ const std::vector validActions = { ++ "", ++ "menu" ++ }; ++ ++ const std::vector invalidActions = { ++ "INVALID" // any unregistered name ++ }; ++ ++ const std::vector 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 invalidParams = { ++ "?/", ++ "?foo", ++ "?/foo", ++ "?foo/", ++ "?foo=", ++ "?foo=&", ++ "?=foo", ++ "? foo=bar", ++ "? &", ++ "?& ", ++ "?=&", ++ "?&=", ++ "? &&&", ++ "?& &&", ++ "?&& &", ++ "?=&&&", ++ "?&=&&", ++ "?&&=&" ++ }; ++ ++ const std::vector 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 diff --git a/backport-CVE-2021-28651.patch b/backport-CVE-2021-28651.patch new file mode 100644 index 0000000000000000000000000000000000000000..36b620bf48df58dcf9d58983256ac81732e77f89 --- /dev/null +++ b/backport-CVE-2021-28651.patch @@ -0,0 +1,23 @@ +From 47a085ff06598b64817875769022b8707a0af7db Mon Sep 17 00:00:00 2001 +From: Amos Jeffries +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; + } + diff --git a/backport-CVE-2021-28662.patch b/backport-CVE-2021-28662.patch new file mode 100644 index 0000000000000000000000000000000000000000..ec5d80646494ae6f5431a683248c629b2b92572b --- /dev/null +++ b/backport-CVE-2021-28662.patch @@ -0,0 +1,22 @@ +From 051824924c709bd6162a378f746fb859454c674e Mon Sep 17 00:00:00 2001 +From: Alex Rousskov +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; + } diff --git a/backport-CVE-2021-31806-CVE-2021-31808.patch b/backport-CVE-2021-31806-CVE-2021-31808.patch new file mode 100644 index 0000000000000000000000000000000000000000..596c20cb29eff62d73670d53e5e7786b8fc39057 --- /dev/null +++ b/backport-CVE-2021-31806-CVE-2021-31808.patch @@ -0,0 +1,235 @@ +From 7024fb734a59409889e53df2257b3fc817809fb4 Mon Sep 17 00:00:00 2001 +From: Alex Rousskov +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 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 diff --git a/backport-CVE-2021-33620.patch b/backport-CVE-2021-33620.patch new file mode 100644 index 0000000000000000000000000000000000000000..9bfc82f9e11a173a8f4d55f21c7eed071e03da00 --- /dev/null +++ b/backport-CVE-2021-33620.patch @@ -0,0 +1,111 @@ +From 6c9c44d0e9cf7b72bb233360c5308aa063af3d69 Mon Sep 17 00:00:00 2001 +From: Alex Rousskov +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; diff --git a/squid.spec b/squid.spec index 4e6bec44a60fc6d154a7d11b698ecbb3868e5c25..b7f1a281d21d9eaa566b2eaede381b5db86b57bb 100644 --- a/squid.spec +++ b/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 - 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 - 4.9-7 - Type:cves - ID:NA