2016-08-23 17:50 GMT+03:00 Alex Rousskov <rouss...@measurement-factory.com>:

> s/request-line/request-line: URI/ for consistency and clarity sake.

> I wonder whether we should make this variable static to avoid repeated
> function calls on a performance-sensitive code path. Same for the old
> "delimiters" variable left inside parseRequestFirstLine(), of course.


2016-08-24 17:30 GMT+03:00 Amos Jeffries <squ...@treenet.co.nz>:

> Option 1, is to use a fixed and slightly generic where message:
> "before protocol version"


Fixed and refreshed the patch.


Eduard.

MUST respond with 414 (URI Too Long) when request target exceeds limits.

Before the fix, Squid simply closed client connection after receiving a
huge URI (or a huge request-line), violating the RFC 7230 MUST. This
happened because a high-level Must(have buffer space) check in
ConnStateData::clientParseRequests() would throw an exception. Now these
problems are detected inside the low-level RequestParser code, where we
can distinguish huge URIs from huge methods.

=== modified file 'src/http/one/RequestParser.cc'
--- src/http/one/RequestParser.cc	2016-05-20 08:28:33 +0000
+++ src/http/one/RequestParser.cc	2016-08-24 23:12:45 +0000
@@ -58,60 +58,65 @@
         while (!buf_.isEmpty() && (buf_[0] == '\n' || (buf_[0] == '\r' && buf_[1] == '\n'))) {
             buf_.consume(1);
         }
     }
 }
 
 /**
  * Attempt to parse the method field out of an HTTP message request-line.
  *
  * Governed by:
  *  RFC 1945 section 5.1
  *  RFC 7230 section 2.6, 3.1 and 3.5
  */
 bool
 Http::One::RequestParser::parseMethodField(Http1::Tokenizer &tok)
 {
     // method field is a sequence of TCHAR.
     // Limit to 32 characters to prevent overly long sequences of non-HTTP
     // being sucked in before mismatch is detected. 32 is itself annoyingly
     // big but there are methods registered by IANA that reach 17 bytes:
     //  http://www.iana.org/assignments/http-methods
     static const size_t maxMethodLength = 32; // TODO: make this configurable?
 
     SBuf methodFound;
     if (!tok.prefix(methodFound, CharacterSet::TCHAR, maxMethodLength)) {
         debugs(33, ErrorLevel(), "invalid request-line: missing or malformed method");
         parseStatusCode = Http::scBadRequest;
         return false;
     }
     method_ = HttpRequestMethod(methodFound);
+
+    static const CharacterSet &delimiters = DelimiterCharacters();
+    if (!skipDelimiter(tok.skipAll(delimiters), "after method"))
+        return false;
+
     return true;
 }
 
 /// the characters which truly are valid within URI
 static const CharacterSet &
 UriValidCharacters()
 {
     /* RFC 3986 section 2:
      * "
      *   A URI is composed from a limited set of characters consisting of
      *   digits, letters, and a few graphic symbols.
      * "
      */
     static const CharacterSet UriChars =
         CharacterSet("URI-Chars","") +
         // RFC 3986 section 2.2 - reserved characters
         CharacterSet("gen-delims", ":/?#[]@") +
         CharacterSet("sub-delims", "!$&'()*+,;=") +
         // RFC 3986 section 2.3 - unreserved characters
         CharacterSet::ALPHA +
         CharacterSet::DIGIT +
         CharacterSet("unreserved", "-._~") +
         // RFC 3986 section 2.1 - percent encoding "%" HEXDIG
         CharacterSet("pct-encoded", "%") +
         CharacterSet::HEXDIG;
 
     return UriChars;
 }
 
 /// characters which Squid will accept in the HTTP request-target (URI)
@@ -185,139 +190,149 @@
 
     SBuf digit;
     // Searching for Http1magic precludes detecting HTTP/2+ versions.
     // Rewrite if we ever _need_ to return 505 (Version Not Supported) errors.
     if (tok.suffix(digit, CharacterSet::DIGIT) && tok.skipSuffix(Http1magic)) {
         msgProtocol_ = Http::ProtocolVersion(1, (*digit.rawContent() - '0'));
         return true;
     }
 
     // A GET request might use HTTP/0.9 syntax
     if (method_ == Http::METHOD_GET) {
         // RFC 1945 - no HTTP version field at all
         tok = savedTok; // in case the URI ends with a digit
         // report this assumption as an error if configured to triage parsing
         debugs(33, ErrorLevel(), "assuming HTTP/0.9 request-line");
         msgProtocol_ = Http::ProtocolVersion(0,9);
         return true;
     }
 
     debugs(33, ErrorLevel(), "invalid request-line: not HTTP");
     parseStatusCode = Http::scBadRequest;
     return false;
 }
 
 /**
  * Skip characters separating request-line fields.
  * To handle bidirectional parsing, the caller does the actual skipping and
  * we just check how many character the caller has skipped.
  */
 bool
