Blob Blame History Raw
--- java/org/apache/coyote/http11/filters/ChunkedInputFilter.java.orig	2014-06-16 18:41:33.642851000 -0400
+++ java/org/apache/coyote/http11/filters/ChunkedInputFilter.java	2014-06-16 19:36:36.796994000 -0400
@@ -29,6 +29,7 @@
 import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.res.StringManager;
 
 /**
  * Chunked input filter. Parses chunked data according to
@@ -39,6 +40,9 @@
  */
 public class ChunkedInputFilter implements InputFilter {
 
+    private static final StringManager sm = StringManager.getManager(
+            ChunkedInputFilter.class.getPackage().getName());
+
     private static final org.apache.juli.logging.Log log
         = org.apache.juli.logging.LogFactory.getLog(ChunkedInputFilter.class);
 
@@ -138,6 +142,11 @@
      */
     private long extensionSize;
 
+    /**
+     * Flat that indicates an error has occured
+     */
+    private boolean error;
+
     // ----------------------------------------------------------- Constructors
     public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize) {
         this.trailingHeaders.setLimit(maxTrailerSize);
@@ -161,6 +170,8 @@
     public int doRead(ByteChunk chunk, Request req)
         throws IOException {
 
+        checkError();
+
         if (endChunk)
             return -1;
 
@@ -171,7 +182,8 @@
 
         if (remaining <= 0) {
             if (!parseChunkHeader()) {
-                throw new IOException("Invalid chunk header");
+                throwIOException(sm.getString(
+                            "chunkedInputFilter.invalidHeader"));
             }
             if (endChunk) {
                 parseEndChunk();
@@ -183,9 +195,9 @@
 
         if (pos >= lastValid) {
             if (readBytes() < 0) {
-                throw new IOException(
-                        "Unexpected end of stream whilst reading request body");
+                throwIOException(sm.getString("chunkedInputFilter.eos"));
             }
+
         }
 
         if (remaining > (lastValid - pos)) {
@@ -232,6 +244,8 @@
     public long end()
         throws IOException {
 
+        checkError();
+
         // Consume extra bytes : parse the stream until the end chunk is found
         while (doRead(readChunk, null) >= 0) {
             // NOOP: Just consume the input
@@ -274,6 +288,7 @@
         trailingHeaders.recycle();
         trailingHeaders.setLimit(maxTrailerSize);
         extensionSize = 0;
+        error = false;
     }
 
 
@@ -286,6 +301,22 @@
         return ENCODING;
     }
 
+    private void throwIOException(String msg) throws IOException {
+        error = true;
+        throw new IOException(msg);
+    }
+
+    private void throwEOFException(String msg) throws IOException {
+        error = true;
+        throw new IOException(msg);
+    }
+
+    private void checkError() throws IOException {
+        if (error) {
+            throw new IOException(
+                    sm.getString("chunkedInputFilter.error"));
+        }
+    }
 
     // ------------------------------------------------------ Protected Methods
 
@@ -322,7 +353,7 @@
 
         int result = 0;
         boolean eol = false;
-        boolean readDigit = false;
+        int readDigit = 0;
         boolean extension = false;
 
         while (!eol) {
@@ -346,10 +377,9 @@
             } else if (!extension) {
                 //don't read data after the trailer
                 int charValue = HexUtils.getDec(buf[pos]);
-                if (charValue != -1) {
-                    readDigit = true;
-                    result *= 16;
-                    result += charValue;
+                if (charValue != -1 && readDigit < 8) {
+                    readDigit++;
+                    result = (result << 4) | charValue;
                 } else {
                     //we shouldn't allow invalid, non hex characters
                     //in the chunked header
@@ -362,7 +392,8 @@
             // validated. Currently it is simply ignored.
                 extensionSize++;
                 if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
-                    throw new IOException("maxExtensionSize exceeded");
+                    throwIOException(sm.getString(
+                                "chunkedInputFilter.maxExtension"));
                 }
            }
             // Parsing the CRLF increments pos
@@ -371,7 +402,7 @@
             }
         }
 
-        if (!readDigit)
+        if (readDigit == 0 || result < 0)
             return false;
 
         if (result == 0)
@@ -411,20 +442,27 @@
         while (!eol) {
 
             if (pos >= lastValid) {
-                if (readBytes() <= 0)
-                    throw new IOException("Invalid CRLF");
+                if (readBytes() <= 0) {
+                    throwIOException(sm.getString(
+                                "chunkedInputFilter.invaldCrlfNoData"));
+                }
             }
 
             if (buf[pos] == Constants.CR) {
-                if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+                if (crfound)  {
+                    throwIOException(sm.getString(
+                                "chunkedInputFilter.invaldCrlfCRCR"));
+                }
                 crfound = true;
             } else if (buf[pos] == Constants.LF) {
                 if (!tolerant && !crfound) {
-                    throw new IOException("Invalid CRLF, no CR character encountered.");
+                    throwIOException(sm.getString(
+                                "chunkedInputFilter.invalidCrlfNoCR"));
                 }
                 eol = true;
             } else {
-                throw new IOException("Invalid CRLF");
+                throwIOException(
+                        sm.getString("chunkedInputFilter.invalidCrlf"));
             }
 
             pos++;
@@ -453,8 +491,10 @@
 
         // Read new bytes if needed
         if (pos >= lastValid) {
-            if (readBytes() <0)
-                throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+            if (readBytes() <0) {
+                throwEOFException(
+                        sm.getString("chunkedInputFilter.eosTrailer"));
+            }
         }
     
         chr = buf[pos];
@@ -478,8 +518,10 @@
     
             // Read new bytes if needed
             if (pos >= lastValid) {
-                if (readBytes() <0)
-                    throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+                if (readBytes() <0) {
+                    throwEOFException(sm.getString(
+                                "chunkedInputFilter.eosTrailer"));
+                }
             }
     
             chr = buf[pos];
@@ -530,7 +572,8 @@
                     // limit placed on trailing header size
                     int newlimit = trailingHeaders.getLimit() -1;
                     if (trailingHeaders.getEnd() > newlimit) {
-                        throw new IOException("Exceeded maxTrailerSize");
+                        throw new IOException(
+                              sm.getString("chunkedInputFilter.maxTrailer"));
                     }
                     trailingHeaders.setLimit(newlimit);
                 } else {
@@ -542,8 +585,11 @@
     
                 // Read new bytes if needed
                 if (pos >= lastValid) {
-                    if (readBytes() <0)
-                        throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+                    if (readBytes() <0) {
+                        throwEOFException(
+                                sm.getString("chunkedInputFilter.eosTrailer"));
+                    }
+                        
                 }
     
                 chr = buf[pos];
@@ -567,8 +613,10 @@
     
             // Read new bytes if needed
             if (pos >= lastValid) {
-                if (readBytes() <0)
-                    throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+                if (readBytes() <0) {
+                    throwEOFException(sm.getString(
+                                "chunkedInputFilter.eosTrailer"));
+                }
             }
     
             chr = buf[pos];
--- java/org/apache/coyote/http11/filters/LocalStrings.properties.orig	2014-06-16 18:41:33.647850000 -0400
+++ java/org/apache/coyote/http11/filters/LocalStrings.properties	2014-06-16 19:22:22.740111000 -0400
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+chunkedInputFilter.error=No data available due to previous error
+chunkedInputFilter.eos=Unexpected end of stream while reading request body
+chunkedInputFilter.eosTrailer=Unexpected end of stream while reading trailer headers
+chunkedInputFilter.invalidCrlf=Invalid end of line sequence (character other than CR or LF found)
+chunkedInputFilter.invalidCrlfCRCR=Invalid end of line sequence (CRCR)
+chunkedInputFilter.invalidCrlfNoCR=Invalid end of line sequence (No CR before LF)
+chunkedInputFilter.invalidCrlfNoData=Invalid end of line sequence (no data available to read)
+chunkedInputFilter.invalidHeader=Invalid chunk header
+chunkedInputFilter.maxExtension=maxExtensionSize exceeded
+chunkedInputFilter.maxTrailer=maxTrailerSize exceeded
--- webapps/docs/changelog.xml.orig	2014-06-16 18:41:33.658857000 -0400
+++ webapps/docs/changelog.xml	2014-06-16 19:37:59.354278000 -0400
@@ -336,6 +336,12 @@
   <subsection name="Coyote">
     <changelog>
       <fix>
+        Resolves: CVE-2014-0075. Improve processing of chunk size from 
+        chunked headers. Avoid overflow and use a bit of shift instead
+        of multiplication as it is marginally faster. (mark/kkolinko)
+        Patch applied by Red Hat Jun 16 2014
+      </fix>
+      <fix>
         <bug>54947</bug>: Fix the HTTP NIO connector that incorrectly rejected a
         request if the CRLF terminating the request line was split across
         multiple packets. Patch by Konstantin Preißer. (markt)