bug 597706 - response header smuggling r=honzab
authorPatrick McManus <mcmanus@ducksong.com>
Tue, 31 May 2011 19:51:51 -0400
changeset 70898 42d996c34679c883d3ad497dc62710535637a3b2
parent 70897 13211282849ca875635e91732ab15156c9617fd2
child 70899 c5e0aaaff9e1e5729e5866e7d71dfc608e4b1e8a
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs597706
milestone7.0a1
bug 597706 - response header smuggling r=honzab reject responses with multiple non identical or invalid content-length headers
netwerk/base/public/nsNetError.h
netwerk/protocol/http/nsHttpChunkedDecoder.cpp
netwerk/protocol/http/nsHttpHeaderArray.cpp
netwerk/protocol/http/nsHttpHeaderArray.h
netwerk/protocol/http/nsHttpRequestHead.h
netwerk/protocol/http/nsHttpResponseHead.cpp
netwerk/protocol/http/nsHttpResponseHead.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/netwerk/base/public/nsNetError.h
+++ b/netwerk/base/public/nsNetError.h
@@ -139,16 +139,24 @@
 
 /**
  * The content encoding of the source document was incorrect, for example
  * returning a plain HTML document advertised as Content-Encoding: gzip
  */
 #define NS_ERROR_INVALID_CONTENT_ENCODING \
     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_NETWORK, 27)
 
+/**
+ * A transport level corruption was found in the source document. for example
+ * a document with a calculated checksum that does not match the Content-MD5
+ * http header.
+ */
+#define NS_ERROR_CORRUPTED_CONTENT \
+    NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_NETWORK, 29)
+
 /******************************************************************************
  * Connectivity error codes:
  */
 
 /**
  * The connection is already established.
  * XXX currently unused - consider removing.
  */
--- a/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -132,17 +132,18 @@ nsHttpChunkedDecoder::ParseChunkRemainin
             buf = (char *) mLineBuf.get();
         }
 
         if (mWaitEOF) {
             if (*buf) {
                 LOG(("got trailer: %s\n", buf));
                 // allocate a header array for the trailers on demand
                 if (!mTrailers) {
-                    mTrailers = new nsHttpHeaderArray();
+                    mTrailers = new nsHttpHeaderArray
+                        (nsHttpHeaderArray::HTTP_RESPONSE_HEADERS);
                     if (!mTrailers)
                         return NS_ERROR_OUT_OF_MEMORY;
                 }
                 mTrailers->ParseHeaderLine(buf);
             }
             else {
                 mWaitEOF = PR_FALSE;
                 mReachedEOF = PR_TRUE;
--- a/netwerk/protocol/http/nsHttpHeaderArray.cpp
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -80,18 +80,21 @@ nsHttpHeaderArray::SetHeader(nsHttpAtom 
             // in the values of these headers contrary to what the spec says.
             entry->value.Append('\n');
         else
             // Delimit each value from the others using a comma (per HTTP spec)
             entry->value.AppendLiteral(", ");
         entry->value.Append(value);
     }
     // Replace the existing string with the new value
-    else
+    else if (CanOverwriteHeader(header))
         entry->value = value;
+    else if (!entry->value.Equals(value))
+        return NS_ERROR_CORRUPTED_CONTENT;
+
     return NS_OK;
 }
 
 void
 nsHttpHeaderArray::ClearHeader(nsHttpAtom header)
 {
     mHeaders.RemoveElement(header, nsEntry::MatchHeader());
 }
@@ -124,17 +127,17 @@ nsHttpHeaderArray::VisitHeaders(nsIHttpH
         const nsEntry &entry = mHeaders[i];
         if (NS_FAILED(visitor->VisitHeader(nsDependentCString(entry.header),
                                            entry.value)))
             break;
     }
     return NS_OK;
 }
 