-Http::One::RequestParser::skipDelimiter(const size_t count)
+Http::One::RequestParser::skipDelimiter(const size_t count, const char *where)
 {
     if (count <= 0) {
-        debugs(33, ErrorLevel(), "invalid request-line: missing delimiter");
+        debugs(33, ErrorLevel(), "invalid request-line: missing delimiter " << where);
         parseStatusCode = Http::scBadRequest;
         return false;
     }
 
     // tolerant parser allows multiple whitespace characters between request-line fields
     if (count > 1 && !Config.onoff.relaxed_header_parser) {
-        debugs(33, ErrorLevel(), "invalid request-line: too many delimiters");
+        debugs(33, ErrorLevel(), "invalid request-line: too many delimiters " << where);
         parseStatusCode = Http::scBadRequest;
         return false;
     }
 
     return true;
 }
 
 /// Parse CRs at the end of request-line, just before the terminating LF.
 bool
 Http::One::RequestParser::skipTrailingCrs(Http1::Tokenizer &tok)
 {
     if (Config.onoff.relaxed_header_parser) {
         (void)tok.skipAllTrailing(CharacterSet::CR); // optional; multiple OK
     } else {
         if (!tok.skipOneTrailing(CharacterSet::CR)) {
             debugs(33, ErrorLevel(), "invalid request-line: missing CR before LF");
             parseStatusCode = Http::scBadRequest;
             return false;
         }
     }
     return true;
 }
 
 /**
  * Attempt to parse the first line of a new request message.
  *
  * Governed by:
  *  RFC 1945 section 5.1
  *  RFC 7230 section 2.6, 3.1 and 3.5
  *
  * \retval -1  an error occurred. parseStatusCode indicates HTTP status result.
  * \retval  1  successful parse. member fields contain the request-line items
  * \retval  0  more data is needed to complete the parse
  */
 int
 Http::One::RequestParser::parseRequestFirstLine()
 {
     debugs(74, 5, "parsing possible request: buf.length=" << buf_.length());
     debugs(74, DBG_DATA, buf_);
 
     SBuf line;
 
     // Earlier, skipGarbageLines() took care of any leading LFs (if allowed).
     // Now, the request line has to end at the first LF.
     static const CharacterSet lineChars = CharacterSet::LF.complement("notLF");
     ::Parser::Tokenizer lineTok(buf_);
     if (!lineTok.prefix(line, lineChars) || !lineTok.skip('\n')) {
+        if (buf_.length() >= Config.maxRequestHeaderSize) {
+            /* who should we blame for our failure to parse this line? */
+
+            Http1::Tokenizer methodTok(buf_);
+            if (!parseMethodField(methodTok))
+                return -1; // blame a bad method (or its delimiter)
+
+            // assume it is the URI
+            debugs(74, ErrorLevel(), "invalid request-line: URI exceeds " <<
+                    Config.maxRequestHeaderSize << "-byte limit");
+            parseStatusCode = Http::scUriTooLong;
+            return -1;
+        }
         debugs(74, 5, "Parser needs more data");
         return 0;
     }
 
     Http1::Tokenizer tok(line);
-    const CharacterSet &delimiters = DelimiterCharacters();
+    static const CharacterSet &delimiters = DelimiterCharacters();
 
     if (!parseMethodField(tok))
         return -1;
 
-    if (!skipDelimiter(tok.skipAll(delimiters)))
-        return -1;
-
     /* now parse backwards, to leave just the URI */
     if (!skipTrailingCrs(tok))
         return -1;
 
     if (!parseHttpVersionField(tok))
         return -1;
 
-    if (!http0() && !skipDelimiter(tok.skipAllTrailing(delimiters)))
+    if (!http0() && !skipDelimiter(tok.skipAllTrailing(delimiters), "before protocol version"))
         return -1;
 
     /* parsed everything before and after the URI */
 
     if (!parseUriField(tok))
         return -1;
 
     if (!tok.atEnd()) {
         debugs(33, ErrorLevel(), "invalid request-line: garbage after URI");
         parseStatusCode = Http::scBadRequest;
         return -1;
     }
 
     parseStatusCode = Http::scOkay;
     buf_ = lineTok.remaining(); // incremental parse checkpoint
     return 1;
 }
 
 bool
 Http::One::RequestParser::parse(const SBuf &aBuf)
 {
     buf_ = aBuf;
     debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}");
 
     // stage 1: locate the request-line
     if (parsingStage_ == HTTP_PARSE_NONE) {
         skipGarbageLines();
 
         // if we hit something before EOS treat it as a message
         if (!buf_.isEmpty())

=== modified file 'src/http/one/RequestParser.h'
--- src/http/one/RequestParser.h	2016-05-20 08:28:33 +0000
+++ src/http/one/RequestParser.h	2016-08-24 11:27:39 +0000
@@ -25,48 +25,48 @@
  * the major CRLF delimited segments of an HTTP/1 request message:
  *
  * \item request-line (method, URL, protocol, version)
  * \item mime-header (set of RFC2616 syntax header fields)
  */
 class RequestParser : public Http1::Parser
 {
 public:
     RequestParser();
     virtual ~RequestParser() {}
 
     /* Http::One::Parser API */
     virtual void clear() {*this = RequestParser();}
     virtual Http1::Parser::size_type firstLineSize() const;
     virtual bool parse(const SBuf &aBuf);
 
     /// the HTTP method if this is a request message
     const HttpRequestMethod & method() const {return method_;}
 
     /// the request-line URI if this is a request message, or an empty string.
     const SBuf &requestUri() const {return uri_;}
 
 private:
     void skipGarbageLines();
     int parseRequestFirstLine();
 
     /* all these return false and set parseStatusCode on parsing failures */
     bool parseMethodField(Http1::Tokenizer &);
     bool parseUriField(Http1::Tokenizer &);
     bool parseHttpVersionField(Http1::Tokenizer &);
-    bool skipDelimiter(const size_t count);
+    bool skipDelimiter(const size_t count, const char *where);
     bool skipTrailingCrs(Http1::Tokenizer &tok);
 
     bool http0() const {return !msgProtocol_.major;}
     static const CharacterSet &RequestTargetCharacters();
 
     /// what request method has been found on the first line
     HttpRequestMethod method_;
 
     /// raw copy of the original client request-line URI field
     SBuf uri_;
 };
 
 } // namespace One
 } // namespace Http
 
 #endif /*  _SQUID_SRC_HTTP_ONE_REQUESTPARSER_H */
 

_______________________________________________
squid-dev mailing list
squid-dev@lists.squid-cache.org
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to