--- 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 @@ + 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 + + 54947: 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)