-void
+nsresult
 nsHttpHeaderArray::ParseHeaderLine(const char *line,
                                    nsHttpAtom *hdr,
                                    char **val)
 {
     //
     // BNF from section 4.2 of RFC 2616:
     //
     //   message-header = field-name ":" [ field-value ]
@@ -146,31 +149,31 @@ nsHttpHeaderArray::ParseHeaderLine(const
     //
     
     // We skip over mal-formed headers in the hope that we'll still be able to
     // do something useful with the response.
 
     char *p = (char *) strchr(line, ':');
     if (!p) {
         LOG(("malformed header [%s]: no colon\n", line));
-        return;
+        return NS_OK;
     }
 
     // make sure we have a valid token for the field-name
     if (!nsHttp::IsValidToken(line, p)) {
         LOG(("malformed header [%s]: field-name not a token\n", line));
-        return;
+        return NS_OK;
     }
     
     *p = 0; // null terminate field-name
 
     nsHttpAtom atom = nsHttp::ResolveAtom(line);
     if (!atom) {
         LOG(("failed to resolve atom [%s]\n", line));
-        return;
+        return NS_OK;
     }
 
     // skip over whitespace
     p = net_FindCharNotInSet(++p, HTTP_LWS);
 
     // trim trailing whitespace - bug 86608
     char *p2 = net_RFindCharNotInSet(p, HTTP_LWS);
 
@@ -178,17 +181,17 @@ nsHttpHeaderArray::ParseHeaderLine(const
                // consisted of LWS, then p2 would have pointed at |p-1|, so
                // the prefix increment is always valid.
 
     // assign return values
     if (hdr) *hdr = atom;
     if (val) *val = p;
 
     // assign response header
-    SetHeader(atom, nsDependentCString(p, p2 - p), PR_TRUE);
+    return SetHeader(atom, nsDependentCString(p, p2 - p), PR_TRUE);
 }
 
 void
 nsHttpHeaderArray::Flatten(nsACString &buf, PRBool pruneProxyHeaders)
 {
     PRUint32 i, count = mHeaders.Length();
     for (i = 0; i < count; ++i) {
         const nsEntry &entry = mHeaders[i];
@@ -242,8 +245,16 @@ nsHttpHeaderArray::CanAppendToHeader(nsH
            header != nsHttp::Authorization       &&
            header != nsHttp::Proxy_Authorization &&
            header != nsHttp::If_Modified_Since   &&
            header != nsHttp::If_Unmodified_Since &&
            header != nsHttp::From                &&
            header != nsHttp::Location            &&
            header != nsHttp::Max_Forwards;
 }
+
+PRBool
+nsHttpHeaderArray::CanOverwriteHeader(nsHttpAtom header)
+{
+    if (mType != HTTP_RESPONSE_HEADERS)
+        return PR_TRUE;
+    return header != nsHttp::Content_Length;
+}
--- a/netwerk/protocol/http/nsHttpHeaderArray.h
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -44,17 +44,22 @@
 #include "nsIHttpChannel.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 
 class nsHttpHeaderArray
 {
 public:
-    nsHttpHeaderArray() {}
+    enum nsHttpHeaderType {
+        HTTP_REQUEST_HEADERS,
+        HTTP_RESPONSE_HEADERS
+    };
+
+    nsHttpHeaderArray(nsHttpHeaderType headerType) : mType(headerType) {}
    ~nsHttpHeaderArray() { Clear(); }
 
     const char *PeekHeader(nsHttpAtom header);
 
     nsresult SetHeader(nsHttpAtom header, const nsACString &value, PRBool merge = PR_FALSE);
     nsresult GetHeader(nsHttpAtom header, nsACString &value);
     void     ClearHeader(nsHttpAtom h);
 
@@ -68,19 +73,19 @@ public:
     PRBool HasHeaderValue(nsHttpAtom header, const char *value) {
         return FindHeaderValue(header, value) != nsnull;
     }
 
     nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor);
 
     // parse a header line, return the header atom and a pointer to the 
     // header value (the substring of the header line -- do not free).
-    void ParseHeaderLine(const char *line,
-                         nsHttpAtom *header=nsnull,
-                         char **value=nsnull);
+    nsresult ParseHeaderLine(const char *line,
+                             nsHttpAtom *header=nsnull,
+                             char **value=nsnull);
 
     void Flatten(nsACString &, PRBool pruneProxyHeaders=PR_FALSE);
 
     PRUint32 Count() { return mHeaders.Length(); }
 
     const char *PeekHeaderAt(PRUint32 i, nsHttpAtom &header);
 
     void Clear();
@@ -99,13 +104,15 @@ public:
         };
     };
 
     nsTArray<nsEntry> &Headers() { return mHeaders; }
 
 private:
     PRInt32 LookupEntry(nsHttpAtom header, nsEntry **);
     PRBool  CanAppendToHeader(nsHttpAtom header);
+    PRBool  CanOverwriteHeader(nsHttpAtom header);
 
     nsTArray<nsEntry> mHeaders;
+    nsHttpHeaderType  mType;
 };
 
 #endif
--- a/netwerk/protocol/http/nsHttpRequestHead.h
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -47,18 +47,20 @@
 //-----------------------------------------------------------------------------
 // nsHttpRequestHead represents the request line and headers from an HTTP
 // request.
 //-----------------------------------------------------------------------------
 
 class nsHttpRequestHead
 {
 public:
-    nsHttpRequestHead() : mMethod(nsHttp::Get), mVersion(NS_HTTP_VERSION_1_1) {}
-   ~nsHttpRequestHead() {}
+    nsHttpRequestHead() : mHeaders(nsHttpHeaderArray::HTTP_REQUEST_HEADERS)
+                        , mMethod(nsHttp::Get)
+                        , mVersion(NS_HTTP_VERSION_1_1) {}
+    ~nsHttpRequestHead() {}
 
     void SetMethod(nsHttpAtom method) { mMethod = method; }
     void SetVersion(nsHttpVersion version) { mVersion = version; }
     void SetRequestURI(const nsCSubstring &s) { mRequestURI = s; }
 
     nsHttpHeaderArray  &Headers()    { return mHeaders; }
     nsHttpAtom          Method()     { return mMethod; }
     nsHttpVersion       Version()    { return mVersion; }
--- a/netwerk/protocol/http/nsHttpResponseHead.cpp
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -192,44 +192,52 @@ nsHttpResponseHead::ParseStatusLine(cons
         else
             mStatusText = ++line;
     }
 
     LOG(("Have status line [version=%u status=%u statusText=%s]\n",
         PRUintn(mVersion), PRUintn(mStatus), mStatusText.get()));
 }
 
-void
+nsresult
 nsHttpResponseHead::ParseHeaderLine(const char *line)
 {
     nsHttpAtom hdr = {0};
     char *val;
-
-    mHeaders.ParseHeaderLine(line, &hdr, &val);
+    nsresult rv;
+    
+    rv = mHeaders.ParseHeaderLine(line, &hdr, &val);
+    if (NS_FAILED(rv))
+        return rv;
+    
     // leading and trailing LWS has been removed from |val|
 
     // handle some special case headers...
     if (hdr == nsHttp::Content_Length) {
         PRInt64 len;
         // permit only a single value here.
-        if (nsHttp::ParseInt64(val, &len))
+        if (nsHttp::ParseInt64(val, &len)) {
             mContentLength = len;
-        else
+        }
+        else {
             LOG(("invalid content-length!\n"));
+            return NS_ERROR_CORRUPTED_CONTENT;
+        }
     }
     else if (hdr == nsHttp::Content_Type) {
         LOG(("ParseContentType [type=%s]\n", val));
         PRBool dummy;
         net_ParseContentType(nsDependentCString(val),
                              mContentType, mContentCharset, &dummy);
     }
     else if (hdr == nsHttp::Cache_Control)
         ParseCacheControl(val);
     else if (hdr == nsHttp::Pragma)
         ParsePragma(val);
+    return NS_OK;
 }
 
 // From section 13.2.3 of RFC2616, we compute the current age of a cached
 // response as follows:
 //
 //    currentAge = max(max(0, responseTime - dateValue), ageValue)
 //               + now - requestTime
 //
--- a/netwerk/protocol/http/nsHttpResponseHead.h
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -46,23 +46,24 @@
 //-----------------------------------------------------------------------------
 // nsHttpResponseHead represents the status line and headers from an HTTP
 // response.
 //-----------------------------------------------------------------------------
 
 class nsHttpResponseHead
 {
 public:
-    nsHttpResponseHead() : mVersion(NS_HTTP_VERSION_1_1)
+    nsHttpResponseHead() : mHeaders(nsHttpHeaderArray::HTTP_RESPONSE_HEADERS)
+                         , mVersion(NS_HTTP_VERSION_1_1)
                          , mStatus(200)
                          , mContentLength(LL_MAXUINT)
                          , mCacheControlNoStore(PR_FALSE)
                          , mCacheControlNoCache(PR_FALSE)
                          , mPragmaNoCache(PR_FALSE) {}
-   ~nsHttpResponseHead() 
+    ~nsHttpResponseHead() 
     {
         Reset();
     }
     
     nsHttpHeaderArray    &Headers()        { return mHeaders; }
     nsHttpVersion         Version()        { return mVersion; }
     PRUint16              Status()         { return mStatus; }
     const nsAFlatCString &StatusText()     { return mStatusText; }
@@ -99,17 +100,17 @@ public:
     // parse flattened response head. block must be null terminated. parsing is
     // destructive.
     nsresult Parse(char *block);
 
     // parse the status line. line must be null terminated.
     void     ParseStatusLine(const char *line);
 
     // parse a header line. line must be null terminated. parsing is destructive.
-    void     ParseHeaderLine(const char *line);
+    nsresult ParseHeaderLine(const char *line);
 
     // cache validation support methods
     nsresult ComputeFreshnessLifetime(PRUint32 *);
     nsresult ComputeCurrentAge(PRUint32 now, PRUint32 requestTime, PRUint32 *result);
     PRBool   MustValidate();
     PRBool   MustValidateIfExpired();
 
     // returns true if the server appears to support byte range requests.
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -779,47 +779,52 @@ nsHttpTransaction::LocateHttpStart(char 
         if (!nsCRT::IsAsciiSpace(*buf))
             firstByte = PR_FALSE;
         buf++;
         len--;
     }
     return 0;
 }
 
-
-void
+nsresult
 nsHttpTransaction::ParseLine(char *line)
 {
     LOG(("nsHttpTransaction::ParseLine [%s]\n", line));
-
+    nsresult rv = NS_OK;
+    
     if (!mHaveStatusLine) {
         mResponseHead->ParseStatusLine(line);
         mHaveStatusLine = PR_TRUE;
         // XXX this should probably never happen
         if (mResponseHead->Version() == NS_HTTP_VERSION_0_9)
             mHaveAllHeaders = PR_TRUE;
     }
-    else
-        mResponseHead->ParseHeaderLine(line);
+    else {
+        rv = mResponseHead->ParseHeaderLine(line);
+    }
+    return rv;
 }
 
 nsresult
 nsHttpTransaction::ParseLineSegment(char *segment, PRUint32 len)
 {
     NS_PRECONDITION(!mHaveAllHeaders, "already have all headers");
 
     if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
         // trim off the new line char, and if this segment is
         // not a continuation of the previous or if we haven't
         // parsed the status line yet, then parse the contents
         // of mLineBuf.
         mLineBuf.Truncate(mLineBuf.Length() - 1);
         if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
-            ParseLine(mLineBuf.BeginWriting());
+            nsresult rv = ParseLine(mLineBuf.BeginWriting());
             mLineBuf.Truncate();
+            if (NS_FAILED(rv)) {
+                return rv;
+            }
         }
     }
 
     // append segment to mLineBuf...
     if (mLineBuf.Length() + len > MAX_LINEBUF_LENGTH) {
         LOG(("excessively long header received, canceling transaction [trans=%x]", this));
         return NS_ERROR_ABORT;
     }
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -136,17 +136,17 @@ public:
     PRInt32    Priority()                 { return mPriority; }
 
     const TimingStruct& Timings() const { return mTimings; }
 
 private:
     nsresult Restart();
     char    *LocateHttpStart(char *buf, PRUint32 len,
                              PRBool aAllowPartialMatch);
-    void     ParseLine(char *line);
+    nsresult ParseLine(char *line);
     nsresult ParseLineSegment(char *seg, PRUint32 len);
     nsresult ParseHead(char *, PRUint32 count, PRUint32 *countRead);
     nsresult HandleContentStart();
     nsresult HandleContent(char *, PRUint32 count, PRUint32 *contentRead, PRUint32 *contentRemaining);
     nsresult ProcessData(char *, PRUint32, PRUint32 *);
     void     DeleteSelfOnConsumerThread();
 
     static NS_METHOD ReadRequestSegment(nsIInputStream *, void *, const char *,