[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

tomcat6: CVE-2014-0227: HTTP request smuggling or DoS by streaming malformed data



Source: tomcat6
Version: 6.0.35-6+deb7u1
Severity: important
Tags: security patch upstream fixed-upstream

Hi there,

The following vulnerability affects current tomcat 6.x in squeeze and wheezy.

According to CVE-2014-0227 [cve], "Apache Tomcat 6.x before 6.0.42, 7.x before
7.0.55, and 8.x before 8.0.9 does not properly handle attempts to continue
reading data after an error has occurred, which allows remote attackers to
conduct HTTP request smuggling attacks or cause a denial of service (resource
consumption) by streaming data with malformed chunked transfer coding".

I have prepared the attached patch, based on [fix].

If you fix the vulnerability please also make sure to include the
CVE (Common Vulnerabilities & Exposures) id in your changelog entry.

[cve] https://security-tracker.debian.org/tracker/CVE-2014-0227 
[fix] https://svn.apache.org/viewvc?view=revision&revision=1603628

Please adjust the affected versions in the BTS as needed.

Cheers,

Santiago
Description: Improvements to ChunkedInputFilter
 - Clean-up
 - i18n for ChunkedInputFilter error message
 - Add error flag to allow subsequent attempts at reading after an error to
   fail fast
 Fixes CVE-2014-0227
Origin: https://svn.apache.org/viewvc?view=revision&revision=1603628

Index: tomcat6-6.0.41/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
===================================================================
--- tomcat6-6.0.41.orig/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
+++ tomcat6-6.0.41/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
@@ -14,7 +14,6 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-
 package org.apache.coyote.http11.filters;
 
 import java.io.EOFException;
@@ -29,6 +28,7 @@ import org.apache.coyote.http11.Constant
 import org.apache.coyote.http11.InputFilter;
 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,9 +39,11 @@ import org.apache.tomcat.util.http.MimeH
  */
 public class ChunkedInputFilter implements InputFilter {
 
+    private static final StringManager sm = StringManager.getManager(
+            ChunkedInputFilter.class.getPackage().getName());
 
-    // -------------------------------------------------------------- Constants
 
+    // -------------------------------------------------------------- Constants
 
     protected static final String ENCODING_NAME = "chunked";
     protected static final ByteChunk ENCODING = new ByteChunk();
@@ -49,7 +51,6 @@ public class ChunkedInputFilter implemen
 
     // ----------------------------------------------------- Static Initializer
 
-
     static {
         ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
     }
@@ -57,7 +58,6 @@ public class ChunkedInputFilter implemen
 
     // ----------------------------------------------------- Instance Variables
 
-
     /**
      * Next buffer in the pipeline.
      */
@@ -120,6 +120,11 @@ public class ChunkedInputFilter implemen
     
     
     /**
+     * Flag that indicates if an error has occurred.
+     */
+    private boolean error;
+
+    /**
      * Flag set to true if the next call to doRead() must parse a CRLF pair
      * before doing anything else.
      */
@@ -130,13 +135,10 @@ public class ChunkedInputFilter implemen
      * Request being parsed.
      */
     private Request request;
-    
-    // ------------------------------------------------------------- Properties
 
 
     // ---------------------------------------------------- InputBuffer Methods
 
-
     /**
      * Read bytes.
      * 
@@ -146,11 +148,12 @@ public class ChunkedInputFilter implemen
      * whichever is greater. If the filter does not do request body length
      * control, the returned value should be -1.
      */
-    public int doRead(ByteChunk chunk, Request req)
-        throws IOException {
-
-        if (endChunk)
+    public int doRead(ByteChunk chunk, Request req) throws IOException {
+        if (endChunk) {
             return -1;
+        }
+
+        checkError();
 
         if(needCRLFParse) {
             needCRLFParse = false;
@@ -159,7 +162,7 @@ public class ChunkedInputFilter implemen
 
         if (remaining <= 0) {
             if (!parseChunkHeader()) {
-                throw new IOException("Invalid chunk header");
+                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
             }
             if (endChunk) {
                 parseEndChunk();
@@ -171,8 +174,7 @@ public class ChunkedInputFilter implemen
 
         if (pos >= lastValid) {
             if (readBytes() < 0) {
-                throw new IOException(
-                        "Unexpected end of stream whilst reading request body");
+                throwIOException(sm.getString("chunkedInputFilter.eos"));
             }
         }
 
@@ -197,13 +199,11 @@ public class ChunkedInputFilter implemen
         }
 
         return result;
-
     }
 
 
     // ---------------------------------------------------- InputFilter Methods
 
-
     /**
      * Read the content length from the request.
      */
@@ -215,16 +215,13 @@ public class ChunkedInputFilter implemen
     /**
      * End the current request.
      */
-    public long end()
-        throws IOException {
-
+    public long end() throws IOException {
         // Consume extra bytes : parse the stream until the end chunk is found
         while (doRead(readChunk, null) >= 0) {
         }
 
         // Return the number of extra bytes which were consumed
-        return (lastValid - pos);
-
+        return lastValid - pos;
     }
 
 
@@ -232,7 +229,7 @@ public class ChunkedInputFilter implemen
      * Amount of bytes still available in a buffer.
      */
     public int available() {
-        return (lastValid - pos);
+        return lastValid - pos;
     }
     
 
@@ -258,6 +255,7 @@ public class ChunkedInputFilter implemen
             trailingHeaders.setLimit(org.apache.coyote.Constants.MAX_TRAILER_SIZE);
         }
         extensionSize = 0;
+        error = false;
     }
 
 
@@ -272,12 +270,10 @@ public class ChunkedInputFilter implemen
 
     // ------------------------------------------------------ Protected Methods
 
-
     /**
      * Read bytes from the previous buffer.
      */
-    protected int readBytes()
-        throws IOException {
+    protected int readBytes() throws IOException {
 
         int nRead = buffer.doRead(readChunk, null);
         pos = readChunk.getStart();
@@ -285,7 +281,6 @@ public class ChunkedInputFilter implemen
         buf = readChunk.getBytes();
 
         return nRead;
-
     }
 
 
@@ -298,8 +293,7 @@ public class ChunkedInputFilter implemen
      * we should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
      * according to spec
      */
-    protected boolean parseChunkHeader()
-        throws IOException {
+    protected boolean parseChunkHeader() throws IOException {
 
         int result = 0;
         boolean eol = false;
@@ -340,7 +334,7 @@ public class ChunkedInputFilter implemen
                 extensionSize++;
                 if (org.apache.coyote.Constants.MAX_EXTENSION_SIZE > -1 &&
                         extensionSize > org.apache.coyote.Constants.MAX_EXTENSION_SIZE) {
-                    throw new IOException("maxExtensionSize exceeded");
+                    throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
                 }
             }
 
@@ -348,21 +342,22 @@ public class ChunkedInputFilter implemen
             if (!eol) {
                 pos++;
             }
-
         }
 
-        if (readDigit == 0 || result < 0)
+        if (readDigit == 0 || result < 0) {
             return false;
+        }
 
-        if (result == 0)
+        if (result == 0) {
             endChunk = true;
+        }
 
         remaining = result;
-        if (remaining < 0)
+        if (remaining < 0) {
             return false;
+        }
 
         return true;
-
     }
 
 
@@ -389,26 +384,27 @@ public class ChunkedInputFilter implemen
         boolean crfound = false;
 
         while (!eol) {
-
             if (pos >= lastValid) {
-                if (readBytes() <= 0)
-                    throw new IOException("Invalid CRLF");
+                if (readBytes() <= 0) {
+                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
+                }
             }
 
             if (buf[pos] == Constants.CR) {
-                if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+                if (crfound) {
+                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
+                }
                 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++;
-
         }
     }
 
@@ -417,7 +413,6 @@ public class ChunkedInputFilter implemen
      * Parse end chunk data.
      */
     protected boolean parseEndChunk() throws IOException {
-
         // Handle optional trailer headers
         while (parseHeader()) {
             // Loop until we run out of headers
@@ -434,8 +429,9 @@ public class ChunkedInputFilter implemen
 
         // 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];
@@ -459,8 +455,9 @@ public class ChunkedInputFilter implemen
     
             // 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];
@@ -500,8 +497,9 @@ public class ChunkedInputFilter implemen
     
                 // 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];
@@ -512,7 +510,7 @@ public class ChunkedInputFilter implemen
                     if (trailingHeaders.getLimit() != -1) {
                         int newlimit = trailingHeaders.getLimit() -1;
                         if (trailingHeaders.getEnd() > newlimit) {
-                            throw new IOException("Exceeded maxTrailerSize");
+                            throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
                         }
                         trailingHeaders.setLimit(newlimit);
                     }
@@ -527,8 +525,9 @@ public class ChunkedInputFilter implemen
     
                 // 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];
@@ -552,8 +551,9 @@ public class ChunkedInputFilter implemen
     
             // 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];
@@ -574,4 +574,23 @@ public class ChunkedInputFilter implemen
     
         return true;
     }
+
+
+    private void throwIOException(String msg) throws IOException {
+        error = true;
+        throw new IOException(msg);
+    }
+
+
+    private void throwEOFException(String msg) throws IOException {
+        error = true;
+        throw new EOFException(msg);
+    }
+
+
+    private void checkError() throws IOException {
+        if (error) {
+            throw new IOException(sm.getString("chunkedInputFilter.error"));
+        }
+    }
 }
Index: tomcat6-6.0.41/java/org/apache/coyote/http11/filters/LocalStrings.properties
===================================================================
--- /dev/null
+++ tomcat6-6.0.41/java/org/apache/coyote/http11/filters/LocalStrings.properties
@@ -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
\ No newline at end of file
Index: tomcat6-6.0.41/webapps/docs/changelog.xml
===================================================================
--- tomcat6-6.0.41.orig/webapps/docs/changelog.xml
+++ tomcat6-6.0.41/webapps/docs/changelog.xml
@@ -272,6 +272,15 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        Various improvements to ChunkedInputFilter including clean-up, i18n for
+        error messages and adding an error flag to allow subsequent attempts at
+        reading after an error to fail fast. (markt)
+      </fix>
+    </changelog>
+  </subsection>
   <subsection name="Jasper">
     <changelog>
       <fix>

Attachment: signature.asc
Description: Digital signature


Reply to: