diff -Nru squid3-3.5.27/debian/changelog squid3-3.5.27/debian/changelog --- squid3-3.5.27/debian/changelog 2018-02-27 11:09:21.000000000 +0000 +++ squid3-3.5.27/debian/changelog 2020-08-25 17:12:13.000000000 +0000 @@ -1,3 +1,124 @@ +squid3 (3.5.27-1ubuntu1.8) bionic-security; urgency=medium + + * SECURITY REGRESSION: regression when parsing icap and ecap protocols + (LP: #1890265) + - debian/patches/CVE-2019-12523-bug965012.patch + * Thanks to Markus Koschany for the regression fix! + + -- Marc Deslauriers Tue, 25 Aug 2020 13:12:13 -0400 + +squid3 (3.5.27-1ubuntu1.7) bionic-security; urgency=medium + + * SECURITY UPDATE: Multiple Issues in HTTP Request processing + - debian/patches/CVE-2019-12520.patch: properly handle userinfo in + src/url.cc. + - CVE-2019-12520 + - CVE-2019-12524 + * SECURITY UPDATE: Multiple issues in URI processing + - debian/patches/CVE-2019-12526.patch: replace patch with the one from + Debian to get backported functions. + - debian/patches/CVE-2019-12523.patch: update URI parser to use SBuf + parsing APIs. + - CVE-2019-12523 + - CVE-2019-18676 + * Thanks to Markus Koschany for the backports this update is based on. + + -- Marc Deslauriers Tue, 28 Jul 2020 12:38:51 -0400 + +squid3 (3.5.27-1ubuntu1.6) bionic-security; urgency=medium + + * SECURITY UPDATE: multiple ESI issues + - debian/patches/CVE-2019-12519_12521.patch: convert parse exceptions + into 500 status response in src/esi/Context.h, src/esi/Esi.cc, + src/esi/Esi.h, src/esi/Expression.cc. + - CVE-2019-12519 + - CVE-2019-12521 + * SECURITY UPDATE: hostname parameter mishandling in cachemgr.cgi + - debian/patches/CVE-2019-18860.patch: add validation for hostname + parameter in src/base/CharacterSet.cc, tools/Makefile.am, + tools/cachemgr.cc. + - CVE-2019-18860 + * SECURITY UPDATE: Digest Authentication nonce replay issue + - debian/patches/CVE-2020-11945.patch: fix auth digest refcount integer + overflow in src/auth/digest/Config.cc. + - CVE-2020-11945 + + -- Marc Deslauriers Thu, 07 May 2020 10:03:32 -0400 + +squid3 (3.5.27-1ubuntu1.5) bionic-security; urgency=medium + + * SECURITY UPDATE: info disclosure via FTP server + - debian/patches/CVE-2019-12528.patch: fix FTP buffers handling in + src/clients/FtpGateway.cc. + - CVE-2019-12528 + * SECURITY UPDATE: incorrect input validation and buffer management + - debian/patches/CVE-2020-84xx-1.patch: ignore malformed Host header in + intercept and reverse proxy mode in src/client_side.cc. + - debian/patches/CVE-2020-84xx-2.patch: fix request URL generation in + reverse proxy configurations in src/client_side.cc. + - debian/patches/CVE-2020-84xx-3.patch: fix security patch in + src/client_side.cc. + - CVE-2020-8449 + - CVE-2020-8450 + * SECURITY UPDATE: DoS in NTLM authentication + - debian/patches/CVE-2020-8517.patch: improved username handling in + helpers/external_acl/LM_group/ext_lm_group_acl.cc. + - CVE-2020-8517 + + -- Marc Deslauriers Wed, 19 Feb 2020 12:50:27 -0500 + +squid3 (3.5.27-1ubuntu1.4) bionic-security; urgency=medium + + * SECURITY UPDATE: Heap Overflow issue in URN processing + - debian/patches/CVE-2019-12526.patch: fix URN response handling in + src/urn.cc. + - CVE-2019-12526 + * SECURITY UPDATE: CSRF issue in HTTP Request processing + - debian/patches/CVE-2019-18677.patch: prevent truncation for large + origin-relative domains in src/URL.h, src/internal.cc, src/url.cc. + - CVE-2019-18677 + * SECURITY UPDATE: HTTP Request Splitting in HTTP message processing + - debian/patches/CVE-2019-18678.patch: server MUST reject messages with + BWS after field-name in src/HttpHeader.cc, src/HttpHeader.h. + - CVE-2019-18678 + - CVE-2019-18679 + + -- Marc Deslauriers Tue, 19 Nov 2019 14:59:43 -0500 + +squid3 (3.5.27-1ubuntu1.3) bionic-security; urgency=medium + + * SECURITY UPDATE: incorrect digest auth parameter parsing + - debian/patches/CVE-2019-12525.patch: check length in + src/auth/digest/Config.cc. + - CVE-2019-12525 + * SECURITY UPDATE: basic auth uudecode length issue + - debian/patches/CVE-2019-12529.patch: replace uudecode with libnettle + base64 decoder in lib/Makefile.*, src/auth/basic/Config.cc, + include/uudecode.h, lib/uudecode.c. + - CVE-2019-12529 + + -- Marc Deslauriers Tue, 16 Jul 2019 11:49:31 -0400 + +squid3 (3.5.27-1ubuntu1.2) bionic-security; urgency=medium + + * SECURITY UPDATE: DoS via SNMP memory leak + - debian/patches/CVE-2018-19132.patch: fix leak in src/snmp_core.cc. + - CVE-2018-19132 + * SECURITY UPDATE: XSS issues in cachemgr.cgi + - debian/patches/CVE-2019-13345.patch: properly escape values in + tools/cachemgr.cc. + - CVE-2019-13345 + + -- Marc Deslauriers Thu, 11 Jul 2019 12:59:25 -0400 + +squid3 (3.5.27-1ubuntu1.1) bionic; urgency=medium + + [ Simon Deziel ] + * d/usr.sbin.squid: Update apparmor profile to grant read access to squid + binary (LP: #1792728) + + -- Christian Ehrhardt Fri, 28 Sep 2018 09:09:50 +0200 + squid3 (3.5.27-1ubuntu1) bionic; urgency=medium * Merge with Debian unstable (LP: #1751286). Remaining changes: diff -Nru squid3-3.5.27/debian/patches/CVE-2018-19132.patch squid3-3.5.27/debian/patches/CVE-2018-19132.patch --- squid3-3.5.27/debian/patches/CVE-2018-19132.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2018-19132.patch 2019-07-11 16:59:15.000000000 +0000 @@ -0,0 +1,22 @@ +commit bc9786119f058a76ddf0625424bc33d36460b9a2 (refs/remotes/origin/v3.5) +Author: flozilla +Date: 2018-10-24 14:12:01 +0200 + + Fix memory leak when parsing SNMP packet (#313) + + SNMP queries denied by snmp_access rules and queries with certain + unsupported SNMPv2 commands were leaking a few hundred bytes each. Such + queries trigger "SNMP agent query DENIED from..." WARNINGs in cache.log. + +diff --git a/src/snmp_core.cc b/src/snmp_core.cc +index c4d21c1..16c2993 100644 +--- a/src/snmp_core.cc ++++ b/src/snmp_core.cc +@@ -409,6 +409,7 @@ snmpDecodePacket(SnmpRequest * rq) + snmpConstructReponse(rq); + } else { + debugs(49, DBG_IMPORTANT, "WARNING: SNMP agent query DENIED from : " << rq->from); ++ snmp_free_pdu(PDU); + } + xfree(Community); + diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12519_12521.patch squid3-3.5.27/debian/patches/CVE-2019-12519_12521.patch --- squid3-3.5.27/debian/patches/CVE-2019-12519_12521.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12519_12521.patch 2020-05-07 14:03:32.000000000 +0000 @@ -0,0 +1,290 @@ +Backport of: + +commit fdd4123629320aa1ee4c3481bb392437c90d188d +Author: Amos Jeffries +Date: 2019-05-20 11:23:13 +0000 + + ESI: convert parse exceptions into 500 status response (#411) + + Produce a valid HTTP 500 status reply and continue operations when + ESI parser throws an exception. This will prevent incomplete ESI + responses reaching clients on server errors. Such responses might + have been cacheable and thus corrupted, albeit corrupted consistently + and at source by the reverse-proxy delivering them. + + ESI: throw on large stack recursions (#408) + + This reduces the impact on concurrent clients to only those + accessing the malformed resource. + + Depending on what type of recursion is being performed the + resource may appear to the client with missing segments, or + not at all. + +--- a/src/esi/Context.h ++++ b/src/esi/Context.h +@@ -12,6 +12,7 @@ + #include "clientStream.h" + #include "err_type.h" + #include "esi/Element.h" ++#include "esi/Esi.h" + #include "esi/Parser.h" + #include "http/StatusCode.h" + #include "HttpReply.h" +@@ -112,7 +113,7 @@ public: + { + + public: +- ESIElement::Pointer stack[10]; /* a stack of esi elements that are open */ ++ ESIElement::Pointer stack[ESI_STACK_DEPTH_LIMIT]; /* a stack of esi elements that are open */ + int stackdepth; /* self explanatory */ + ESIParser::Pointer theParser; + ESIElement::Pointer top(); +--- a/src/esi/Esi.cc ++++ b/src/esi/Esi.cc +@@ -29,6 +29,7 @@ + #include "esi/Expression.h" + #include "esi/Segment.h" + #include "esi/VarState.h" ++#include "FadingCounter.h" + #include "HttpHdrSc.h" + #include "HttpHdrScTarget.h" + #include "HttpReply.h" +@@ -943,13 +944,18 @@ void + ESIContext::addStackElement (ESIElement::Pointer element) + { + /* Put on the stack to allow skipping of 'invalid' markup */ +- assert (parserState.stackdepth <11); ++ ++ // throw an error if the stack location would be invalid ++ if (parserState.stackdepth >= ESI_STACK_DEPTH_LIMIT) ++ throw Esi::Error("ESI Too many nested elements"); ++ if (parserState.stackdepth < 0) ++ throw Esi::Error("ESI elements stack error, probable error in ESI template"); ++ + assert (!failed()); + debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element.getRaw()); + + if (!parserState.top()->addElement(element)) { +- debugs(86, DBG_IMPORTANT, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template"); +- flags.error = 1; ++ throw Esi::Error("ESIContext::addStackElement failed, probable error in ESI template"); + } else { + /* added ok, push onto the stack */ + parserState.stack[parserState.stackdepth] = element; +@@ -1201,13 +1207,10 @@ ESIContext::addLiteral (const char *s, i + assert (len); + debugs(86, 5, "literal length is " << len); + /* give a literal to the current element */ +- assert (parserState.stackdepth <11); + ESIElement::Pointer element (new esiLiteral (this, s, len)); + +- if (!parserState.top()->addElement(element)) { +- debugs(86, DBG_IMPORTANT, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template"); +- flags.error = 1; +- } ++ if (!parserState.top()->addElement(element)) ++ throw Esi::Error("ESIContext::addLiteral failed, probable error in ESI template"); + } + + void +@@ -1269,8 +1272,24 @@ ESIContext::parse() + + PROF_start(esiParsing); + +- while (buffered.getRaw() && !flags.error) +- parseOneBuffer(); ++ try { ++ while (buffered.getRaw() && !flags.error) ++ parseOneBuffer(); ++ ++ } catch (Esi::ErrorDetail &errMsg) { // FIXME: non-const for c_str() ++ // level-2: these are protocol/syntax errors from upstream ++ debugs(86, 2, "WARNING: ESI syntax error: " << errMsg); ++ setError(); ++ setErrorMessage(errMsg.c_str()); ++ ++ } catch (...) { ++ // DBG_IMPORTANT because these are local issues the admin needs to fix ++ static FadingCounter logEntries; // TODO: set horizon less than infinity ++ if (logEntries.count(1) < 100) ++ debugs(86, DBG_IMPORTANT, "ERROR: ESI parser: unhandled exception"); ++ setError(); ++ setErrorMessage("ESI parser error"); ++ } + + PROF_stop(esiParsing); + +--- a/src/esi/Esi.h ++++ b/src/esi/Esi.h +@@ -10,6 +10,11 @@ + #define SQUID_ESI_H + + #include "clientStream.h" ++#include "SBuf.h" ++ ++#if !defined(ESI_STACK_DEPTH_LIMIT) ++#define ESI_STACK_DEPTH_LIMIT 20 ++#endif + + /* ESI.c */ + extern CSR esiStreamRead; +@@ -18,5 +23,14 @@ extern CSD esiStreamDetach; + extern CSS esiStreamStatus; + int esiEnableProcessing (HttpReply *); + ++namespace Esi ++{ ++ ++typedef SBuf ErrorDetail; ++/// prepare an Esi::ErrorDetail for throw on ESI parser internal errors ++inline Esi::ErrorDetail Error(const char *msg) { return ErrorDetail(msg); } ++ ++} // namespace Esi ++ + #endif /* SQUID_ESI_H */ + +--- a/src/esi/Expression.cc ++++ b/src/esi/Expression.cc +@@ -10,6 +10,7 @@ + + #include "squid.h" + #include "Debug.h" ++#include "esi/Esi.h" + #include "esi/Expression.h" + #include "profiler/Profiler.h" + +@@ -97,6 +98,17 @@ stackpop(stackmember * s, int *depth) + cleanmember(&s[*depth]); + } + ++static void ++stackpush(stackmember *stack, stackmember &item, int *depth) ++{ ++ if (*depth < 0) ++ throw Esi::Error("ESIExpression stack has negative size"); ++ if (*depth >= ESI_STACK_DEPTH_LIMIT) ++ throw Esi::Error("ESIExpression stack is full, cannot push"); ++ ++ stack[(*depth)++] = item; ++} ++ + static evaluate evalnegate; + static evaluate evalliteral; + static evaluate evalor; +@@ -208,6 +220,11 @@ evalnegate(stackmember * stack, int *dep + /* invalid stack */ + return 1; + ++ if (whereAmI < 0) ++ throw Esi::Error("negate expression location too small"); ++ if (*depth >= ESI_STACK_DEPTH_LIMIT) ++ throw Esi::Error("negate expression too complex"); ++ + if (stack[whereAmI + 1].valuetype != ESI_EXPR_EXPR) + /* invalid operand */ + return 1; +@@ -280,7 +297,7 @@ evalor(stackmember * stack, int *depth, + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -327,7 +344,7 @@ evaland(stackmember * stack, int *depth, + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -373,7 +390,7 @@ evallesseq(stackmember * stack, int *dep + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -421,7 +438,7 @@ evallessthan(stackmember * stack, int *d + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -469,7 +486,7 @@ evalmoreeq(stackmember * stack, int *dep + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -517,7 +534,7 @@ evalmorethan(stackmember * stack, int *d + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -566,7 +583,7 @@ evalequals(stackmember * stack, int *dep + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -613,7 +630,7 @@ evalnotequals(stackmember * stack, int * + + srv.precedence = 1; + +- stack[(*depth)++] = srv; ++ stackpush(stack, srv, depth); + + /* we're out of way, try adding now */ + if (!addmember(stack, depth, candidate)) +@@ -953,6 +970,9 @@ addmember(stackmember * stack, int *stac + /* !(!(a==b))) is why thats safe */ + /* strictly less than until we unwind */ + ++ if (*stackdepth >= ESI_STACK_DEPTH_LIMIT) ++ throw Esi::Error("ESI expression too complex to add member"); ++ + if (candidate->precedence < stack[*stackdepth - 1].precedence || + candidate->precedence < stack[*stackdepth - 2].precedence) { + /* must be an operator */ +@@ -968,10 +988,10 @@ addmember(stackmember * stack, int *stac + return 0; + } + } else { +- stack[(*stackdepth)++] = *candidate; ++ stackpush(stack, *candidate, stackdepth); + } + } else if (candidate->valuetype != ESI_EXPR_INVALID) +- stack[(*stackdepth)++] = *candidate; ++ stackpush(stack, *candidate, stackdepth); + + return 1; + } +@@ -979,7 +999,7 @@ addmember(stackmember * stack, int *stac + int + ESIExpression::Evaluate(char const *s) + { +- stackmember stack[20]; ++ stackmember stack[ESI_STACK_DEPTH_LIMIT]; + int stackdepth = 0; + char const *end; + PROF_start(esiExpressionEval); diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12520.patch squid3-3.5.27/debian/patches/CVE-2019-12520.patch --- squid3-3.5.27/debian/patches/CVE-2019-12520.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12520.patch 2020-07-28 16:36:36.000000000 +0000 @@ -0,0 +1,43 @@ +From: Markus Koschany +Date: Wed, 1 Jul 2020 13:55:21 +0200 +Subject: CVE-2019-12520 + +This is also the fix for CVE-2019-12524. + +Origin: http://www.squid-cache.org/Versions/v4/changesets/SQUID-2019_4.patch +--- + src/url.cc | 13 +++++++++---- + 1 file changed, 9 insertions(+), 4 deletions(-) + +--- a/src/url.cc ++++ b/src/url.cc +@@ -679,20 +679,25 @@ urlMakeAbsolute(const HttpRequest * req, + } + + size_t urllen; ++ const bool allowUserInfo = req->url.getScheme() == AnyP::PROTO_FTP || ++ req->url.getScheme() == AnyP::PROTO_UNKNOWN; ++ const char *login_data = ""; ++ if (allowUserInfo && *req->login) ++ login_data = req->login; + + if (req->port != urlDefaultPort(req->url.getScheme())) { + urllen = snprintf(urlbuf, MAX_URL, "%s://%s%s%s:%d", + req->url.getScheme().c_str(), +- req->login, +- *req->login ? "@" : null_string, ++ login_data, ++ *login_data ? "@" : null_string, + req->GetHost(), + req->port + ); + } else { + urllen = snprintf(urlbuf, MAX_URL, "%s://%s%s%s", + req->url.getScheme().c_str(), +- req->login, +- *req->login ? "@" : null_string, ++ login_data, ++ *login_data ? "@" : null_string, + req->GetHost() + ); + } diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12523-bug965012.patch squid3-3.5.27/debian/patches/CVE-2019-12523-bug965012.patch --- squid3-3.5.27/debian/patches/CVE-2019-12523-bug965012.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12523-bug965012.patch 2020-08-25 17:12:13.000000000 +0000 @@ -0,0 +1,71 @@ +From: Markus Koschany +Date: Wed, 12 Aug 2020 20:23:26 +0200 +Subject: CVE-2019-12523 bug965012 + +--- + src/adaptation/icap/ModXact.cc | 4 +++- + src/anyp/ProtocolType.cc | 4 ++++ + src/anyp/ProtocolType.h | 4 ++++ + src/url.cc | 12 ++++++++++++ + 4 files changed, 23 insertions(+), 1 deletion(-) + +--- a/src/adaptation/icap/ModXact.cc ++++ b/src/adaptation/icap/ModXact.cc +@@ -1540,7 +1540,9 @@ void Adaptation::Icap::ModXact::encapsul + if (const HttpRequest* old_request = dynamic_cast(head)) { + HttpRequest::Pointer new_request(new HttpRequest); + Must(old_request->canonical); +- urlParse(old_request->method, SBuf(old_request->canonical), new_request.getRaw()); ++ // copy the requst-line details ++ new_request->method = old_request->method; ++ new_request->url = old_request->url; + new_request->http_ver = old_request->http_ver; + headClone = new_request.getRaw(); + } else if (const HttpReply *old_reply = dynamic_cast(head)) { +--- a/src/anyp/ProtocolType.cc ++++ b/src/anyp/ProtocolType.cc +@@ -24,6 +24,10 @@ const char * ProtocolType_str[] = { + "WHOIS", + "ICY", + "UNKNOWN", ++ "ICAP", ++ "ICAPS", ++ "ECAP", ++ "ECAPS", + "MAX" + }; + }; // namespace AnyP +--- a/src/anyp/ProtocolType.h ++++ b/src/anyp/ProtocolType.h +@@ -37,6 +37,10 @@ typedef enum { + PROTO_WHOIS, + PROTO_ICY, + PROTO_UNKNOWN, ++ PROTO_ICAP, ++ PROTO_ICAPS, ++ PROTO_ECAP, ++ PROTO_ECAPS, + PROTO_MAX + } ProtocolType; + +--- a/src/url.cc ++++ b/src/url.cc +@@ -180,6 +180,18 @@ urlParseProtocol(const SBuf & protocol) + if (strncasecmp(b, "whois", len) == 0) + return AnyP::PROTO_WHOIS; + ++ if (strncasecmp(b, "icap", len) == 0) ++ return AnyP::PROTO_ICAP; ++ ++ if (strncasecmp(b, "icaps", len) == 0) ++ return AnyP::PROTO_ICAPS; ++ ++ if (strncasecmp(b, "ecap", len) == 0) ++ return AnyP::PROTO_ECAP; ++ ++ if (strncasecmp(b, "ecaps", len) == 0) ++ return AnyP::PROTO_ECAPS; ++ + return AnyP::PROTO_NONE; + } + diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12523.patch squid3-3.5.27/debian/patches/CVE-2019-12523.patch --- squid3-3.5.27/debian/patches/CVE-2019-12523.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12523.patch 2020-08-25 17:12:13.000000000 +0000 @@ -0,0 +1,737 @@ +From: Markus Koschany +Date: Tue, 30 Jun 2020 22:11:00 +0200 +Subject: CVE-2019-12523 + +Origin: http://www.squid-cache.org/Versions/v4/changesets/squid-4-fbbdf75efd7a5cc244b4886a9d42ea458c5a3a73.patch +--- + src/HttpRequest.cc | 12 +-- + src/HttpRequest.h | 4 +- + src/URL.h | 4 +- + src/acl/Asn.cc | 2 +- + src/adaptation/ecap/MessageRep.cc | 4 +- + src/adaptation/icap/ModXact.cc | 2 +- + src/client_side.cc | 2 +- + src/client_side_request.cc | 4 +- + src/htcp.cc | 2 +- + src/icmp/net_db.cc | 2 +- + src/icp_v2.cc | 2 +- + src/mgr/Inquirer.cc | 2 +- + src/mime.cc | 2 +- + src/neighbors.cc | 2 +- + src/peer_digest.cc | 2 +- + src/servers/FtpServer.cc | 6 +- + src/store_digest.cc | 2 +- + src/tests/testHttpRequest.cc | 31 ++---- + src/url.cc | 199 ++++++++++++++++++++++++++++++-------- + src/urn.cc | 2 +- + src/urn.h | 2 + + 21 files changed, 193 insertions(+), 97 deletions(-) + +--- a/src/HttpRequest.cc ++++ b/src/HttpRequest.cc +@@ -320,13 +320,7 @@ HttpRequest::parseFirstLine(const char * + if (end < start) // missing URI + return false; + +- char save = *end; +- +- * (char *) end = '\0'; // temp terminate URI, XXX dangerous? +- +- HttpRequest *tmp = urlParse(method, (char *) start, this); +- +- * (char *) end = save; ++ HttpRequest *tmp = urlParse(method, SBuf(start, size_t(end-start)), this); + + if (NULL == tmp) + return false; +@@ -540,7 +534,7 @@ HttpRequest::expectingBody(const HttpReq + * If the request cannot be created cleanly, NULL is returned + */ + HttpRequest * +-HttpRequest::CreateFromUrlAndMethod(char * url, const HttpRequestMethod& method) ++HttpRequest::CreateFromUrlAndMethod(const SBuf & url, const HttpRequestMethod& method) + { + return urlParse(method, url, NULL); + } +@@ -551,7 +545,7 @@ HttpRequest::CreateFromUrlAndMethod(char + * If the request cannot be created cleanly, NULL is returned + */ + HttpRequest * +-HttpRequest::CreateFromUrl(char * url) ++HttpRequest::CreateFromUrl(const SBuf & url) + { + return urlParse(Http::METHOD_GET, url, NULL); + } +--- a/src/HttpRequest.h ++++ b/src/HttpRequest.h +@@ -224,9 +224,9 @@ public: + + static void httpRequestPack(void *obj, Packer *p); + +- static HttpRequest * CreateFromUrlAndMethod(char * url, const HttpRequestMethod& method); ++ static HttpRequest * CreateFromUrlAndMethod(const SBuf & url, const HttpRequestMethod& method); + +- static HttpRequest * CreateFromUrl(char * url); ++ static HttpRequest * CreateFromUrl(const SBuf & url); + + ConnStateData *pinnedConnection(); + +--- a/src/URL.h ++++ b/src/URL.h +@@ -62,9 +62,9 @@ MEMPROXY_CLASS_INLINE(URL); + class HttpRequest; + class HttpRequestMethod; + +-AnyP::ProtocolType urlParseProtocol(const char *, const char *e = NULL); ++AnyP::ProtocolType urlParseProtocol(const SBuf &); + void urlInitialize(void); +-HttpRequest *urlParse(const HttpRequestMethod&, char *, HttpRequest *request = NULL); ++HttpRequest *urlParse(const HttpRequestMethod&, const SBuf & rawRequest, HttpRequest *request = NULL); + const char *urlCanonical(HttpRequest *); + char *urlCanonicalClean(const HttpRequest *); + const char *urlCanonicalFakeHttps(const HttpRequest * request); +--- a/src/acl/Asn.cc ++++ b/src/acl/Asn.cc +@@ -240,7 +240,7 @@ asnCacheStart(int as) + debugs(53, 3, "AS " << as); + snprintf(asres, 4096, "whois://%s/!gAS%d", Config.as_whois_server, as); + asState->as_number = as; +- asState->request = HttpRequest::CreateFromUrl(asres); ++ asState->request = HttpRequest::CreateFromUrl(SBuf(asres)); + assert(asState->request != NULL); + + if ((e = storeGetPublic(asres, Http::METHOD_GET)) == NULL) { +--- a/src/adaptation/ecap/MessageRep.cc ++++ b/src/adaptation/ecap/MessageRep.cc +@@ -210,9 +210,7 @@ Adaptation::Ecap::RequestLineRep::uri(co + // TODO: if method is not set, urlPath will assume it is not connect; + // Can we change urlParse API to remove the method parameter? + // TODO: optimize: urlPath should take constant URL buffer +- char *buf = xstrdup(aUri.toString().c_str()); +- const bool ok = urlParse(theMessage.method, buf, &theMessage); +- xfree(buf); ++ const bool ok = urlParse(theMessage.method, SBuf(aUri.toString()), &theMessage); + Must(ok); + } + +--- a/src/adaptation/icap/ModXact.cc ++++ b/src/adaptation/icap/ModXact.cc +@@ -1540,7 +1540,7 @@ void Adaptation::Icap::ModXact::encapsul + if (const HttpRequest* old_request = dynamic_cast(head)) { + HttpRequest::Pointer new_request(new HttpRequest); + Must(old_request->canonical); +- urlParse(old_request->method, old_request->canonical, new_request.getRaw()); ++ urlParse(old_request->method, SBuf(old_request->canonical), new_request.getRaw()); + new_request->http_ver = old_request->http_ver; + headClone = new_request.getRaw(); + } else if (const HttpReply *old_reply = dynamic_cast(head)) { +--- a/src/client_side.cc ++++ b/src/client_side.cc +@@ -2647,7 +2647,7 @@ clientProcessRequest(ConnStateData *conn + return; + } + +- if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) { ++ if ((request = HttpRequest::CreateFromUrlAndMethod(SBuf(http->uri), method)) == NULL) { + clientStreamNode *node = context->getClientReplyContext(); + debugs(33, 5, "Invalid URL: " << http->uri); + conn->quitAfterError(request.getRaw()); +--- a/src/client_side_request.cc ++++ b/src/client_side_request.cc +@@ -325,7 +325,7 @@ clientBeginRequest(const HttpRequestMeth + http->uri = (char *)xcalloc(url_sz, 1); + strcpy(http->uri, url); + +- if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) { ++ if ((request = HttpRequest::CreateFromUrlAndMethod(SBuf(http->uri), method)) == NULL) { + debugs(85, 5, "Invalid URL: " << http->uri); + return -1; + } +@@ -1282,7 +1282,7 @@ ClientRequestContext::clientRedirectDone + // XXX: validate the URL properly *without* generating a whole new request object right here. + // XXX: the clone() should be done only AFTER we know the new URL is valid. + HttpRequest *new_request = old_request->clone(); +- if (urlParse(old_request->method, const_cast(urlNote), new_request)) { ++ if (urlParse(old_request->method, SBuf(urlNote), new_request)) { + debugs(61,2, HERE << "URL-rewriter diverts URL from " << urlCanonical(old_request) << " to " << urlCanonical(new_request)); + + // update the new request to flag the re-writing was done on it +--- a/src/htcp.cc ++++ b/src/htcp.cc +@@ -747,7 +747,7 @@ htcpUnpackSpecifier(char *buf, int sz) + */ + method = HttpRequestMethod(s->method, NULL); + +- s->request = HttpRequest::CreateFromUrlAndMethod(s->uri, method == Http::METHOD_NONE ? HttpRequestMethod(Http::METHOD_GET) : method); ++ s->request = HttpRequest::CreateFromUrlAndMethod(SBuf(s->uri), method == Http::METHOD_NONE ? HttpRequestMethod(Http::METHOD_GET) : method); + + if (s->request) + HTTPMSGLOCK(s->request); +--- a/src/icmp/net_db.cc ++++ b/src/icmp/net_db.cc +@@ -1285,7 +1285,7 @@ netdbExchangeStart(void *data) + uri = internalRemoteUri(p->host, p->http_port, "/squid-internal-dynamic/", "netdb"); + debugs(38, 3, "netdbExchangeStart: Requesting '" << uri << "'"); + assert(NULL != uri); +- ex->r = HttpRequest::CreateFromUrl(uri); ++ ex->r = HttpRequest::CreateFromUrl(SBuf(uri)); + + if (NULL == ex->r) { + debugs(38, DBG_IMPORTANT, "netdbExchangeStart: Bad URI " << uri); +--- a/src/icp_v2.cc ++++ b/src/icp_v2.cc +@@ -438,7 +438,7 @@ icpGetRequest(char *url, int reqnum, int + + HttpRequest *result; + +- if ((result = HttpRequest::CreateFromUrl(url)) == NULL) ++ if ((result = HttpRequest::CreateFromUrl(SBuf(url))) == NULL) + icpCreateAndSend(ICP_ERR, 0, url, reqnum, 0, fd, from); + + return result; +--- a/src/mgr/Inquirer.cc ++++ b/src/mgr/Inquirer.cc +@@ -76,7 +76,7 @@ Mgr::Inquirer::start() + if (strands.empty()) { + LOCAL_ARRAY(char, url, MAX_URL); + snprintf(url, MAX_URL, "%s", aggrAction->command().params.httpUri.termedBuf()); +- HttpRequest *req = HttpRequest::CreateFromUrl(url); ++ HttpRequest *req = HttpRequest::CreateFromUrl(SBuf(url)); + ErrorState err(ERR_INVALID_URL, Http::scNotFound, req); + std::unique_ptr reply(err.BuildHttpReply()); + replyBuf.reset(reply->pack()); +--- a/src/mime.cc ++++ b/src/mime.cc +@@ -407,7 +407,7 @@ MimeIcon::created(StoreEntry *newEntry) + EBIT_SET(e->flags, ENTRY_SPECIAL); + e->setPublicKey(); + e->buffer(); +- HttpRequest *r = HttpRequest::CreateFromUrl(url_); ++ HttpRequest *r = HttpRequest::CreateFromUrl(SBuf(url_)); + + if (NULL == r) + fatalf("mimeLoadIcon: cannot parse internal URL: %s", url_); +--- a/src/neighbors.cc ++++ b/src/neighbors.cc +@@ -1398,7 +1398,7 @@ peerCountMcastPeersStart(void *data) + p->in_addr.toUrl(url+7, MAX_URL -8 ); + strcat(url, "/"); + fake = storeCreateEntry(url, url, RequestFlags(), Http::METHOD_GET); +- HttpRequest *req = HttpRequest::CreateFromUrl(url); ++ HttpRequest *req = HttpRequest::CreateFromUrl(SBuf(url)); + psstate = new ps_state; + psstate->request = req; + HTTPMSGLOCK(psstate->request); +--- a/src/peer_digest.cc ++++ b/src/peer_digest.cc +@@ -293,7 +293,7 @@ peerDigestRequest(PeerDigest * pd) + else + url = xstrdup(internalRemoteUri(p->host, p->http_port, "/squid-internal-periodic/", StoreDigestFileName)); + +- req = HttpRequest::CreateFromUrl(url); ++ req = HttpRequest::CreateFromUrl(SBuf(url)); + + assert(req); + +--- a/src/servers/FtpServer.cc ++++ b/src/servers/FtpServer.cc +@@ -721,12 +721,10 @@ Ftp::Server::parseOneRequest(Http::Proto + const SBuf *path = (params.length() && CommandHasPathParameter(cmd)) ? + ¶ms : NULL; + calcUri(path); +- char *newUri = xstrdup(uri.c_str()); +- HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(newUri, method); ++ HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(uri, method); + if (!request) { + debugs(33, 5, "Invalid FTP URL: " << uri); + uri.clear(); +- safe_free(newUri); + return earlyError(eekInvalidUri); + } + +@@ -749,7 +747,7 @@ Ftp::Server::parseOneRequest(Http::Proto + http->request = request; + HTTPMSGLOCK(http->request); + http->req_sz = tok.parsedSize(); +- http->uri = newUri; ++ http->uri = xstrdup(uri.c_str()); + + ClientSocketContext *const result = + new ClientSocketContext(clientConnection, http); +--- a/src/store_digest.cc ++++ b/src/store_digest.cc +@@ -420,7 +420,7 @@ storeDigestRewriteStart(void *datanotuse + assert(e); + sd_state.rewrite_lock = e; + debugs(71, 3, "storeDigestRewrite: url: " << url << " key: " << e->getMD5Text()); +- HttpRequest *req = HttpRequest::CreateFromUrl(url); ++ HttpRequest *req = HttpRequest::CreateFromUrl(SBuf(url)); + e->mem_obj->request = req; + HTTPMSGLOCK(e->mem_obj->request); + /* wait for rebuild (if any) to finish */ +--- a/src/tests/testHttpRequest.cc ++++ b/src/tests/testHttpRequest.cc +@@ -43,47 +43,43 @@ testHttpRequest::testCreateFromUrlAndMet + { + /* vanilla url */ + unsigned short expected_port; +- char * url = xstrdup("http://foo:90/bar"); ++ SBuf url("http://foo:90/bar"); + HttpRequest *aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET); + expected_port = 90; ++ CPPUNIT_ASSERT(aRequest != nullptr); + HttpRequest *nullRequest = NULL; + CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->port); + CPPUNIT_ASSERT(aRequest->method == Http::METHOD_GET); + CPPUNIT_ASSERT_EQUAL(String("foo"), String(aRequest->GetHost())); + CPPUNIT_ASSERT_EQUAL(String("/bar"), aRequest->urlpath); + CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast(aRequest->url.getScheme())); +- CPPUNIT_ASSERT_EQUAL(String("http://foo:90/bar"), String(url)); +- xfree(url); + + /* vanilla url, different method */ +- url = xstrdup("http://foo/bar"); ++ url = "http://foo/bar"; + aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_PUT); + expected_port = 80; ++ CPPUNIT_ASSERT(aRequest != nullptr); + CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->port); + CPPUNIT_ASSERT(aRequest->method == Http::METHOD_PUT); + CPPUNIT_ASSERT_EQUAL(String("foo"), String(aRequest->GetHost())); + CPPUNIT_ASSERT_EQUAL(String("/bar"), aRequest->urlpath); + CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast(aRequest->url.getScheme())); +- CPPUNIT_ASSERT_EQUAL(String("http://foo/bar"), String(url)); +- xfree(url); + + /* a connect url with non-CONNECT data */ +- url = xstrdup(":foo/bar"); ++ url = ":foo/bar"; + aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_CONNECT); +- xfree(url); + CPPUNIT_ASSERT_EQUAL(nullRequest, aRequest); + + /* a CONNECT url with CONNECT data */ +- url = xstrdup("foo:45"); ++ url = "foo:45"; + aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_CONNECT); + expected_port = 45; ++ CPPUNIT_ASSERT(aRequest != nullptr); + CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->port); + CPPUNIT_ASSERT(aRequest->method == Http::METHOD_CONNECT); + CPPUNIT_ASSERT_EQUAL(String("foo"), String(aRequest->GetHost())); + CPPUNIT_ASSERT_EQUAL(String(""), aRequest->urlpath); + CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_NONE, static_cast(aRequest->url.getScheme())); +- CPPUNIT_ASSERT_EQUAL(String("foo:45"), String(url)); +- xfree(url); + } + + /* +@@ -113,11 +109,10 @@ void + testHttpRequest::testIPv6HostColonBug() + { + unsigned short expected_port; +- char * url = NULL; + HttpRequest *aRequest = NULL; + + /* valid IPv6 address without port */ +- url = xstrdup("http://[2000:800::45]/foo"); ++ SBuf url("http://[2000:800::45]/foo"); + aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET); + expected_port = 80; + CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->port); +@@ -125,11 +120,9 @@ testHttpRequest::testIPv6HostColonBug() + CPPUNIT_ASSERT_EQUAL(String("[2000:800::45]"), String(aRequest->GetHost())); + CPPUNIT_ASSERT_EQUAL(String("/foo"), aRequest->urlpath); + CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast(aRequest->url.getScheme())); +- CPPUNIT_ASSERT_EQUAL(String("http://[2000:800::45]/foo"), String(url)); +- xfree(url); + + /* valid IPv6 address with port */ +- url = xstrdup("http://[2000:800::45]:90/foo"); ++ url = "http://[2000:800::45]:90/foo"; + aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET); + expected_port = 90; + CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->port); +@@ -137,11 +130,9 @@ testHttpRequest::testIPv6HostColonBug() + CPPUNIT_ASSERT_EQUAL(String("[2000:800::45]"), String(aRequest->GetHost())); + CPPUNIT_ASSERT_EQUAL(String("/foo"), aRequest->urlpath); + CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast(aRequest->url.getScheme())); +- CPPUNIT_ASSERT_EQUAL(String("http://[2000:800::45]:90/foo"), String(url)); +- xfree(url); + + /* IPv6 address as invalid (bug trigger) */ +- url = xstrdup("http://2000:800::45/foo"); ++ url = "http://2000:800::45/foo"; + aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET); + expected_port = 80; + CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->port); +@@ -149,8 +140,6 @@ testHttpRequest::testIPv6HostColonBug() + CPPUNIT_ASSERT_EQUAL(String("[2000:800::45]"), String(aRequest->GetHost())); + CPPUNIT_ASSERT_EQUAL(String("/foo"), aRequest->urlpath); + CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast(aRequest->url.getScheme())); +- CPPUNIT_ASSERT_EQUAL(String("http://2000:800::45/foo"), String(url)); +- xfree(url); + } + + void +--- a/src/url.cc ++++ b/src/url.cc +@@ -11,10 +11,12 @@ + #include "squid.h" + #include "globals.h" + #include "HttpRequest.h" ++#include "parser/Tokenizer.h" + #include "rfc1738.h" + #include "SquidConfig.h" + #include "SquidString.h" + #include "URL.h" ++#include "urn.h" + + static HttpRequest *urlParseFinish(const HttpRequestMethod& method, + const AnyP::ProtocolType protocol, +@@ -23,7 +25,9 @@ static HttpRequest *urlParseFinish(const + const char *const login, + const int port, + HttpRequest *request); +-static HttpRequest *urnParse(const HttpRequestMethod& method, char *urn, HttpRequest *request); ++ ++static HttpRequest *parseUrn(const HttpRequestMethod &method, Parser::Tokenizer &tok, HttpRequest *request); ++ + static const char valid_hostname_chars_u[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" +@@ -37,6 +41,7 @@ static const char valid_hostname_chars[] + "[:]" + ; + ++ + void + urlInitialize(void) + { +@@ -82,12 +87,54 @@ urlInitialize(void) + } + + /** ++ * Extract the URI scheme and ':' delimiter from the given input buffer. ++ * ++ * Schemes up to 16 characters are accepted. ++ * ++ * Governed by RFC 3986 section 3.1 ++ */ ++static AnyP::UriScheme ++uriParseScheme(Parser::Tokenizer &tok) ++{ ++ /* ++ * RFC 3986 section 3.1 paragraph 2: ++ * ++ * Scheme names consist of a sequence of characters beginning with a ++ * letter and followed by any combination of letters, digits, plus ++ * ("+"), period ("."), or hyphen ("-"). ++ * ++ * The underscore ("_") required to match "cache_object://" squid ++ * special URI scheme. ++ */ ++ static const auto schemeChars = ++#if USE_HTTP_VIOLATIONS ++ CharacterSet("special", "_") + ++#endif ++ CharacterSet("scheme", "+.-") + CharacterSet::ALPHA + CharacterSet::DIGIT; ++ ++ SBuf str; ++ if (tok.prefix(str, schemeChars, 16) && tok.skip(':') && CharacterSet::ALPHA[str.at(0)]) { ++ // const auto protocol = AnyP::UriScheme::FindProtocolType(str); ++ const auto protocol = urlParseProtocol(str); ++ if (protocol == AnyP::PROTO_UNKNOWN) { ++ // return AnyP::UriScheme(protocol, str.c_str()); ++ throw TextException("Unknown protocol", __FILE__, __LINE__); ++ } ++ return AnyP::UriScheme(protocol); ++ } ++ ++ throw TextException("invalid URI scheme", __FILE__, __LINE__); ++} ++ ++ ++/** + * urlParseProtocol() takes begin (b) and end (e) pointers, but for + * backwards compatibility, e defaults to NULL, in which case we + * assume b is NULL-terminated. + */ ++ + AnyP::ProtocolType +-urlParseProtocol(const char *b, const char *e) ++urlParseProtocol(const SBuf & protocol) //const char *b, const char *e) + { + /* + * if e is NULL, b must be NULL terminated and we +@@ -95,10 +142,8 @@ urlParseProtocol(const char *b, const ch + * after b. + */ + +- if (NULL == e) +- e = b + strcspn(b, ":"); +- +- int len = e - b; ++ auto len = protocol.length(); ++ auto b = protocol.rawContent(); + + /* test common stuff first */ + +@@ -199,6 +244,13 @@ urlAppendDomain(char *host) + return true; + } + ++static const SBuf & ++Asterisk() ++{ ++ static SBuf star("*"); ++ return star; ++} ++ + /* + * Parse a URI/URL. + * +@@ -221,59 +273,71 @@ urlAppendDomain(char *host) + * being "end of host with implied path of /". + */ + HttpRequest * +-urlParse(const HttpRequestMethod& method, char *url, HttpRequest *request) ++urlParse(const HttpRequestMethod& method, const SBuf &rawUrl, HttpRequest *request) + { +- LOCAL_ARRAY(char, proto, MAX_URL); ++ try { ++ + LOCAL_ARRAY(char, login, MAX_URL); + LOCAL_ARRAY(char, host, MAX_URL); + LOCAL_ARRAY(char, urlpath, MAX_URL); + char *t = NULL; + char *q = NULL; + int port; +- AnyP::ProtocolType protocol = AnyP::PROTO_NONE; + int l; + int i; + const char *src; + char *dst; +- proto[0] = host[0] = urlpath[0] = login[0] = '\0'; ++ host[0] = urlpath[0] = login[0] = '\0'; + +- if ((l = strlen(url)) + Config.appendDomainLen > (MAX_URL - 1)) { +- /* terminate so it doesn't overflow other buffers */ +- *(url + (MAX_URL >> 1)) = '\0'; ++ if ((l = rawUrl.length()) + Config.appendDomainLen > (MAX_URL - 1)) { + debugs(23, DBG_IMPORTANT, "urlParse: URL too large (" << l << " bytes)"); + return NULL; + } ++ ++ if ((method == Http::METHOD_OPTIONS || method == Http::METHOD_TRACE) && ++ Asterisk().cmp(rawUrl) == 0) { ++ // XXX: these methods might also occur in HTTPS traffic. Handle this better. ++ port = urlDefaultPort(AnyP::PROTO_HTTP); ++ ++ const SBuf& h = Asterisk(); ++ return urlParseFinish(method, AnyP::PROTO_HTTP, h.rawContent(), host, login, port, request); ++ } ++ ++ Parser::Tokenizer tok(rawUrl); ++ AnyP::UriScheme scheme; ++ + if (method == Http::METHOD_CONNECT) { + port = CONNECT_PORT; + ++ // XXX: use tokenizer ++ auto B = tok.buf(); ++ const char *url = B.c_str(); ++ + if (sscanf(url, "[%[^]]]:%d", host, &port) < 1) + if (sscanf(url, "%[^:]:%d", host, &port) < 1) + return NULL; + +- } else if ((method == Http::METHOD_OPTIONS || method == Http::METHOD_TRACE) && +- strcmp(url, "*") == 0) { +- protocol = AnyP::PROTO_HTTP; +- port = urlDefaultPort(protocol); +- return urlParseFinish(method, protocol, url, host, login, port, request); +- } else if (!strncmp(url, "urn:", 4)) { +- return urnParse(method, url, request); + } else { +- /* Parse the URL: */ +- src = url; +- i = 0; +- /* Find first : - everything before is protocol */ +- for (i = 0, dst = proto; i < l && *src != ':'; ++i, ++src, ++dst) { +- *dst = *src; ++ scheme = uriParseScheme(tok); ++ ++ if (scheme == AnyP::PROTO_NONE) ++ return NULL; // invalid scheme ++ ++ if (scheme == AnyP::PROTO_URN) { ++ return parseUrn(method, tok, request); // throws on any error + } +- if (i >= l) +- return NULL; +- *dst = '\0'; + +- /* Then its :// */ +- if ((i+3) > l || *src != ':' || *(src + 1) != '/' || *(src + 2) != '/') +- return NULL; +- i += 3; +- src += 3; ++ // URLs then have "//" ++ static const SBuf doubleSlash("//"); ++ if (!tok.skip(doubleSlash)) ++ return NULL; ++ ++ auto B = tok.remaining(); ++ const char *url = B.c_str(); ++ ++ /* Parse the URL: */ ++ src = url; ++ i = 0; + + /* Then everything until first /; thats host (and port; which we'll look for here later) */ + // bug 1881: If we don't get a "/" then we imply it was there +@@ -314,8 +378,8 @@ urlParse(const HttpRequestMethod& method + } + *dst = '\0'; + +- protocol = urlParseProtocol(proto); +- port = urlDefaultPort(protocol); ++ // port = scheme.defaultPort(); // may be reset later ++ port = urlDefaultPort(scheme); + + /* Is there any login information? (we should eventually parse it above) */ + t = strrchr(host, '@'); +@@ -363,7 +427,7 @@ urlParse(const HttpRequestMethod& method + } + + // Bug 3183 sanity check: If scheme is present, host must be too. +- if (protocol != AnyP::PROTO_NONE && host[0] == '\0') { ++ if (scheme != AnyP::PROTO_NONE && host[0] == '\0') { + debugs(23, DBG_IMPORTANT, "SECURITY ALERT: Missing hostname in URL '" << url << "'. see access.log for details."); + return NULL; + } +@@ -392,7 +456,7 @@ urlParse(const HttpRequestMethod& method + } + } + +- debugs(23, 3, "urlParse: Split URL '" << url << "' into proto='" << proto << "', host='" << host << "', port='" << port << "', path='" << urlpath << "'"); ++ debugs(23, 3, "urlParse: Split URL '" << rawUrl << "' into proto='" << scheme << "', host='" << host << "', port='" << port << "', path='" << urlpath << "'"); + + if (Config.onoff.check_hostnames && strspn(host, Config.onoff.allow_underscore ? valid_hostname_chars_u : valid_hostname_chars) != strlen(host)) { + debugs(23, DBG_IMPORTANT, "urlParse: Illegal character in hostname '" << host << "'"); +@@ -427,7 +491,7 @@ urlParse(const HttpRequestMethod& method + #endif + + if (stringHasWhitespace(urlpath)) { +- debugs(23, 2, "urlParse: URI has whitespace: {" << url << "}"); ++ debugs(23, 2, "urlParse: URI has whitespace: {" << rawUrl << "}"); + + switch (Config.uri_whitespace) { + +@@ -460,7 +524,12 @@ urlParse(const HttpRequestMethod& method + } + } + +- return urlParseFinish(method, protocol, urlpath, host, login, port, request); ++ return urlParseFinish(method, scheme, urlpath, host, login, port, request); ++ ++ } catch (...) { ++ debugs(23, 2, "error: " << CurrentException << " " << Raw("rawUrl", rawUrl.rawContent(), rawUrl.length())); ++ } ++ return NULL; + } + + /** +@@ -490,6 +559,51 @@ urlParseFinish(const HttpRequestMethod& + return request; + } + ++/** ++ * Governed by RFC 8141 section 2: ++ * ++ * assigned-name = "urn" ":" NID ":" NSS ++ * NID = (alphanum) 0*30(ldh) (alphanum) ++ * ldh = alphanum / "-" ++ * NSS = pchar *(pchar / "/") ++ * ++ * RFC 3986 Appendix D.2 defines (as deprecated): ++ * ++ * alphanum = ALPHA / DIGIT ++ * ++ * Notice that NID is exactly 2-32 characters in length. ++ */ ++static HttpRequest* ++parseUrn(const HttpRequestMethod &method, Parser::Tokenizer &tok, HttpRequest *request) ++{ ++ static const auto nidChars = CharacterSet("NID","-") + CharacterSet::ALPHA + CharacterSet::DIGIT; ++ static const auto alphanum = (CharacterSet::ALPHA + CharacterSet::DIGIT).rename("alphanum"); ++ SBuf nid; ++ if (!tok.prefix(nid, nidChars, 32)) ++ throw TextException("NID not found", __FILE__, __LINE__); ++ ++ if (!tok.skip(':')) ++ throw TextException("NID too long or missing ':' delimiter", __FILE__, __LINE__); ++ ++ if (nid.length() < 2) ++ throw TextException("NID too short", __FILE__, __LINE__); ++ ++ if (!alphanum[nid[0]]) ++ throw TextException("NID prefix is not alphanumeric", __FILE__, __LINE__); ++ ++ if (!alphanum[nid[nid.length()-1]]) ++ throw TextException("NID suffix is not alphanumeric", __FILE__, __LINE__); ++ ++ nid.append(':').append(tok.remaining()); ++ if (request) { ++ request->initHTTP(method, AnyP::PROTO_URN, nid.rawContent()); ++ safe_free(request->canonical); ++ return request; ++ } ++ return new HttpRequest(method, AnyP::PROTO_URN, nid.rawContent()); ++} ++ ++/* + static HttpRequest * + urnParse(const HttpRequestMethod& method, char *urn, HttpRequest *request) + { +@@ -501,7 +615,7 @@ urnParse(const HttpRequestMethod& method + } + + return new HttpRequest(method, AnyP::PROTO_URN, urn + 4); +-} ++}*/ + + const char * + urlCanonical(HttpRequest * request) +@@ -673,7 +787,8 @@ urlMakeAbsolute(const HttpRequest * req, + char *urlbuf = (char *)xmalloc(MAX_URL * sizeof(char)); + + if (req->url.getScheme() == AnyP::PROTO_URN) { +- snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH, ++ snprintf(urlbuf, MAX_URL, "urn:%s:" SQUIDSTRINGPH, ++ req->GetHost(), + SQUIDSTRINGPRINT(req->urlpath)); + return (urlbuf); + } +--- a/src/urn.cc ++++ b/src/urn.cc +@@ -183,7 +183,7 @@ UrnState::createUriResRequest (String &u + safe_free(host); + safe_free(urlres); + urlres = xstrdup(local_urlres); +- urlres_r = HttpRequest::CreateFromUrl(urlres); ++ urlres_r = HttpRequest::CreateFromUrl(SBuf(urlres)); + } + + void +--- a/src/urn.h ++++ b/src/urn.h +@@ -16,5 +16,7 @@ class StoreEntry; + + void urnStart(HttpRequest *, StoreEntry *); + ++std::ostream& CurrentException(std::ostream &os); ++ + #endif /* SQUID_URN_H_ */ + diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12525.patch squid3-3.5.27/debian/patches/CVE-2019-12525.patch --- squid3-3.5.27/debian/patches/CVE-2019-12525.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12525.patch 2019-07-16 15:47:48.000000000 +0000 @@ -0,0 +1,32 @@ +From ec0d0f39cf28da14eead0ba5e777e95855bc2f67 Mon Sep 17 00:00:00 2001 +From: Amos Jeffries +Date: Sat, 8 Jun 2019 21:09:23 +0000 +Subject: [PATCH] Fix Digest auth parameter parsing (#415) + +Only remove quoting if the domain=, uri= or qop= parameter +value is surrounded by double-quotes. +--- + src/auth/digest/Config.cc | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/auth/digest/Config.cc b/src/auth/digest/Config.cc +index 674dd93cd2..d2cd2e96e5 100644 +--- a/src/auth/digest/Config.cc ++++ b/src/auth/digest/Config.cc +@@ -781,14 +781,14 @@ Auth::Digest::Config::decode(char const *proxy_auth, const char *aRequestRealm) + if (keyName == SBuf("domain",6) || keyName == SBuf("uri",3)) { + // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"' + // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain +- if (*p == '"' && *(p + vlen -1) == '"') { ++ if (vlen > 1 && *p == '"' && *(p + vlen -1) == '"') { + value.limitInit(p+1, vlen-2); + } + } else if (keyName == SBuf("qop",3)) { + // qop is more special. + // On request this must not be quoted-string de-quoted. But is several values wrapped in '"' + // On response this is a single un-quoted token. +- if (*p == '"' && *(p + vlen -1) == '"') { ++ if (vlen > 1 && *p == '"' && *(p + vlen -1) == '"') { + value.limitInit(p+1, vlen-2); + } else { + value.limitInit(p, vlen); diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12526.patch squid3-3.5.27/debian/patches/CVE-2019-12526.patch --- squid3-3.5.27/debian/patches/CVE-2019-12526.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12526.patch 2020-07-28 16:38:51.000000000 +0000 @@ -0,0 +1,418 @@ +From: Markus Koschany +Date: Wed, 10 Jun 2020 14:18:16 +0200 +Subject: CVE-2019-12526 + +Origin: http://www.squid-cache.org/Versions/v4/changesets/squid-4-7aa0184a720fd216191474e079f4fe87de7c4f5a.patch +--- + src/base/Here.cc | 93 +++++++++++++++++++++++++++++++++++++++++++++++ + src/base/Here.h | 77 +++++++++++++++++++++++++++++++++++++++ + src/base/Makefile.am | 2 + + src/base/Makefile.in | 4 +- + src/base/TextException.cc | 19 ++++++++++ + src/base/TextException.h | 16 ++++++++ + src/urn.cc | 45 ++++++++++++----------- + 7 files changed, 233 insertions(+), 23 deletions(-) + create mode 100644 src/base/Here.cc + create mode 100644 src/base/Here.h + +diff --git a/src/base/Here.cc b/src/base/Here.cc +new file mode 100644 +index 0000000..48f94af +--- /dev/null ++++ b/src/base/Here.cc +@@ -0,0 +1,93 @@ ++/* ++ * 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. ++ */ ++ ++#include "squid.h" ++#include "base/Here.h" ++ ++#include ++ ++/* File name hashing helpers */ ++ ++// Build prefix is the file system path leading to Squid src/ source directory. ++// It is "." for in-tree builds but may be lengthy and sensitive otherwise. ++ ++/// \returns the build prefix length or, if estimation is not possible, zero ++static size_t ++BuildPrefixLength() ++{ ++ // The hard-coded tail must be kept in sync with this file actual name! ++ const char *tail = "src/base/Here.cc"; ++ const char *full = __FILE__; ++ ++ // Disable heuristic if it does not work. ++ if (strstr(full, tail) == 0) ++ return 0; ++ ++ return strlen(full) - strlen(tail); ++} ++ ++/// \returns filename portion without the build prefix ++static const char * ++SkipBuildPrefix(const char* path) ++{ ++ static const size_t ToSkip = BuildPrefixLength(); ++ return path + ToSkip; ++} ++ ++/// quickly computes a (weak) hash of a file name ++static SourceLocationId ++FileNameHash(const char *path) ++{ ++ // Keep in sync with FileNameHash() in scripts/calc-must-ids.pl! ++ ++ const char *name = strrchr(path, '/'); ++ if (name) ++ ++name; // skip '/' ++ else ++ name = path; ++ ++ uint32_t hash = 0; ++ uint32_t iterations = 0; ++ while (*name) { ++ ++iterations; ++ hash ^= 271 * static_cast(*name); ++ ++name; ++ } ++ return hash ^ (iterations * 271); ++} ++ ++/* SourceLocation */ ++ ++SourceLocationId ++SourceLocation::id() const ++{ ++ const auto fnameHashFull = fileNameHashCacher(fileName, &FileNameHash); ++ // 32 bits = 18 bits for the filename hash + 14 bits for the line number. ++ // Keep in sync with ComputeMustIds() in scripts/calc-must-ids.pl. ++ const auto fnameHash = fnameHashFull % 0x3FFFF; ++ return (fnameHash << 14) | (lineNo & 0x3FFF); ++} ++ ++std::ostream & ++SourceLocation::print(std::ostream &os) const ++{ ++ if (fileName) { ++ os << SkipBuildPrefix(fileName); ++ ++ // TODO: Use more common and search-friendlier fileName:lineNo: format. ++ if (lineNo > 0) ++ os << '(' << lineNo << ')'; ++ } ++ if (context) { ++ if (fileName) ++ os << ' '; ++ os << context; ++ } ++ return os; ++} ++ +diff --git a/src/base/Here.h b/src/base/Here.h +new file mode 100644 +index 0000000..013f7c3 +--- /dev/null ++++ b/src/base/Here.h +@@ -0,0 +1,77 @@ ++/* ++ * 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_BASE_HERE_H ++#define SQUID_BASE_HERE_H ++ ++#include ++ ++/// source code location of the caller ++#define Here() SourceLocation(__FUNCTION__, __FILE__, __LINE__) ++ ++/// semi-uniquely identifies a source code location; stable across Squid runs ++typedef uint32_t SourceLocationId; ++ ++/// returns a hash of a file name ++typedef SourceLocationId FileNameHasher(const char *fileName); ++ ++/// a caching proxy for `hasher` results ++typedef SourceLocationId FileNameHashCacher(const char *fileName, FileNameHasher hasher); ++ ++static FileNameHashCacher UnitFileNameHashCacher; ++ ++/// a source code location that is cheap to create, copy, and store ++class SourceLocation ++{ ++public: ++ SourceLocation(const char *aContext, const char *aFileName, const int aLineNo): ++ context(aContext), ++ fileName(aFileName), ++ lineNo(aLineNo), ++ fileNameHashCacher(&UnitFileNameHashCacher) ++ {} ++ ++ /// \returns our location identifier ++ SourceLocationId id() const; ++ ++ /// describes location using a compact but human-friendly format ++ std::ostream &print(std::ostream &os) const; ++ ++ const char *context; ///< line-independent location description ++ const char *fileName; ///< source file name, often relative to build path ++ int lineNo; ///< line number inside the source file name (if positive) ++ ++private: ++ SourceLocationId calculateId(FileNameHasher) const; ++ FileNameHashCacher *fileNameHashCacher; ++}; ++ ++inline std::ostream & ++operator <<(std::ostream &os, const SourceLocation &location) ++{ ++ return location.print(os); ++} ++ ++/// SourceLocation::id() speed optimization hack: Caches `hasher` results. The ++/// cache capacity is one filename hash. Each translation unit gets one cache. ++static SourceLocationId ++UnitFileNameHashCacher(const char *fileName, FileNameHasher hasher) ++{ ++ static SourceLocationId cachedHash = 0; ++ static const char *hashedFilename = 0; ++ // Each file #included in a translation unit has its own __FILE__ value. ++ // Keep the cache fresh (and the result correct). ++ if (hashedFilename != fileName) { // cheap pointer comparison ++ hashedFilename = fileName; ++ cachedHash = hasher(fileName); ++ } ++ return cachedHash; ++} ++ ++#endif ++ +diff --git a/src/base/Makefile.am b/src/base/Makefile.am +index 55ee292..9ffb47a 100644 +--- a/src/base/Makefile.am ++++ b/src/base/Makefile.am +@@ -22,6 +22,8 @@ libbase_la_SOURCES = \ + CharacterSet.h \ + CharacterSet.cc \ + CbcPointer.h \ ++ Here.h \ ++ Here.cc \ + InstanceId.h \ + Lock.h \ + LruMap.h \ +diff --git a/src/base/Makefile.in b/src/base/Makefile.in +index a282ae7..fc90ddb 100644 +--- a/src/base/Makefile.in ++++ b/src/base/Makefile.in +@@ -160,7 +160,7 @@ CONFIG_CLEAN_VPATH_FILES = + LTLIBRARIES = $(noinst_LTLIBRARIES) + libbase_la_LIBADD = + am_libbase_la_OBJECTS = AsyncCall.lo AsyncJob.lo AsyncCallQueue.lo \ +- CharacterSet.lo RunnersRegistry.lo TextException.lo ++ CharacterSet.lo RunnersRegistry.lo TextException.lo Here.lo + libbase_la_OBJECTS = $(am_libbase_la_OBJECTS) + AM_V_lt = $(am__v_lt_@AM_V@) + am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +@@ -705,6 +705,8 @@ libbase_la_SOURCES = \ + CharacterSet.h \ + CharacterSet.cc \ + CbcPointer.h \ ++ Here.h \ ++ Here.cc \ + InstanceId.h \ + Lock.h \ + LruMap.h \ +diff --git a/src/base/TextException.cc b/src/base/TextException.cc +index ef843b1..06528e2 100644 +--- a/src/base/TextException.cc ++++ b/src/base/TextException.cc +@@ -93,3 +93,22 @@ void Throw(const char *message, const char *fileName, int lineNo, unsigned int i + throw TextException(message, fileName, lineNo, id); + } + ++std::ostream & ++CurrentException(std::ostream &os) ++{ ++ if (std::current_exception()) { ++ try { ++ throw; // re-throw to recognize the exception type ++ } ++ catch (const std::exception &ex) { ++ os << ex.what(); ++ } ++ catch (...) { ++ os << "[unknown exception type]"; ++ } ++ } else { ++ os << "[no active exception]"; ++ } ++ return os; ++} ++ +diff --git a/src/base/TextException.h b/src/base/TextException.h +index 9e97acd..2ee5cc8 100644 +--- a/src/base/TextException.h ++++ b/src/base/TextException.h +@@ -12,6 +12,7 @@ + // Origin: xstd/TextException + + #include ++#include "base/Here.h" + + static unsigned int FileNameHashCached(const char *fname); + +@@ -49,6 +50,10 @@ protected: + friend unsigned int FileNameHashCached(const char *fname); + }; + ++/// prints active (i.e., thrown but not yet handled) exception ++std::ostream &CurrentException(std::ostream &); ++ ++ + //inline + //ostream &operator <<(ostream &os, const TextException &exx) { + // return exx.print(os); +@@ -89,5 +94,16 @@ void Throw(const char *message, const char *fileName, int lineNo, unsigned int i + (FileNameHashCached(__FILE__)<<14) | (__LINE__ & 0x3FFF))) + #endif + ++/// Reports and swallows all exceptions to prevent compiler warnings and runtime ++/// errors related to throwing class destructors. Should be used for most dtors. ++#define SWALLOW_EXCEPTIONS(code) \ ++ try { \ ++ code \ ++ } catch (...) { \ ++ debugs(0, DBG_IMPORTANT, "BUG: ignoring exception;\n" << \ ++ " bug location: " << Here() << "\n" << \ ++ " ignored exception: " << CurrentException); \ ++ } ++ + #endif /* SQUID__TEXTEXCEPTION_H */ + +diff --git a/src/urn.cc b/src/urn.cc +index bc7e67b..487e4f2 100644 +--- a/src/urn.cc ++++ b/src/urn.cc +@@ -9,6 +9,7 @@ + /* DEBUG: section 52 URN Parsing */ + + #include "squid.h" ++#include "base/TextException.h" + #include "cbdata.h" + #include "errorpage.h" + #include "FwdState.h" +@@ -79,7 +80,18 @@ CBDATA_CLASS_INIT(UrnState); + + UrnState::~UrnState() + { +- safe_free(urlres); ++ SWALLOW_EXCEPTIONS({ ++ if (urlres_e) { ++ if (sc) ++ storeUnregister(sc, urlres_e, this); ++ urlres_e->unlock("~UrnState+res"); ++ } ++ ++ if (entry) ++ entry->unlock("~UrnState+prime"); ++ ++ safe_free(urlres); ++ }); + } + + static url_entry * +@@ -260,14 +272,6 @@ url_entry_sort(const void *A, const void *B) + return u1->rtt - u2->rtt; + } + +-static void +-urnHandleReplyError(UrnState *urnState, StoreEntry *urlres_e) +-{ +- urlres_e->unlock("urnHandleReplyError+res"); +- urnState->entry->unlock("urnHandleReplyError+prime"); +- delete urnState; +-} +- + /* TODO: use the clientStream support for this */ + static void + urnHandleReply(void *data, StoreIOBuffer result) +@@ -290,8 +294,8 @@ urnHandleReply(void *data, StoreIOBuffer result) + + debugs(52, 3, "urnHandleReply: Called with size=" << result.length << "."); + +- if (EBIT_TEST(urlres_e->flags, ENTRY_ABORTED) || result.length == 0 || result.flags.error) { +- urnHandleReplyError(urnState, urlres_e); ++ if (EBIT_TEST(urlres_e->flags, ENTRY_ABORTED) || result.flags.error) { ++ delete urnState; + return; + } + +@@ -300,15 +304,15 @@ urnHandleReply(void *data, StoreIOBuffer result) + + /* Handle reqofs being bigger than normal */ + if (urnState->reqofs >= URN_REQBUF_SZ) { +- urnHandleReplyError(urnState, urlres_e); ++ delete urnState; + return; + } + + /* If we haven't received the entire object (urn), copy more */ +- if (urlres_e->store_status == STORE_PENDING && +- urnState->reqofs < URN_REQBUF_SZ) { ++ if (urlres_e->store_status == STORE_PENDING) { ++ Must(result.length > 0); // zero length ought to imply STORE_OK + tempBuffer.offset = urnState->reqofs; +- tempBuffer.length = URN_REQBUF_SZ; ++ tempBuffer.length = URN_REQBUF_SZ - urnState->reqofs; + tempBuffer.data = urnState->reqbuf + urnState->reqofs; + storeClientCopy(urnState->sc, urlres_e, + tempBuffer, +@@ -322,7 +326,7 @@ urnHandleReply(void *data, StoreIOBuffer result) + + if (0 == k) { + debugs(52, DBG_IMPORTANT, "urnHandleReply: didn't find end-of-headers for " << e->url() ); +- urnHandleReplyError(urnState, urlres_e); ++ delete urnState; + return; + } + +@@ -338,7 +342,7 @@ urnHandleReply(void *data, StoreIOBuffer result) + err->url = xstrdup(e->url()); + errorAppendEntry(e, err); + delete rep; +- urnHandleReplyError(urnState, urlres_e); ++ delete urnState; + return; + } + +@@ -359,7 +363,7 @@ urnHandleReply(void *data, StoreIOBuffer result) + err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw()); + err->url = xstrdup(e->url()); + errorAppendEntry(e, err); +- urnHandleReplyError(urnState, urlres_e); ++ delete urnState; + return; + } + +@@ -416,10 +420,7 @@ urnHandleReply(void *data, StoreIOBuffer result) + } + + safe_free(urls); +- /* mb was absorbed in httpBodySet call, so we must not clean it */ +- storeUnregister(urnState->sc, urlres_e, urnState); +- +- urnHandleReplyError(urnState, urlres_e); ++ delete urnState; + } + + static url_entry * diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12528.patch squid3-3.5.27/debian/patches/CVE-2019-12528.patch --- squid3-3.5.27/debian/patches/CVE-2019-12528.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12528.patch 2020-02-19 17:49:56.000000000 +0000 @@ -0,0 +1,169 @@ +commit 8cdb18ca1829a0b7faa1c9e472604ed0e7e105ac +Author: Christos Tsantilas +Date: 2019-12-20 07:29:58 +0000 + + Fix FTP buffers handling (#521) + + Fix the parsing of the received listing from FTP services. + Also relaxed size/filename grammar used for DOS listings: Tolerate + multiple spaces between the size and the filename. + + This is a Measurement Factory project + +diff --git a/src/clients/FtpGateway.cc b/src/clients/FtpGateway.cc +index 2e50ad9..5bac13b 100644 +--- a/src/clients/FtpGateway.cc ++++ b/src/clients/FtpGateway.cc +@@ -539,8 +539,10 @@ ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags) + { + ftpListParts *p = NULL; + char *t = NULL; +- const char *ct = NULL; +- char *tokens[MAX_TOKENS]; ++ struct FtpLineToken { ++ char *token = NULL; ///< token image copied from the received line ++ size_t pos = 0; ///< token offset on the received line ++ } tokens[MAX_TOKENS]; + int i; + int n_tokens; + static char tbuf[128]; +@@ -581,7 +583,8 @@ ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags) + } + + for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space)) { +- tokens[n_tokens] = xstrdup(t); ++ tokens[n_tokens].token = xstrdup(t); ++ tokens[n_tokens].pos = t - xbuf; + ++n_tokens; + } + +@@ -589,10 +592,10 @@ ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags) + + /* locate the Month field */ + for (i = 3; i < n_tokens - 2; ++i) { +- char *size = tokens[i - 1]; +- char *month = tokens[i]; +- char *day = tokens[i + 1]; +- char *year = tokens[i + 2]; ++ const char *size = tokens[i - 1].token; ++ char *month = tokens[i].token; ++ char *day = tokens[i + 1].token; ++ char *year = tokens[i + 2].token; + + if (!is_month(month)) + continue; +@@ -606,23 +609,27 @@ ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags) + if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */ + continue; + +- snprintf(tbuf, 128, "%s %2s %5s", +- month, day, year); ++ const char *copyFrom = buf + tokens[i].pos; + +- if (!strstr(buf, tbuf)) +- snprintf(tbuf, 128, "%s %2s %-5s", +- month, day, year); ++ // "MMM DD [ YYYY|hh:mm]" with at most two spaces between DD and YYYY ++ int dateSize = snprintf(tbuf, sizeof(tbuf), "%s %2s %5s", month, day, year); ++ bool isTypeA = (dateSize == 12) && (strncmp(copyFrom, tbuf, dateSize) == 0); + +- char const *copyFrom = NULL; ++ // "MMM DD [YYYY|hh:mm]" with one space between DD and YYYY ++ dateSize = snprintf(tbuf, sizeof(tbuf), "%s %2s %-5s", month, day, year); ++ bool isTypeB = (dateSize == 12 || dateSize == 11) && (strncmp(copyFrom, tbuf, dateSize) == 0); + +- if ((copyFrom = strstr(buf, tbuf))) { +- p->type = *tokens[0]; ++ // TODO: replace isTypeA and isTypeB with a regex. ++ if (isTypeA || isTypeB) { ++ p->type = *tokens[0].token; + p->size = strtoll(size, NULL, 10); ++ const int finalDateSize = snprintf(tbuf, sizeof(tbuf), "%s %2s %5s", month, day, year); ++ assert(finalDateSize >= 0); + p->date = xstrdup(tbuf); + ++ // point after tokens[i+2] : ++ copyFrom = buf + tokens[i + 2].pos + strlen(tokens[i + 2].token); + if (flags.skip_whitespace) { +- copyFrom += strlen(tbuf); +- + while (strchr(w_space, *copyFrom)) + ++copyFrom; + } else { +@@ -634,7 +641,6 @@ ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags) + * Assuming a single space between date and filename + * suggested by: Nathan.Bailey@cc.monash.edu.au and + * Mike Battersby */ +- copyFrom += strlen(tbuf); + if (strchr(w_space, *copyFrom)) + ++copyFrom; + } +@@ -654,45 +660,36 @@ ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags) + + /* try it as a DOS listing, 04-05-70 09:33PM ... */ + if (n_tokens > 3 && +- regexec(&scan_ftp_dosdate, tokens[0], 0, NULL, 0) == 0 && +- regexec(&scan_ftp_dostime, tokens[1], 0, NULL, 0) == 0) { +- if (!strcasecmp(tokens[2], "")) { ++ regexec(&scan_ftp_dosdate, tokens[0].token, 0, NULL, 0) == 0 && ++ regexec(&scan_ftp_dostime, tokens[1].token, 0, NULL, 0) == 0) { ++ if (!strcasecmp(tokens[2].token, "")) { + p->type = 'd'; + } else { + p->type = '-'; +- p->size = strtoll(tokens[2], NULL, 10); ++ p->size = strtoll(tokens[2].token, NULL, 10); + } + +- snprintf(tbuf, 128, "%s %s", tokens[0], tokens[1]); ++ snprintf(tbuf, sizeof(tbuf), "%s %s", tokens[0].token, tokens[1].token); + p->date = xstrdup(tbuf); + + if (p->type == 'd') { +- /* Directory.. name begins with first printable after */ +- ct = strstr(buf, tokens[2]); +- ct += strlen(tokens[2]); +- +- while (xisspace(*ct)) +- ++ct; +- +- if (!*ct) +- ct = NULL; ++ // Directory.. name begins with first printable after ++ // Because of the "n_tokens > 3", the next printable after ++ // is stored at token[3]. No need for more checks here. + } else { +- /* A file. Name begins after size, with a space in between */ +- snprintf(tbuf, 128, " %s %s", tokens[2], tokens[3]); +- ct = strstr(buf, tbuf); +- +- if (ct) { +- ct += strlen(tokens[2]) + 2; +- } ++ // A file. Name begins after size, with a space in between. ++ // Also a space should exist before size. ++ // But there is not needed to be very strict with spaces. ++ // The name is stored at token[3], take it from here. + } + +- p->name = xstrdup(ct ? ct : tokens[3]); ++ p->name = xstrdup(tokens[3].token); + goto found; + } + + /* Try EPLF format; carson@lehman.com */ + if (buf[0] == '+') { +- ct = buf + 1; ++ const char *ct = buf + 1; + p->type = 0; + + while (ct && *ct) { +@@ -763,7 +760,7 @@ blank: + found: + + for (i = 0; i < n_tokens; ++i) +- xfree(tokens[i]); ++ xfree(tokens[i].token); + + if (!p->name) + ftpListPartsFree(&p); /* cleanup */ diff -Nru squid3-3.5.27/debian/patches/CVE-2019-12529.patch squid3-3.5.27/debian/patches/CVE-2019-12529.patch --- squid3-3.5.27/debian/patches/CVE-2019-12529.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-12529.patch 2020-08-25 17:12:13.000000000 +0000 @@ -0,0 +1,230 @@ +Backport of: + +From dd46b5417809647f561d8a5e0e74c3aacd235258 Mon Sep 17 00:00:00 2001 +From: Amos Jeffries +Date: Tue, 21 May 2019 21:31:31 +0000 +Subject: [PATCH] Replace uudecode with libnettle base64 decoder (#406) + +Since RFC 7235 updated the HTTP Authentication credentials token +to the token68 characterset it is possible that characters +uudecode cannot cope with are received. + +The Nettle decoder better handles characters which are valid but +not to be used for Basic auth token. +--- + include/uudecode.h | 21 ------------ + lib/Makefile.am | 3 +- + lib/uudecode.c | 73 ---------------------------------------- + src/auth/basic/Config.cc | 20 ++++++++--- + 4 files changed, 17 insertions(+), 100 deletions(-) + delete mode 100644 include/uudecode.h + delete mode 100644 lib/uudecode.c + +Index: squid3-3.5.27/src/auth/basic/Config.cc +=================================================================== +--- squid3-3.5.27.orig/src/auth/basic/Config.cc 2019-07-16 13:09:39.745471168 -0400 ++++ squid3-3.5.27/src/auth/basic/Config.cc 2019-07-16 13:34:24.375242593 -0400 +@@ -19,6 +19,7 @@ + #include "auth/basic/UserRequest.h" + #include "auth/Gadgets.h" + #include "auth/State.h" ++#include "base64.h" + #include "cache_cf.h" + #include "charset.h" + #include "helper.h" +@@ -28,7 +29,6 @@ + #include "rfc1738.h" + #include "SquidTime.h" + #include "Store.h" +-#include "uudecode.h" + #include "wordlist.h" + + /* Basic Scheme */ +@@ -166,21 +166,25 @@ Auth::Basic::Config::decodeCleartext(con + // XXX: really? is the \n actually still there? does the header parse not drop it? + char *eek = xstrdup(proxy_auth); + strtok(eek, "\n"); +- char *cleartext = uudecode(eek); +- safe_free(eek); + +- if (cleartext) { +- /* +- * Don't allow NL or CR in the credentials. +- * Oezguer Kesim +- */ +- debugs(29, 9, HERE << "'" << cleartext << "'"); +- +- if (strcspn(cleartext, "\r\n") != strlen(cleartext)) { +- debugs(29, DBG_IMPORTANT, "WARNING: Bad characters in authorization header '" << httpAuthHeader << "'"); +- safe_free(cleartext); +- } ++ const int dstLen = base64_decode_len(eek); ++ char *cleartext = (char*)xmalloc(dstLen+1); ++ ++ const int decodedLen = base64_decode(cleartext, dstLen, eek); ++ cleartext[decodedLen] = '\0'; ++ ++ /* ++ * Don't allow NL or CR in the credentials. ++ * Oezguer Kesim ++ */ ++ debugs(29, 9, HERE << "'" << cleartext << "'"); ++ ++ if (strcspn(cleartext, "\r\n") != strlen(cleartext)) { ++ debugs(29, DBG_IMPORTANT, "WARNING: Bad characters in authorization header '" << httpAuthHeader << "'"); ++ safe_free(cleartext); + } ++ ++ safe_free(eek); + return cleartext; + } + +Index: squid3-3.5.27/include/uudecode.h +=================================================================== +--- squid3-3.5.27.orig/include/uudecode.h 2019-07-16 13:09:39.745471168 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,21 +0,0 @@ +-/* +- * Copyright (C) 1996-2017 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_UUDECODE_H +-#define _SQUID_UUDECODE_H +- +-#ifdef __cplusplus +-extern "C" +-#else +-extern +-#endif +- +-char *uudecode(const char *); +- +-#endif /* _SQUID_UUDECODE_H */ +- +Index: squid3-3.5.27/lib/uudecode.c +=================================================================== +--- squid3-3.5.27.orig/lib/uudecode.c 2019-07-16 13:09:39.745471168 -0400 ++++ /dev/null 1970-01-01 00:00:00.000000000 +0000 +@@ -1,73 +0,0 @@ +-/* +- * Copyright (C) 1996-2017 The Squid Software Foundation and contributors +- * +- * Squid software is distributed under GPLv2+ license and includes +- * contributions from numerous individuals and organizations. +- * Please see the COPYING and CONTRIBUTORS files for details. +- */ +- +-#include "squid.h" +-#include "uudecode.h" +- +-/* aaaack but it's fast and const should make it shared text page. */ +-const int pr2six[256] = { +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, +- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +- 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, +- 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +-}; +- +-char * +-uudecode(const char *bufcoded) +-{ +- int nbytesdecoded; +- const unsigned char *bufin; +- char *bufplain; +- unsigned char *bufout; +- int nprbytes; +- +- /* Strip leading whitespace. */ +- +- while (*bufcoded == ' ' || *bufcoded == '\t') +- bufcoded++; +- +- /* Figure out how many characters are in the input buffer. +- * Allocate this many from the per-transaction pool for the result. +- */ +- bufin = (const unsigned char *) bufcoded; +- while (pr2six[*(bufin++)] <= 63); +- nprbytes = (const char *) bufin - bufcoded - 1; +- nbytesdecoded = ((nprbytes + 3) / 4) * 3; +- +- bufplain = xmalloc(nbytesdecoded + 1); +- bufout = (unsigned char *) bufplain; +- bufin = (const unsigned char *) bufcoded; +- +- while (nprbytes > 0) { +- *(bufout++) = +- (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); +- *(bufout++) = +- (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); +- *(bufout++) = +- (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); +- bufin += 4; +- nprbytes -= 4; +- } +- +- if (nprbytes & 03) { +- if (pr2six[bufin[-2]] > 63) +- nbytesdecoded -= 2; +- else +- nbytesdecoded -= 1; +- } +- bufplain[nbytesdecoded] = '\0'; +- return bufplain; +-} +- +Index: squid3-3.5.27/lib/Makefile.am +=================================================================== +--- squid3-3.5.27.orig/lib/Makefile.am 2019-07-16 13:09:39.745471168 -0400 ++++ squid3-3.5.27/lib/Makefile.am 2019-07-16 13:09:39.745471168 -0400 +@@ -58,8 +58,7 @@ libmiscencoding_la_SOURCES = \ + html_quote.c \ + md5.c \ + rfc1738.c \ +- rfc2617.c \ +- uudecode.c ++ rfc2617.c + + libmisccontainers_la_SOURCES = \ + hash.cc +Index: squid3-3.5.27/lib/Makefile.in +=================================================================== +--- squid3-3.5.27.orig/lib/Makefile.in 2019-07-16 13:09:39.745471168 -0400 ++++ squid3-3.5.27/lib/Makefile.in 2019-07-16 13:09:39.745471168 -0400 +@@ -181,7 +181,7 @@ am__v_lt_0 = --silent + am__v_lt_1 = + libmiscencoding_la_LIBADD = + am_libmiscencoding_la_OBJECTS = base64.lo charset.lo html_quote.lo \ +- md5.lo rfc1738.lo rfc2617.lo uudecode.lo ++ md5.lo rfc1738.lo rfc2617.lo + libmiscencoding_la_OBJECTS = $(am_libmiscencoding_la_OBJECTS) + libmiscutil_la_LIBADD = + am_libmiscutil_la_OBJECTS = MemPool.lo MemPoolChunked.lo \ +@@ -810,8 +810,7 @@ libmiscencoding_la_SOURCES = \ + html_quote.c \ + md5.c \ + rfc1738.c \ +- rfc2617.c \ +- uudecode.c ++ rfc2617.c + + libmisccontainers_la_SOURCES = \ + hash.cc +@@ -973,7 +972,6 @@ distclean-compile: + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sspwin32.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stub_memaccount.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Plo@am__quote@ +-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uudecode.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xusleep.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/testRFC1035.Po@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/testRFC1738.Po@am__quote@ diff -Nru squid3-3.5.27/debian/patches/CVE-2019-13345.patch squid3-3.5.27/debian/patches/CVE-2019-13345.patch --- squid3-3.5.27/debian/patches/CVE-2019-13345.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-13345.patch 2019-07-11 16:59:20.000000000 +0000 @@ -0,0 +1,76 @@ +From 5730c2b5cb56e7639dc423dd62651c8736a54e35 Mon Sep 17 00:00:00 2001 +From: Amos Jeffries +Date: Fri, 5 Jul 2019 03:17:26 +0000 +Subject: [PATCH] Bug 4957: Multiple XSS issues in cachemgr.cgi (#429) + +The cachemgr.cgi web module of the squid proxy is vulnerable +to XSS issue. The vulnerable parameters "user_name" and "auth" +have insufficient sanitization in place. +--- + tools/cachemgr.cc | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/tools/cachemgr.cc b/tools/cachemgr.cc +index 0c6753843b..9aecaa9193 100644 +--- a/tools/cachemgr.cc ++++ b/tools/cachemgr.cc +@@ -354,7 +354,7 @@ auth_html(const char *host, int port, const char *user_name) + + printf("Manager name:\n", user_name); ++ printf("size=\"30\" VALUE=\"%s\">\n", rfc1738_escape(user_name)); + + printf("Password:hostname, + req->port, +- safe_str(req->user_name), ++ rfc1738_escape(safe_str(req->user_name)), + action, + safe_str(req->pub_auth)); + return url; +@@ -1073,8 +1073,8 @@ make_pub_auth(cachemgr_request * req) + const int bufLen = snprintf(buf, sizeof(buf), "%s|%d|%s|%s", + req->hostname, + (int) now, +- req->user_name ? req->user_name : "", +- req->passwd); ++ rfc1738_escape(safe_str(req->user_name)), ++ rfc1738_escape(req->passwd)); + debug("cmgr: pre-encoded for pub: %s\n", buf); + + const int encodedLen = base64_encode_len(bufLen); +@@ -1089,8 +1089,6 @@ decode_pub_auth(cachemgr_request * req) + char *buf; + const char *host_name; + const char *time_str; +- const char *user_name; +- const char *passwd; + + debug("cmgr: decoding pub: '%s'\n", safe_str(req->pub_auth)); + safe_free(req->passwd); +@@ -1119,17 +1117,21 @@ decode_pub_auth(cachemgr_request * req) + + debug("cmgr: decoded time: '%s' (now: %d)\n", time_str, (int) now); + ++ char *user_name; + if ((user_name = strtok(NULL, "|")) == NULL) { + xfree(buf); + return; + } ++ rfc1738_unescape(user_name); + + debug("cmgr: decoded uname: '%s'\n", user_name); + ++ char *passwd; + if ((passwd = strtok(NULL, "|")) == NULL) { + xfree(buf); + return; + } ++ rfc1738_unescape(passwd); + + debug("cmgr: decoded passwd: '%s'\n", passwd); + diff -Nru squid3-3.5.27/debian/patches/CVE-2019-18677.patch squid3-3.5.27/debian/patches/CVE-2019-18677.patch --- squid3-3.5.27/debian/patches/CVE-2019-18677.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-18677.patch 2019-11-19 19:38:54.000000000 +0000 @@ -0,0 +1,89 @@ +commit e5f1813a674848dde570f7920873e1071f96e0b4 +Author: Amos Jeffries +Date: 2019-07-12 03:08:00 +0000 + + Prevent truncation for large origin-relative domains (#427) + + The domain in a URL must obey hostname length restrictions after + append_domain is applied, not just MAX_URL for the normalized + absolute-URL. + +diff --git a/src/URL.h b/src/URL.h +index d8ad3f2..c17928a 100644 +--- a/src/URL.h ++++ b/src/URL.h +@@ -115,6 +115,7 @@ enum MatchDomainNameFlags { + int matchDomainName(const char *host, const char *domain, uint flags = mdnNone); + int urlCheckRequest(const HttpRequest *); + int urlDefaultPort(AnyP::ProtocolType p); ++bool urlAppendDomain(char *host); + char *urlHostname(const char *url); + void urlExtMethodConfigure(void); + +diff --git a/src/internal.cc b/src/internal.cc +index 772255e..693aa1f 100644 +--- a/src/internal.cc ++++ b/src/internal.cc +@@ -92,13 +92,9 @@ internalRemoteUri(const char *host, unsigned short port, const char *dir, const + + /* + * append the domain in order to mirror the requests with appended +- * domains ++ * domains. If that fails, just use the hostname anyway. + */ +- +- /* For IPv6 addresses also check for a colon */ +- if (Config.appendDomain && !strchr(lc_host, '.') && !strchr(lc_host, ':')) +- strncat(lc_host, Config.appendDomain, SQUIDHOSTNAMELEN - +- strlen(lc_host) - 1); ++ (void)urlAppendDomain(lc_host); + + /* build uri in mb */ + static MemBuf mb; +diff --git a/src/url.cc b/src/url.cc +index 92aea36..2e54bef 100644 +--- a/src/url.cc ++++ b/src/url.cc +@@ -175,6 +175,30 @@ urlDefaultPort(AnyP::ProtocolType p) + } + } + ++/** ++ * Appends configured append_domain to hostname, assuming ++ * the given buffer is at least SQUIDHOSTNAMELEN bytes long, ++ * and that the host FQDN is not a 'dotless' TLD. ++ * ++ * \returns false if and only if there is not enough space to append ++ */ ++bool ++urlAppendDomain(char *host) ++{ ++ /* For IPv4 addresses check for a dot */ ++ /* For IPv6 addresses also check for a colon */ ++ if (Config.appendDomain && !strchr(host, '.') && !strchr(host, ':')) { ++ const uint64_t dlen = strlen(host); ++ const uint64_t want = dlen + Config.appendDomainLen; ++ if (want > SQUIDHOSTNAMELEN - 1) { ++ debugs(23, 2, "URL domain too large (" << dlen << " bytes)"); ++ return false; ++ } ++ strncat(host, Config.appendDomain, SQUIDHOSTNAMELEN - dlen - 1); ++ } ++ return true; ++} ++ + /* + * Parse a URI/URL. + * +@@ -375,9 +399,8 @@ urlParse(const HttpRequestMethod& method, char *url, HttpRequest *request) + return NULL; + } + +- /* For IPV6 addresses also check for a colon */ +- if (Config.appendDomain && !strchr(host, '.') && !strchr(host, ':')) +- strncat(host, Config.appendDomain, SQUIDHOSTNAMELEN - strlen(host) - 1); ++ if (!urlAppendDomain(host)) ++ return NULL; + + /* remove trailing dots from hostnames */ + while ((l = strlen(host)) > 0 && host[--l] == '.') diff -Nru squid3-3.5.27/debian/patches/CVE-2019-18678.patch squid3-3.5.27/debian/patches/CVE-2019-18678.patch --- squid3-3.5.27/debian/patches/CVE-2019-18678.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-18678.patch 2020-08-25 17:12:13.000000000 +0000 @@ -0,0 +1,116 @@ +Backport of: + +commit 671ba97abe929156dc4c717ee52ad22fba0f7443 +Author: Amos Jeffries +Date: 2019-09-11 02:52:52 +0000 + + RFC 7230: server MUST reject messages with BWS after field-name (#445) + + Obey the RFC requirement to reject HTTP requests with whitespace + between field-name and the colon delimiter. Rejection is + critical in the presence of broken HTTP agents that mishandle + malformed messages. + + Also obey requirement to always strip such whitespace from HTTP + response messages. The relaxed parser is no longer necessary for + this response change. + + For now non-HTTP protocols retain the old behaviour of removal + only when using the relaxed parser. + +--- a/src/HttpHeader.cc ++++ b/src/HttpHeader.cc +@@ -727,14 +727,11 @@ HttpHeader::parse(const char *header_sta + break; /* terminating blank line */ + } + +- if ((e = HttpHeaderEntry::parse(field_start, field_end)) == NULL) { ++ if ((e = HttpHeaderEntry::parse(field_start, field_end, owner)) == NULL) { + debugs(55, warnOnError, "WARNING: unparseable HTTP header field {" << + getStringPrefix(field_start, field_end) << "}"); + debugs(55, warnOnError, " in {" << getStringPrefix(header_start, header_end) << "}"); + +- if (Config.onoff.relaxed_header_parser) +- continue; +- + PROF_stop(HttpHeaderParse); + return reset(); + } +@@ -1665,7 +1662,7 @@ HttpHeaderEntry::~HttpHeaderEntry() + + /* parses and inits header entry, returns true/false */ + HttpHeaderEntry * +-HttpHeaderEntry::parse(const char *field_start, const char *field_end) ++HttpHeaderEntry::parse(const char *field_start, const char *field_end, const http_hdr_owner_type msgType) + { + /* note: name_start == field_start */ + const char *name_end = (const char *)memchr(field_start, ':', field_end - field_start); +@@ -1682,19 +1679,41 @@ HttpHeaderEntry::parse(const char *field + + if (name_len > 65534) { + /* String must be LESS THAN 64K and it adds a terminating NULL */ +- debugs(55, DBG_IMPORTANT, "WARNING: ignoring header name of " << name_len << " bytes"); ++ // TODO: update this to show proper name_len in Raw markup, but not print all that ++ debugs(55, 2, "ignoring huge header field (" << Raw("field_start", field_start, 100) << "...)"); + return NULL; + } + +- if (Config.onoff.relaxed_header_parser && xisspace(field_start[name_len - 1])) { ++ /* ++ * RFC 7230 section 3.2.4: ++ * "No whitespace is allowed between the header field-name and colon. ++ * ... ++ * A server MUST reject any received request message that contains ++ * whitespace between a header field-name and colon with a response code ++ * of 400 (Bad Request). A proxy MUST remove any such whitespace from a ++ * response message before forwarding the message downstream." ++ */ ++ if (xisspace(field_start[name_len - 1])) { ++ ++ if (msgType == hoRequest) ++ return nullptr; ++ ++ // for now, also let relaxed parser remove this BWS from any non-HTTP messages ++ const bool stripWhitespace = (msgType == hoReply) || ++ Config.onoff.relaxed_header_parser; ++ if (!stripWhitespace) ++ return nullptr; // reject if we cannot strip ++ + debugs(55, Config.onoff.relaxed_header_parser <= 0 ? 1 : 2, + "NOTICE: Whitespace after header name in '" << getStringPrefix(field_start, field_end) << "'"); + + while (name_len > 0 && xisspace(field_start[name_len - 1])) + --name_len; + +- if (!name_len) ++ if (!name_len) { ++ debugs(55, 2, "found header with only whitespace for name"); + return NULL; ++ } + } + + /* now we know we can parse it */ +@@ -1728,11 +1747,7 @@ HttpHeaderEntry::parse(const char *field + + if (field_end - value_start > 65534) { + /* String must be LESS THAN 64K and it adds a terminating NULL */ +- debugs(55, DBG_IMPORTANT, "WARNING: ignoring '" << name << "' header of " << (field_end - value_start) << " bytes"); +- +- if (id == HDR_OTHER) +- name.clean(); +- ++ debugs(55, 2, "WARNING: found '" << name << "' header of " << (field_end - value_start) << " bytes"); + return NULL; + } + +--- a/src/HttpHeader.h ++++ b/src/HttpHeader.h +@@ -187,7 +187,7 @@ class HttpHeaderEntry + public: + HttpHeaderEntry(http_hdr_type id, const char *name, const char *value); + ~HttpHeaderEntry(); +- static HttpHeaderEntry *parse(const char *field_start, const char *field_end); ++ static HttpHeaderEntry *parse(const char *field_start, const char *field_end, const http_hdr_owner_type msgType); + HttpHeaderEntry *clone() const; + void packInto(Packer *p) const; + int getInt() const; diff -Nru squid3-3.5.27/debian/patches/CVE-2019-18860.patch squid3-3.5.27/debian/patches/CVE-2019-18860.patch --- squid3-3.5.27/debian/patches/CVE-2019-18860.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2019-18860.patch 2020-05-07 14:03:32.000000000 +0000 @@ -0,0 +1,105 @@ +Backport of: + +From 5a90b4ce64c346ba7f317a278ba601091d9de076 Mon Sep 17 00:00:00 2001 +From: aaron-costello <56684862+aaron-costello@users.noreply.github.com> +Date: Sun, 3 Nov 2019 16:22:22 +0000 +Subject: [PATCH] cachemgr.cgi: Add validation for hostname parameter (#504) + +Prevention of HTML/invalid chars in host param +--- + src/base/CharacterSet.cc | 2 +- + tools/Makefile.am | 8 ++++++-- + tools/cachemgr.cc | 28 +++++++++++++++++++++++++--- + 3 files changed, 32 insertions(+), 6 deletions(-) + +--- a/src/base/CharacterSet.cc ++++ b/src/base/CharacterSet.cc +@@ -7,7 +7,7 @@ + */ + + #include "squid.h" +-#include "CharacterSet.h" ++#include "base/CharacterSet.h" + + #include + #include +--- a/tools/Makefile.am ++++ b/tools/Makefile.am +@@ -34,6 +34,9 @@ test_tools.cc: $(top_srcdir)/test-suite/ + stub_debug.cc: $(top_srcdir)/src/tests/stub_debug.cc + cp $(top_srcdir)/src/tests/stub_debug.cc . + ++CharacterSet.cc: $(top_srcdir)/src/base/CharacterSet.cc ++ cp $(top_srcdir)/src/base/CharacterSet.cc $@ ++ + MemBuf.cc: $(top_srcdir)/src/MemBuf.cc + cp $(top_srcdir)/src/MemBuf.cc $@ + +@@ -51,7 +54,7 @@ stub_mem.cc: $(top_srcdir)/src/tests/stu + # globals.cc is needed by test_tools.cc. + # Neither of these should be disted from here. + TESTSOURCES= test_tools.cc +-CLEANFILES += test_tools.cc MemBuf.cc stub_debug.cc time.cc stub_cbdata.cc stub_mem.cc ++CLEANFILES += test_tools.cc CharacterSet.cc MemBuf.cc stub_debug.cc time.cc stub_cbdata.cc stub_mem.cc + + ## ##### helper-mux ##### + +@@ -69,6 +72,7 @@ DEFAULT_CACHEMGR_CONFIG = $(sysconfdir)/ + libexec_PROGRAMS = cachemgr$(CGIEXT) + + cachemgr__CGIEXT__SOURCES = cachemgr.cc \ ++ CharacterSet.cc \ + MemBuf.cc \ + stub_cbdata.cc \ + stub_debug.cc \ +--- a/tools/cachemgr.cc ++++ b/tools/cachemgr.cc +@@ -8,6 +8,7 @@ + + #include "squid.h" + #include "base64.h" ++#include "base/CharacterSet.h" + #include "getfullhostname.h" + #include "html_quote.h" + #include "ip/Address.h" +@@ -215,6 +216,21 @@ xstrtok(char **str, char del) + return ""; + } + ++bool ++hostname_check(const char *uri) ++{ ++ static CharacterSet hostChars = CharacterSet("host",".:[]_") + ++ CharacterSet::ALPHA + CharacterSet::DIGIT; ++ ++ const auto limit = strlen(uri); ++ for (size_t i = 0; i < limit; i++) { ++ if (!hostChars[uri[i]]) { ++ return false; ++ } ++ } ++ return true; ++} ++ + static void + print_trailer(void) + { +@@ -806,9 +822,15 @@ process_request(cachemgr_request * req) + } else if ((S = req->hostname)) + (void) 0; + else { +- snprintf(buf, sizeof(buf), "Unknown host: %s\n", req->hostname); +- error_html(buf); +- return 1; ++ if (hostname_check(req->hostname)) { ++ snprintf(buf, sizeof(buf), "Unknown Host: %s\n", req->hostname); ++ error_html(buf); ++ return 1; ++ } else { ++ snprintf(buf, sizeof(buf), "%s\n", "Invalid Hostname"); ++ error_html(buf); ++ return 1; ++ } + } + + S.port(req->port); diff -Nru squid3-3.5.27/debian/patches/CVE-2020-11945.patch squid3-3.5.27/debian/patches/CVE-2020-11945.patch --- squid3-3.5.27/debian/patches/CVE-2020-11945.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2020-11945.patch 2020-05-07 14:03:27.000000000 +0000 @@ -0,0 +1,59 @@ +commit eeebf0f37a72a2de08348e85ae34b02c34e9a811 +Author: desbma-s1n <62935004+desbma-s1n@users.noreply.github.com> +Date: 2020-04-02 11:16:45 +0000 + + Fix auth digest refcount integer overflow (#585) + + This fixes a possible overflow of the nonce reference counter in the + digest authentication scheme, found by security researchers + @synacktiv. + + It changes `references` to be an 64 bits unsigned integer. This makes + overflowing the counter impossible in practice. + +--- a/src/auth/digest/Config.cc ++++ b/src/auth/digest/Config.cc +@@ -85,9 +85,6 @@ static void authenticateDigestNonceDelet + static void authenticateDigestNonceSetup(void); + static void authDigestNonceEncode(digest_nonce_h * nonce); + static void authDigestNonceLink(digest_nonce_h * nonce); +-#if NOT_USED +-static int authDigestNonceLinks(digest_nonce_h * nonce); +-#endif + static void authDigestNonceUserUnlink(digest_nonce_h * nonce); + + static void +@@ -276,21 +273,10 @@ authDigestNonceLink(digest_nonce_h * non + { + assert(nonce != NULL); + ++nonce->references; ++ assert(nonce->references != 0); // no overflows + debugs(29, 9, "nonce '" << nonce << "' now at '" << nonce->references << "'."); + } + +-#if NOT_USED +-static int +-authDigestNonceLinks(digest_nonce_h * nonce) +-{ +- if (!nonce) +- return -1; +- +- return nonce->references; +-} +- +-#endif +- + void + authDigestNonceUnlink(digest_nonce_h * nonce) + { +--- a/src/auth/digest/Config.h ++++ b/src/auth/digest/Config.h +@@ -42,7 +42,7 @@ struct _digest_nonce_h : public hash_lin + /* number of uses we've seen of this nonce */ + unsigned long nc; + /* reference count */ +- short references; ++ uint64_t references; + /* the auth_user this nonce has been tied to */ + Auth::Digest::User *user; + /* has this nonce been invalidated ? */ diff -Nru squid3-3.5.27/debian/patches/CVE-2020-84xx-1.patch squid3-3.5.27/debian/patches/CVE-2020-84xx-1.patch --- squid3-3.5.27/debian/patches/CVE-2020-84xx-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2020-84xx-1.patch 2020-02-19 17:50:07.000000000 +0000 @@ -0,0 +1,50 @@ +commit 8e657e835965c3a011375feaa0359921c5b3e2dd (refs/remotes/origin/v3.5) +Author: Amos Jeffries +Date: 2019-08-13 13:50:06 +0000 + + Ignore malformed Host header in intercept and reverse proxy mode (#456) + +--- a/src/client_side.cc ++++ b/src/client_side.cc +@@ -2043,6 +2043,23 @@ setLogUri(ClientHttpRequest * http, char + } + } + ++static char * ++getHostHeader(const char *req_hdr) ++{ ++ char *host = mime_get_header(req_hdr, "Host"); ++ if (!host) ++ return NULL; ++ ++ // check the header contents are valid ++ for(const char *c = host; *c != '\0'; ++c) { ++ // currently only used for pre-parse Host header, ensure valid domain[:port] or ip[:port] ++ static const CharacterSet hostChars = CharacterSet("host",":[].-_") + CharacterSet::ALPHA + CharacterSet::DIGIT; ++ if (hostChars[*c]) ++ return NULL; // error. line contains character not accepted in Host header ++ } ++ return host; ++} ++ + static void + prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, char *url, const char *req_hdr) + { +@@ -2085,7 +2102,7 @@ prepareAcceleratedURL(ConnStateData * co + + const bool switchedToHttps = conn->switchedToHttps(); + const bool tryHostHeader = vhost || switchedToHttps; +- if (tryHostHeader && (host = mime_get_header(req_hdr, "Host")) != NULL) { ++ if (tryHostHeader && (host = getHostHeader(req_hdr)) != NULL) { + debugs(33, 5, "ACCEL VHOST REWRITE: vhost=" << host << " + vport=" << vport); + char thost[256]; + if (vport > 0) { +@@ -2144,7 +2161,7 @@ prepareTransparentURL(ConnStateData * co + + /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */ + +- if ((host = mime_get_header(req_hdr, "Host")) != NULL) { ++ if ((host = getHostHeader(req_hdr)) != NULL) { + int url_sz = strlen(url) + 32 + Config.appendDomainLen + + strlen(host); + http->uri = (char *)xcalloc(url_sz, 1); diff -Nru squid3-3.5.27/debian/patches/CVE-2020-84xx-2.patch squid3-3.5.27/debian/patches/CVE-2020-84xx-2.patch --- squid3-3.5.27/debian/patches/CVE-2020-84xx-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2020-84xx-2.patch 2020-02-19 17:50:12.000000000 +0000 @@ -0,0 +1,20 @@ +commit d29ac78fd203f55bf391bcb24348ed43ea469d21 +Author: squidadm +Date: 2020-02-02 00:03:24 +1300 + + Fix request URL generation in reverse proxy configurations (#550) + +--- a/src/client_side.cc ++++ b/src/client_side.cc +@@ -2102,9 +2102,9 @@ prepareAcceleratedURL(ConnStateData * co + + const bool switchedToHttps = conn->switchedToHttps(); + const bool tryHostHeader = vhost || switchedToHttps; +- if (tryHostHeader && (host = getHostHeader(req_hdr)) != NULL) { ++ if (tryHostHeader && (host = getHostHeader(req_hdr)) != NULL && strlen(host) >= SQUIDHOSTNAMELEN) { + debugs(33, 5, "ACCEL VHOST REWRITE: vhost=" << host << " + vport=" << vport); +- char thost[256]; ++ char thost[SQUIDHOSTNAMELEN + 6 /* ':' vport */]; + if (vport > 0) { + thost[0] = '\0'; + char *t = NULL; diff -Nru squid3-3.5.27/debian/patches/CVE-2020-84xx-3.patch squid3-3.5.27/debian/patches/CVE-2020-84xx-3.patch --- squid3-3.5.27/debian/patches/CVE-2020-84xx-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2020-84xx-3.patch 2020-02-19 17:50:18.000000000 +0000 @@ -0,0 +1,26 @@ +commit 21d99bdeaed7b2208098d824496da954920ea720 (HEAD, refs/remotes/origin/v3.5, refs/heads/v3.5) +Author: Armin Wolfermann +Date: 2020-02-04 21:15:00 +0100 + + fix security patch + +--- a/src/client_side.cc ++++ b/src/client_side.cc +@@ -2054,7 +2054,7 @@ getHostHeader(const char *req_hdr) + for(const char *c = host; *c != '\0'; ++c) { + // currently only used for pre-parse Host header, ensure valid domain[:port] or ip[:port] + static const CharacterSet hostChars = CharacterSet("host",":[].-_") + CharacterSet::ALPHA + CharacterSet::DIGIT; +- if (hostChars[*c]) ++ if (!hostChars[*c]) + return NULL; // error. line contains character not accepted in Host header + } + return host; +@@ -2102,7 +2102,7 @@ prepareAcceleratedURL(ConnStateData * co + + const bool switchedToHttps = conn->switchedToHttps(); + const bool tryHostHeader = vhost || switchedToHttps; +- if (tryHostHeader && (host = getHostHeader(req_hdr)) != NULL && strlen(host) >= SQUIDHOSTNAMELEN) { ++ if (tryHostHeader && (host = getHostHeader(req_hdr)) != NULL && strlen(host) <= SQUIDHOSTNAMELEN) { + debugs(33, 5, "ACCEL VHOST REWRITE: vhost=" << host << " + vport=" << vport); + char thost[SQUIDHOSTNAMELEN + 6 /* ':' vport */]; + if (vport > 0) { diff -Nru squid3-3.5.27/debian/patches/CVE-2020-8517.patch squid3-3.5.27/debian/patches/CVE-2020-8517.patch --- squid3-3.5.27/debian/patches/CVE-2020-8517.patch 1970-01-01 00:00:00.000000000 +0000 +++ squid3-3.5.27/debian/patches/CVE-2020-8517.patch 2020-02-19 17:50:22.000000000 +0000 @@ -0,0 +1,24 @@ +commit c62d2b43ad4962ea44aa0c5edb4cc99cb83a413d (HEAD, refs/remotes/origin/v3.5, refs/heads/v3.5) +Author: aaron-costello <56684862+aaron-costello@users.noreply.github.com> +Date: 2019-11-22 02:44:29 +0000 + + ext_lm_group_acl: Improved username handling (#512) + +diff --git a/helpers/external_acl/LM_group/ext_lm_group_acl.cc b/helpers/external_acl/LM_group/ext_lm_group_acl.cc +index def9db5..f93fc33 100644 +--- a/helpers/external_acl/LM_group/ext_lm_group_acl.cc ++++ b/helpers/external_acl/LM_group/ext_lm_group_acl.cc +@@ -343,10 +343,10 @@ Valid_Global_Groups(char *UserName, const char **Groups) + break; + } + if (domain_qualify == NULL) { +- strcpy(User, NTDomain); +- strcpy(NTDomain, DefaultDomain); ++ xstrncpy(User, NTDomain, sizeof(User)); ++ xstrncpy(NTDomain, DefaultDomain, sizeof(NTDomain)); + } else { +- strcpy(User, domain_qualify + 1); ++ xstrncpy(User, domain_qualify + 1, sizeof(User)); + domain_qualify[0] = '\0'; + strlwr(NTDomain); + } diff -Nru squid3-3.5.27/debian/patches/series squid3-3.5.27/debian/patches/series --- squid3-3.5.27/debian/patches/series 2018-02-27 11:09:21.000000000 +0000 +++ squid3-3.5.27/debian/patches/series 2020-08-25 17:12:13.000000000 +0000 @@ -4,3 +4,21 @@ 0004-CVE-2018-1000027.patch 90-cf.data.ubuntu.patch 99-ubuntu-ssl-cert-snakeoil.patch +CVE-2018-19132.patch +CVE-2019-13345.patch +CVE-2019-12525.patch +CVE-2019-12529.patch +CVE-2019-12526.patch +CVE-2019-18677.patch +CVE-2019-18678.patch +CVE-2019-12528.patch +CVE-2020-84xx-1.patch +CVE-2020-84xx-2.patch +CVE-2020-84xx-3.patch +CVE-2020-8517.patch +CVE-2019-12519_12521.patch +CVE-2019-18860.patch +CVE-2020-11945.patch +CVE-2019-12520.patch +CVE-2019-12523.patch +CVE-2019-12523-bug965012.patch diff -Nru squid3-3.5.27/debian/usr.sbin.squid squid3-3.5.27/debian/usr.sbin.squid --- squid3-3.5.27/debian/usr.sbin.squid 2018-02-27 11:09:21.000000000 +0000 +++ squid3-3.5.27/debian/usr.sbin.squid 2018-09-19 07:42:38.000000000 +0000 @@ -14,7 +14,7 @@ capability sys_chroot, # allow child processes to run execvp(argv[0], [kidname, ...]) - /usr/sbin/squid ix, + /usr/sbin/squid rix, # pinger network inet raw,