diff --git a/SOURCES/tomcat-7.0.69-CVE-2016-6816.patch b/SOURCES/tomcat-7.0.69-CVE-2016-6816.patch new file mode 100644 index 0000000..654c804 --- /dev/null +++ b/SOURCES/tomcat-7.0.69-CVE-2016-6816.patch @@ -0,0 +1,1194 @@ +--- conf/catalina.properties.orig 2017-03-28 15:06:40.627389816 -0400 ++++ conf/catalina.properties 2017-03-28 15:06:40.657389967 -0400 +@@ -131,3 +131,7 @@ + #tomcat.util.buf.StringCache.char.enabled=true + #tomcat.util.buf.StringCache.trainThreshold=500000 + #tomcat.util.buf.StringCache.cacheSize=5000 ++ ++# Allow for changes to HTTP request validation ++# WARNING: Using this option will expose the server to CVE-2016-6816 ++#tomcat.util.http.parser.HttpParser.requestTargetAllow=| +--- java/org/apache/coyote/http11/AbstractInputBuffer.java.orig 2017-03-28 15:06:40.628389821 -0400 ++++ java/org/apache/coyote/http11/AbstractInputBuffer.java 2017-03-28 15:06:40.649389926 -0400 +@@ -28,62 +28,10 @@ + + public abstract class AbstractInputBuffer implements InputBuffer{ + +- protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128]; +- + /** + * The string manager for this package. + */ +- protected static final StringManager sm = +- StringManager.getManager(Constants.Package); +- +- +- static { +- for (int i = 0; i < 128; i++) { +- if (i < 32) { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == 127) { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '(') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == ')') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '<') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '>') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '@') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == ',') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == ';') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == ':') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '\\') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '\"') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '/') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '[') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == ']') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '?') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '=') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '{') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == '}') { +- HTTP_TOKEN_CHAR[i] = false; +- } else if (i == ' ') { +- HTTP_TOKEN_CHAR[i] = false; +- } else { +- HTTP_TOKEN_CHAR[i] = true; +- } +- } +- } ++ protected static final StringManager sm = StringManager.getManager(Constants.Package); + + + /** +--- java/org/apache/coyote/http11/InternalAprInputBuffer.java.orig 2017-03-28 15:06:40.629389826 -0400 ++++ java/org/apache/coyote/http11/InternalAprInputBuffer.java 2017-03-28 15:06:40.650389932 -0400 +@@ -30,6 +30,7 @@ + import org.apache.tomcat.jni.Status; + import org.apache.tomcat.util.buf.ByteChunk; + import org.apache.tomcat.util.buf.MessageBytes; ++import org.apache.tomcat.util.http.parser.HttpParser; + import org.apache.tomcat.util.net.AbstractEndpoint; + import org.apache.tomcat.util.net.SocketWrapper; + +@@ -70,7 +71,7 @@ + + parsingHeader = true; + swallowInput = true; +- ++ + } + + +@@ -93,7 +94,7 @@ + // --------------------------------------------------------- Public Methods + + /** +- * Recycle the input buffer. This should be called when closing the ++ * Recycle the input buffer. This should be called when closing the + * connection. + */ + @Override +@@ -105,14 +106,14 @@ + + + /** +- * Read the request line. This function is meant to be used during the +- * HTTP request header parsing. Do NOT attempt to read the request body ++ * Read the request line. This function is meant to be used during the ++ * HTTP request header parsing. Do NOT attempt to read the request body + * using it. + * + * @throws IOException If an exception occurs during the underlying socket + * read operations, or if the given buffer is not big enough to accommodate + * the whole line. +- * @return true if data is properly fed; false if no data is available ++ * @return true if data is properly fed; false if no data is available + * immediately and thread should be freed + */ + @Override +@@ -159,7 +160,7 @@ + + // + // Reading the method name +- // Method name is always US-ASCII ++ // Method name is a token + // + + boolean space = false; +@@ -172,22 +173,20 @@ + throw new EOFException(sm.getString("iib.eof.error")); + } + +- // Spec says no CR or LF in method name +- if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) { +- throw new IllegalArgumentException( +- sm.getString("iib.invalidmethod")); +- } +- // Spec says single SP but it also says be tolerant of HT ++ // Spec says method name is a token followed by a single SP but ++ // also be tolerant of multiple SP and/or HT. + if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { + space = true; + request.method().setBytes(buf, start, pos - start); ++ } else if (!HttpParser.isToken(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); + } + + pos++; + + } + +- // Spec says single SP but also says be tolerant of multiple and/or HT ++ // Spec says single SP but also says be tolerant of multiple SP and/or HT + while (space) { + // Read new bytes if needed + if (pos >= lastValid) { +@@ -224,15 +223,16 @@ + if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { + space = true; + end = pos; +- } else if ((buf[pos] == Constants.CR) ++ } else if ((buf[pos] == Constants.CR) + || (buf[pos] == Constants.LF)) { + // HTTP/0.9 style request + eol = true; + space = true; + end = pos; +- } else if ((buf[pos] == Constants.QUESTION) +- && (questionPos == -1)) { ++ } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { + questionPos = pos; ++ } else if (HttpParser.isNotRequestTarget(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); + } + + pos++; +@@ -241,7 +241,7 @@ + + request.unparsedURI().setBytes(buf, start, end - start); + if (questionPos >= 0) { +- request.queryString().setBytes(buf, questionPos + 1, ++ request.queryString().setBytes(buf, questionPos + 1, + end - questionPos - 1); + request.requestURI().setBytes(buf, start, questionPos - start); + } else { +@@ -269,7 +269,7 @@ + + // + // Reading the protocol +- // Protocol is always US-ASCII ++ // Protocol is always "HTTP/" DIGIT "." DIGIT + // + + while (!eol) { +@@ -286,6 +286,8 @@ + if (end == 0) + end = pos; + eol = true; ++ } else if (!HttpParser.isHttpProtocol(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); + } + + pos++; +@@ -297,7 +299,7 @@ + } else { + request.protocol().setString(""); + } +- ++ + return true; + + } +@@ -326,7 +328,7 @@ + + /** + * Parse an HTTP header. +- * ++ * + * @return false after reading a blank line (which indicates that the + * HTTP header parsing is done + */ +@@ -384,7 +386,7 @@ + if (buf[pos] == Constants.COLON) { + colon = true; + headerValue = headers.addValue(buf, start, pos - start); +- } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { ++ } else if (!HttpParser.isToken(buf[pos])) { + // If a non-token header is detected, skip the line and + // ignore the header + skipLine(start); +@@ -490,14 +492,14 @@ + + } + +- ++ + private void skipLine(int start) throws IOException { + boolean eol = false; + int lastRealByte = start; + if (pos - 1 > start) { + lastRealByte = pos - 1; + } +- ++ + while (!eol) { + + // Read new bytes if needed +@@ -521,8 +523,8 @@ + lastRealByte - start + 1, Charset.forName("ISO-8859-1")))); + } + } +- +- ++ ++ + // ---------------------------------------------------- InputBuffer Methods + + +@@ -530,7 +532,7 @@ + * Read some bytes. + */ + @Override +- public int doRead(ByteChunk chunk, Request req) ++ public int doRead(ByteChunk chunk, Request req) + throws IOException { + + if (lastActiveFilter == -1) +@@ -558,11 +560,11 @@ + // Ignore the block parameter and just call fill + return fill(); + } +- +- ++ ++ + /** + * Fill the internal buffer using data from the underlying input stream. +- * ++ * + * @return false if at end of stream + */ + protected boolean fill() +@@ -594,7 +596,7 @@ + } else { + + if (buf.length - end < 4500) { +- // In this case, the request header was really large, so we allocate a ++ // In this case, the request header was really large, so we allocate a + // brand new one; the old one will get GCed when subsequent requests + // clear all references + buf = new byte[buf.length]; +@@ -640,7 +642,7 @@ + * This class is an input buffer which will read its data from an input + * stream. + */ +- protected class SocketInputBuffer ++ protected class SocketInputBuffer + implements InputBuffer { + + +@@ -648,7 +650,7 @@ + * Read bytes into the specified chunk. + */ + @Override +- public int doRead(ByteChunk chunk, Request req ) ++ public int doRead(ByteChunk chunk, Request req ) + throws IOException { + + if (pos >= lastValid) { +--- java/org/apache/coyote/http11/InternalInputBuffer.java.orig 2017-03-28 15:06:40.630389831 -0400 ++++ java/org/apache/coyote/http11/InternalInputBuffer.java 2017-03-28 15:06:40.650389932 -0400 +@@ -28,6 +28,7 @@ + import org.apache.juli.logging.LogFactory; + import org.apache.tomcat.util.buf.ByteChunk; + import org.apache.tomcat.util.buf.MessageBytes; ++import org.apache.tomcat.util.http.parser.HttpParser; + import org.apache.tomcat.util.net.AbstractEndpoint; + import org.apache.tomcat.util.net.SocketWrapper; + +@@ -69,10 +70,10 @@ + + } + +- ++ + /** +- * Read the request line. This function is meant to be used during the +- * HTTP request header parsing. Do NOT attempt to read the request body ++ * Read the request line. This function is meant to be used during the ++ * HTTP request header parsing. Do NOT attempt to read the request body + * using it. + * + * @throws IOException If an exception occurs during the underlying socket +@@ -81,7 +82,7 @@ + */ + @Override + public boolean parseRequestLine(boolean useAvailableDataOnly) +- ++ + throws IOException { + + int start = 0; +@@ -113,7 +114,7 @@ + + // + // Reading the method name +- // Method name is always US-ASCII ++ // Method name is a token + // + + boolean space = false; +@@ -126,23 +127,20 @@ + throw new EOFException(sm.getString("iib.eof.error")); + } + +- // Spec says no CR or LF in method name +- if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) { +- throw new IllegalArgumentException( +- sm.getString("iib.invalidmethod")); +- } +- // Spec says single SP but it also says be tolerant of HT ++ // Spec says method name is a token followed by a single SP but ++ // also be tolerant of multiple SP and/or HT. + if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { + space = true; + request.method().setBytes(buf, start, pos - start); ++ } else if (!HttpParser.isToken(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); + } + + pos++; + + } + +- +- // Spec says single SP but also says be tolerant of multiple and/or HT ++ // Spec says single SP but also be tolerant of multiple SP and/or HT + while (space) { + // Read new bytes if needed + if (pos >= lastValid) { +@@ -179,15 +177,16 @@ + if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { + space = true; + end = pos; +- } else if ((buf[pos] == Constants.CR) ++ } else if ((buf[pos] == Constants.CR) + || (buf[pos] == Constants.LF)) { + // HTTP/0.9 style request + eol = true; + space = true; + end = pos; +- } else if ((buf[pos] == Constants.QUESTION) +- && (questionPos == -1)) { ++ } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { + questionPos = pos; ++ } else if (HttpParser.isNotRequestTarget(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); + } + + pos++; +@@ -196,14 +195,14 @@ + + request.unparsedURI().setBytes(buf, start, end - start); + if (questionPos >= 0) { +- request.queryString().setBytes(buf, questionPos + 1, ++ request.queryString().setBytes(buf, questionPos + 1, + end - questionPos - 1); + request.requestURI().setBytes(buf, start, questionPos - start); + } else { + request.requestURI().setBytes(buf, start, end - start); + } + +- // Spec says single SP but also says be tolerant of multiple and/or HT ++ // Spec says single SP but also says be tolerant of multiple SP and/or HT + while (space) { + // Read new bytes if needed + if (pos >= lastValid) { +@@ -223,9 +222,8 @@ + + // + // Reading the protocol +- // Protocol is always US-ASCII ++ // Protocol is always "HTTP/" DIGIT "." DIGIT + // +- + while (!eol) { + + // Read new bytes if needed +@@ -240,6 +238,8 @@ + if (end == 0) + end = pos; + eol = true; ++ } else if (!HttpParser.isHttpProtocol(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); + } + + pos++; +@@ -251,7 +251,7 @@ + } else { + request.protocol().setString(""); + } +- ++ + return true; + + } +@@ -280,7 +280,7 @@ + + /** + * Parse an HTTP header. +- * ++ * + * @return false after reading a blank line (which indicates that the + * HTTP header parsing is done + */ +@@ -338,7 +338,7 @@ + if (buf[pos] == Constants.COLON) { + colon = true; + headerValue = headers.addValue(buf, start, pos - start); +- } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { ++ } else if (!HttpParser.isToken(buf[pos])) { + // If a non-token header is detected, skip the line and + // ignore the header + skipLine(start); +@@ -470,7 +470,7 @@ + if (pos - 1 > start) { + lastRealByte = pos - 1; + } +- ++ + while (!eol) { + + // Read new bytes if needed +@@ -497,7 +497,7 @@ + + /** + * Fill the internal buffer using data from the underlying input stream. +- * ++ * + * @return false if at end of stream + */ + protected boolean fill() throws IOException { +@@ -524,7 +524,7 @@ + } else { + + if (buf.length - end < 4500) { +- // In this case, the request header was really large, so we allocate a ++ // In this case, the request header was really large, so we allocate a + // brand new one; the old one will get GCed when subsequent requests + // clear all references + buf = new byte[buf.length]; +@@ -551,7 +551,7 @@ + * This class is an input buffer which will read its data from an input + * stream. + */ +- protected class InputStreamInputBuffer ++ protected class InputStreamInputBuffer + implements InputBuffer { + + +@@ -559,7 +559,7 @@ + * Read bytes into the specified chunk. + */ + @Override +- public int doRead(ByteChunk chunk, Request req ) ++ public int doRead(ByteChunk chunk, Request req ) + throws IOException { + + if (pos >= lastValid) { +--- java/org/apache/coyote/http11/InternalNioInputBuffer.java.orig 2017-03-28 15:06:40.631389836 -0400 ++++ java/org/apache/coyote/http11/InternalNioInputBuffer.java 2017-03-28 15:06:40.650389932 -0400 +@@ -25,6 +25,7 @@ + import org.apache.coyote.Request; + import org.apache.tomcat.util.buf.ByteChunk; + import org.apache.tomcat.util.buf.MessageBytes; ++import org.apache.tomcat.util.http.parser.HttpParser; + import org.apache.tomcat.util.net.AbstractEndpoint; + import org.apache.tomcat.util.net.NioChannel; + import org.apache.tomcat.util.net.NioEndpoint; +@@ -92,7 +93,7 @@ + } + + // ----------------------------------------------------------- Constructors +- ++ + + /** + * Alternate constructor. +@@ -137,7 +138,7 @@ + * Underlying socket. + */ + private NioChannel socket; +- ++ + /** + * Selector pool, for blocking reads and blocking writes + */ +@@ -159,7 +160,7 @@ + // --------------------------------------------------------- Public Methods + + /** +- * Recycle the input buffer. This should be called when closing the ++ * Recycle the input buffer. This should be called when closing the + * connection. + */ + @Override +@@ -178,7 +179,7 @@ + + /** + * End processing of current HTTP request. +- * Note: All bytes of the current request should have been already ++ * Note: All bytes of the current request should have been already + * consumed. This method only resets all the pointers so that we are ready + * to parse the next HTTP request. + */ +@@ -195,14 +196,14 @@ + } + + /** +- * Read the request line. This function is meant to be used during the +- * HTTP request header parsing. Do NOT attempt to read the request body ++ * Read the request line. This function is meant to be used during the ++ * HTTP request header parsing. Do NOT attempt to read the request body + * using it. + * + * @throws IOException If an exception occurs during the underlying socket + * read operations, or if the given buffer is not big enough to accommodate + * the whole line. +- * @return true if data is properly fed; false if no data is available ++ * @return true if data is properly fed; false if no data is available + * immediately and thread should be freed + */ + @Override +@@ -217,7 +218,7 @@ + if ( parsingRequestLinePhase == 0 ) { + byte chr = 0; + do { +- ++ + // Read new bytes if needed + if (pos >= lastValid) { + if (useAvailableDataOnly) { +@@ -248,7 +249,7 @@ + if ( parsingRequestLinePhase == 2 ) { + // + // Reading the method name +- // Method name is always US-ASCII ++ // Method name is a token + // + boolean space = false; + while (!space) { +@@ -257,21 +258,20 @@ + if (!fill(true, false)) //request line parsing + return false; + } +- // Spec says no CR or LF in method name +- if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) { +- throw new IllegalArgumentException( +- sm.getString("iib.invalidmethod")); +- } ++ // Spec says method name is a token followed by a single SP but ++ // also be tolerant of multiple SP and/or HT. + if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { + space = true; + request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart); ++ } else if (!HttpParser.isToken(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); + } + pos++; + } + parsingRequestLinePhase = 3; + } + if ( parsingRequestLinePhase == 3 ) { +- // Spec says single SP but also be tolerant of multiple and/or HT ++ // Spec says single SP but also be tolerant of multiple SP and/or HT + boolean space = true; + while (space) { + // Read new bytes if needed +@@ -290,7 +290,7 @@ + } + if (parsingRequestLinePhase == 4) { + // Mark the current buffer position +- ++ + int end = 0; + // + // Reading the URI +@@ -305,21 +305,22 @@ + if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { + space = true; + end = pos; +- } else if ((buf[pos] == Constants.CR) ++ } else if ((buf[pos] == Constants.CR) + || (buf[pos] == Constants.LF)) { + // HTTP/0.9 style request + parsingRequestLineEol = true; + space = true; + end = pos; +- } else if ((buf[pos] == Constants.QUESTION) +- && (parsingRequestLineQPos == -1)) { ++ } else if ((buf[pos] == Constants.QUESTION) && (parsingRequestLineQPos == -1)) { + parsingRequestLineQPos = pos; ++ } else if (HttpParser.isNotRequestTarget(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); + } + pos++; + } + request.unparsedURI().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); + if (parsingRequestLineQPos >= 0) { +- request.queryString().setBytes(buf, parsingRequestLineQPos + 1, ++ request.queryString().setBytes(buf, parsingRequestLineQPos + 1, + end - parsingRequestLineQPos - 1); + request.requestURI().setBytes(buf, parsingRequestLineStart, parsingRequestLineQPos - parsingRequestLineStart); + } else { +@@ -348,10 +349,10 @@ + // Mark the current buffer position + end = 0; + } +- if (parsingRequestLinePhase == 6) { ++ if (parsingRequestLinePhase == 6) { + // + // Reading the protocol +- // Protocol is always US-ASCII ++ // Protocol is always "HTTP/" DIGIT "." DIGIT + // + while (!parsingRequestLineEol) { + // Read new bytes if needed +@@ -359,17 +360,19 @@ + if (!fill(true, false)) //request line parsing + return false; + } +- ++ + if (buf[pos] == Constants.CR) { + end = pos; + } else if (buf[pos] == Constants.LF) { + if (end == 0) + end = pos; + parsingRequestLineEol = true; ++ } else if (!HttpParser.isHttpProtocol(buf[pos])) { ++ throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); + } + pos++; + } +- ++ + if ( (end - parsingRequestLineStart) > 0) { + request.protocol().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); + } else { +@@ -383,7 +386,7 @@ + } + throw new IllegalStateException("Invalid request line parse phase:"+parsingRequestLinePhase); + } +- ++ + private void expand(int newsize) { + if ( newsize > buf.length ) { + if (parsingHeader) { +@@ -398,7 +401,7 @@ + buf = tmp; + } + } +- ++ + /** + * Perform blocking read with a timeout if desired + * @param timeout boolean - if we want to use the timeout data +@@ -407,7 +410,7 @@ + * @throws IOException if a socket exception occurs + * @throws EOFException if end of stream is reached + */ +- ++ + private int readSocket(boolean timeout, boolean block) throws IOException { + int nRead = 0; + socket.getBufHandler().getReadBuffer().clear(); +@@ -429,7 +432,7 @@ + socket.getIOChannel().socket().getSoTimeout()); + } catch ( EOFException eof ) { + nRead = -1; +- } finally { ++ } finally { + if ( selector != null ) pool.put(selector); + } + } else { +@@ -462,7 +465,7 @@ + } + + HeaderParseStatus status = HeaderParseStatus.HAVE_MORE_HEADERS; +- ++ + do { + status = parseHeader(); + // Checking that +@@ -491,7 +494,7 @@ + + /** + * Parse an HTTP header. +- * ++ * + * @return false after reading a blank line (which indicates that the + * HTTP header parsing is done + */ +@@ -507,7 +510,7 @@ + + // Read new bytes if needed + if (pos >= lastValid) { +- if (!fill(true,false)) {//parse header ++ if (!fill(true,false)) {//parse header + headerParsePos = HeaderParsePosition.HEADER_START; + return HeaderParseStatus.NEED_MORE_DATA; + } +@@ -543,7 +546,7 @@ + + // Read new bytes if needed + if (pos >= lastValid) { +- if (!fill(true,false)) { //parse header ++ if (!fill(true,false)) { //parse header + return HeaderParseStatus.NEED_MORE_DATA; + } + } +@@ -558,7 +561,7 @@ + headerData.realPos = pos; + headerData.lastSignificantChar = pos; + break; +- } else if (!HTTP_TOKEN_CHAR[chr]) { ++ } else if (!HttpParser.isToken(chr)) { + // If a non-token header is detected, skip the line and + // ignore the header + headerData.lastSignificantChar = pos; +@@ -590,7 +593,7 @@ + while (true) { + // Read new bytes if needed + if (pos >= lastValid) { +- if (!fill(true,false)) {//parse header ++ if (!fill(true,false)) {//parse header + //HEADER_VALUE_START + return HeaderParseStatus.NEED_MORE_DATA; + } +@@ -613,7 +616,7 @@ + + // Read new bytes if needed + if (pos >= lastValid) { +- if (!fill(true,false)) {//parse header ++ if (!fill(true,false)) {//parse header + //HEADER_VALUE + return HeaderParseStatus.NEED_MORE_DATA; + } +@@ -646,7 +649,7 @@ + // Read new bytes if needed + if (pos >= lastValid) { + if (!fill(true,false)) {//parse header +- ++ + //HEADER_MULTI_LINE + return HeaderParseStatus.NEED_MORE_DATA; + } +@@ -672,7 +675,7 @@ + headerData.recycle(); + return HeaderParseStatus.HAVE_MORE_HEADERS; + } +- ++ + public int getParsingRequestLinePhase() { + return parsingRequestLinePhase; + } +@@ -771,7 +774,7 @@ + + /** + * Fill the internal buffer using data from the underlying input stream. +- * ++ * + * @return false if at end of stream + */ + @Override +@@ -780,7 +783,7 @@ + } + + protected boolean fill(boolean timeout, boolean block) throws IOException, EOFException { +- ++ + + boolean read = false; + +@@ -809,7 +812,7 @@ + * This class is an input buffer which will read its data from an input + * stream. + */ +- protected class SocketInputBuffer ++ protected class SocketInputBuffer + implements InputBuffer { + + +@@ -817,7 +820,7 @@ + * Read bytes into the specified chunk. + */ + @Override +- public int doRead(ByteChunk chunk, Request req ) ++ public int doRead(ByteChunk chunk, Request req ) + throws IOException { + + if (pos >= lastValid) { +--- java/org/apache/coyote/http11/LocalStrings.properties.orig 2017-03-28 15:06:40.632389841 -0400 ++++ java/org/apache/coyote/http11/LocalStrings.properties 2017-03-28 15:06:40.649389926 -0400 +@@ -42,8 +42,10 @@ + iib.apr.sslGeneralError=An APR general error was returned by the SSL read operation on APR/native socket [{0}] with wrapper [{1}]. It will be treated as EAGAIN and the socket returned to the poller. + + iib.eof.error=Unexpected EOF read on the socket +-iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 2616 and has been ignored. +-iib.invalidmethod=Invalid character (CR or LF) found in method name ++iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 7230 and has been ignored. ++iib.invalidmethod=Invalid character found in method name. HTTP method names must be tokens ++iib.invalidRequestTarget=Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 ++iib.invalidHttpProtocol=Invalid character found in the HTTP protocol + iib.parseheaders.ise.error=Unexpected state: headers already parsed. Buffer not recycled? + iib.requestheadertoolarge.error=Request header is too large + +--- java/org/apache/tomcat/util/http/parser/HttpParser.java.orig 2017-03-28 15:06:40.633389846 -0400 ++++ java/org/apache/tomcat/util/http/parser/HttpParser.java 2017-03-28 15:06:40.657389967 -0400 +@@ -23,6 +23,11 @@ + import java.util.Locale; + import java.util.Map; + ++import org.apache.juli.logging.Log; ++import org.apache.juli.logging.LogFactory; ++ ++import org.apache.tomcat.util.res.StringManager; ++ + /** + * HTTP header value parser implementation. Parsing HTTP headers as per RFC2616 + * is not always as simple as it first appears. For headers that only use tokens +@@ -53,9 +58,19 @@ + private static final Map fieldTypes = + new HashMap(); + +- // Arrays used by isToken(), isHex() +- private static final boolean isToken[] = new boolean[128]; +- private static final boolean isHex[] = new boolean[128]; ++ private static final StringManager sm = StringManager.getManager(HttpParser.class); ++ ++ private static final Log log = LogFactory.getLog(HttpParser.class); ++ ++ private static final int ARRAY_SIZE = 128; ++ ++ private static final boolean[] IS_CONTROL = new boolean[ARRAY_SIZE]; ++ private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; ++ private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; ++ private static final boolean[] IS_HEX = new boolean[ARRAY_SIZE]; ++ private static final boolean[] IS_NOT_REQUEST_TARGET = new boolean[ARRAY_SIZE]; ++ private static final boolean[] IS_HTTP_PROTOCOL = new boolean[ARRAY_SIZE]; ++ private static final boolean[] REQUEST_TARGET_ALLOW = new boolean[ARRAY_SIZE]; + + static { + // Digest field types. +@@ -77,24 +92,57 @@ + // RFC2617 says nc is 8LHEX. <">8LHEX<"> will also be accepted + fieldTypes.put("nc", FIELD_TYPE_LHEX); + +- // Setup the flag arrays +- for (int i = 0; i < 128; i++) { +- if (i <= 32) { // includes '\t' and ' ' +- isToken[i] = false; +- } else if (i == '(' || i == ')' || i == '<' || i == '>' || i == '@' || +- i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' || +- i == '/' || i == '[' || i == ']' || i == '?' || i == '=' || +- i == '{' || i == '}') { +- isToken[i] = false; +- } else { +- isToken[i] = true; ++ String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow"); ++ if (prop != null) { ++ for (int i = 0; i < prop.length(); i++) { ++ char c = prop.charAt(i); ++ if (c == '{' || c == '}' || c == '|') { ++ REQUEST_TARGET_ALLOW[c] = true; ++ } else { ++ log.warn(sm.getString("httpparser.invalidRequestTargetCharacter", c)); ++ } + } ++ } + +- if (i >= '0' && i <= '9' || i >= 'A' && i <= 'F' || +- i >= 'a' && i <= 'f') { +- isHex[i] = true; +- } else { +- isHex[i] = false; ++ for (int i = 0; i < ARRAY_SIZE; i++) { ++ // Control> 0-31, 127 ++ if (i < 32 || i == 127) { ++ IS_CONTROL[i] = true; ++ } ++ ++ // Separator ++ if ( i == '(' || i == ')' || i == '<' || i == '>' || i == '@' || ++ i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' || ++ i == '/' || i == '[' || i == ']' || i == '?' || i == '=' || ++ i == '{' || i == '}' || i == ' ' || i == '\t') { ++ IS_SEPARATOR[i] = true; ++ } ++ ++ // Token: Anything 0-127 that is not a control and not a separator ++ if (!IS_CONTROL[i] && !IS_SEPARATOR[i] && i < 128) { ++ IS_TOKEN[i] = true; ++ } ++ ++ // Hex: 0-9, a-f, A-F ++ if ((i >= '0' && i <='9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) { ++ IS_HEX[i] = true; ++ } ++ ++ // Not valid for request target. ++ // Combination of multiple rules from RFC7230 and RFC 3986. Must be ++ // ASCII, no controls plus a few additional characters excluded ++ if (IS_CONTROL[i] || i > 127 || ++ i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' || ++ i == '^' || i == '`' || i == '{' || i == '|' || i == '}') { ++ if (!REQUEST_TARGET_ALLOW[i]) { ++ IS_NOT_REQUEST_TARGET[i] = true; ++ } ++ } ++ ++ // Not valid for HTTP protocol ++ // "HTTP/" DIGIT "." DIGIT ++ if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) { ++ IS_HTTP_PROTOCOL[i] = true; + } + } + } +@@ -228,6 +276,7 @@ + return new MediaType(type, subtype, parameters); + } + ++ + public static String unquote(String input) { + if (input == null || input.length() < 2) { + return input; +@@ -258,24 +307,49 @@ + return result.toString(); + } + +- private static boolean isToken(int c) { ++ ++ public static boolean isToken(int c) { + // Fast for correct values, slower for incorrect ones + try { +- return isToken[c]; ++ return IS_TOKEN[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return false; + } + } + +- private static boolean isHex(int c) { +- // Fast for correct values, slower for incorrect ones ++ ++ public static boolean isHex(int c) { ++ // Fast for correct values, slower for some incorrect ones ++ try { ++ return IS_HEX[c]; ++ } catch (ArrayIndexOutOfBoundsException ex) { ++ return false; ++ } ++ } ++ ++ ++ public static boolean isNotRequestTarget(int c) { ++ // Fast for valid request target characters, slower for some incorrect ++ // ones + try { +- return isHex[c]; ++ return IS_NOT_REQUEST_TARGET[c]; ++ } catch (ArrayIndexOutOfBoundsException ex) { ++ return true; ++ } ++ } ++ ++ ++ public static boolean isHttpProtocol(int c) { ++ // Fast for valid HTTP protocol characters, slower for some incorrect ++ // ones ++ try { ++ return IS_HTTP_PROTOCOL[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return false; + } + } + ++ + // Skip any LWS and return the next char + private static int skipLws(StringReader input, boolean withReset) + throws IOException { +--- webapps/docs/changelog.xml.orig 2017-03-28 15:06:40.634389851 -0400 ++++ webapps/docs/changelog.xml 2017-03-28 15:11:46.543926068 -0400 +@@ -64,6 +64,24 @@ + 60409: When unable to complete sendfile request, ensure the + Processor will be added to the cache only once. (markt/violetagg) + ++ ++ Ensure that requests with HTTP method names that are not tokens (as ++ required by RFC 7231) are rejected with a 400 response. (markt) ++ ++ ++ Correct the HTTP header parser so that DEL is not treated as a valid ++ token character. (markt) ++ ++ ++ Add additional checks for valid characters to the HTTP request line ++ parsing so invalid request lines are rejected sooner. (markt) ++ ++ ++ 60594: Allow some invalid characters that were recently ++ restricted to be processed in requests by using the system property ++ tomcat.util.http.parser.HttpParser.requestTargetAllow. ++ (csutherl) ++ + + + +--- webapps/docs/config/systemprops.xml.orig 2017-03-28 15:06:40.635389856 -0400 ++++ webapps/docs/config/systemprops.xml 2017-03-28 15:06:40.657389967 -0400 +@@ -708,6 +708,16 @@ +

If not specified, the default value of 3 will be used.

+ + ++ ++

A string comprised of characters the server should allow even when they are not encoded. ++ These characters would normally result in a 400 status.

++

The acceptable characters for this property are: |, { ++ , and }

++

WARNING: Use of this option will expose the server to CVE-2016-6816. ++

++

If not specified, the default value of null will be used.

++
++ + + + +--- java/org/apache/tomcat/util/http/parser/LocalStrings.properties.orig 2017-03-28 15:06:40.637389866 -0400 ++++ java/org/apache/tomcat/util/http/parser/LocalStrings.properties 2017-03-28 15:06:40.657389967 -0400 +@@ -0,0 +1,16 @@ ++# 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. ++ ++httpparser.invalidRequestTargetCharacter=Character [{0}] is not allowed and will continue to be rejected. +--- test/org/apache/tomcat/util/http/parser/TestHttpParser.java.orig 2017-03-28 15:06:40.638389871 -0400 ++++ test/org/apache/tomcat/util/http/parser/TestHttpParser.java 2017-03-28 15:06:40.646389911 -0400 +@@ -0,0 +1,28 @@ ++/* ++ * 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. ++ */ ++package org.apache.tomcat.util.http.parser; ++ ++import org.junit.Assert; ++import org.junit.Test; ++ ++public class TestHttpParser { ++ ++ @Test ++ public void testTokenDel() { ++ Assert.assertFalse("DEL is not a token", HttpParser.isToken(127)); ++ } ++} +--- test/org/apache/coyote/http11/TestInternalInputBuffer.java.orig 2017-03-28 15:06:40.639389876 -0400 ++++ test/org/apache/coyote/http11/TestInternalInputBuffer.java 2017-03-28 15:06:40.642389891 -0400 +@@ -478,4 +478,61 @@ + } + + } ++ ++ ++ @Test ++ public void testInvalidMethod() { ++ ++ InvalidMethodClient client = new InvalidMethodClient(); ++ ++ client.doRequest(); ++ assertTrue(client.getResponseLine(), client.isResponse400()); ++ assertTrue(client.isResponseBodyOK()); ++ } ++ ++ ++ /** ++ * Bug 48839 test client. ++ */ ++ private class InvalidMethodClient extends SimpleHttpClient { ++ ++ private Exception doRequest() { ++ ++ Tomcat tomcat = getTomcatInstance(); ++ ++ tomcat.addContext("", TEMP_DIR); ++ ++ try { ++ tomcat.start(); ++ setPort(tomcat.getConnector().getLocalPort()); ++ ++ // Open connection ++ connect(); ++ ++ String[] request = new String[1]; ++ request[0] = ++ "GET" + (char) 0 + " /test HTTP/1.1" + CRLF + ++ "Host: localhost:8080" + CRLF + ++ "Connection: close" + CRLF + ++ CRLF; ++ ++ setRequest(request); ++ processRequest(); // blocks until response has been read ++ ++ // Close the connection ++ disconnect(); ++ } catch (Exception e) { ++ return e; ++ } ++ return null; ++ } ++ ++ @Override ++ public boolean isResponseBodyOK() { ++ if (getResponseBody() == null) { ++ return false; ++ } ++ return true; ++ } ++ } + } diff --git a/SOURCES/tomcat-7.0.69-CVE-2016-8745.patch b/SOURCES/tomcat-7.0.69-CVE-2016-8745.patch new file mode 100644 index 0000000..d0becbb --- /dev/null +++ b/SOURCES/tomcat-7.0.69-CVE-2016-8745.patch @@ -0,0 +1,163 @@ +--- java/org/apache/tomcat/util/net/NioEndpoint.java.orig 2017-03-28 14:39:31.522852483 -0400 ++++ java/org/apache/tomcat/util/net/NioEndpoint.java 2017-03-28 14:39:31.528852515 -0400 +@@ -1406,11 +1406,15 @@ + } + }catch ( IOException x ) { + if ( log.isDebugEnabled() ) log.debug("Unable to complete sendfile request:", x); +- cancelledKey(sk,SocketStatus.ERROR,false); ++ if (!calledByProcessor) { ++ cancelledKey(sk,SocketStatus.ERROR,false); ++ } + return SendfileState.ERROR; + }catch ( Throwable t ) { + log.error("",t); +- cancelledKey(sk, SocketStatus.ERROR, false); ++ if (!calledByProcessor) { ++ cancelledKey(sk, SocketStatus.ERROR, false); ++ } + return SendfileState.ERROR; + } + } +--- webapps/docs/changelog.xml.orig 2017-03-28 14:39:31.523852488 -0400 ++++ webapps/docs/changelog.xml 2017-03-28 14:41:38.105546243 -0400 +@@ -57,6 +57,16 @@ + They eventually become mixed with the numbered issues. (I.e., numbered + issues do not "pop up" wrt. others). + --> ++
++ ++ ++ ++ 60409: When unable to complete sendfile request, ensure the ++ Processor will be added to the cache only once. (markt/violetagg) ++ ++ ++ ++
+
+ + +--- test/org/apache/catalina/connector/TestSendFile.java.orig 2017-03-28 14:39:31.524852493 -0400 ++++ test/org/apache/catalina/connector/TestSendFile.java 2017-03-28 14:39:31.527852510 -0400 +@@ -22,10 +22,14 @@ + import java.io.FileInputStream; + import java.io.FileWriter; + import java.io.IOException; ++import java.util.ArrayList; + import java.util.Arrays; + import java.util.HashMap; + import java.util.List; + import java.util.Map; ++import java.util.Random; ++import java.util.concurrent.CountDownLatch; ++import java.util.concurrent.TimeUnit; + + import javax.servlet.ServletException; + import javax.servlet.http.HttpServlet; +@@ -33,6 +37,8 @@ + import javax.servlet.http.HttpServletResponse; + + import static org.junit.Assert.assertEquals; ++ ++import org.junit.Assert; + import org.junit.Test; + + import org.apache.catalina.Context; +@@ -163,4 +169,97 @@ + } + } + ++ ++ @Test ++ public void testBug60409() throws Exception { ++ Tomcat tomcat = getTomcatInstance(); ++ ++ Context ctx = tomcat.addContext("", TEMP_DIR); ++ File file = generateFile(TEMP_DIR, "", EXPECTED_CONTENT_LENGTH); ++ Tomcat.addServlet(ctx, "test", new Bug60409Servlet(file)); ++ ctx.addServletMapping("/", "test"); ++ ++ tomcat.start(); ++ ++ ByteChunk bc = new ByteChunk(); ++ getUrl("http://localhost:" + getPort() + "/test/?" + Globals.SENDFILE_SUPPORTED_ATTR ++ + "=true", bc, null); ++ ++ CountDownLatch latch = new CountDownLatch(2); ++ List exceptions = new ArrayList(); ++ new Thread( ++ new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions)) ++ .start(); ++ new Thread( ++ new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions)) ++ .start(); ++ ++ latch.await(3000, TimeUnit.MILLISECONDS); ++ ++ if (exceptions.size() > 0) { ++ Assert.fail(); ++ } ++ } ++ ++ private static final class Bug60409Servlet extends HttpServlet { ++ private static final long serialVersionUID = 1L; ++ private final File file; ++ ++ Bug60409Servlet(File file) { ++ this.file = file; ++ } ++ ++ @Override ++ protected void doGet(HttpServletRequest req, HttpServletResponse resp) ++ throws ServletException, IOException { ++ if (Boolean.valueOf(req.getParameter(Globals.SENDFILE_SUPPORTED_ATTR)).booleanValue()) { ++ resp.setContentType("'application/octet-stream"); ++ resp.setCharacterEncoding("ISO-8859-1"); ++ resp.setContentLength((int) file.length()); ++ req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, file.getAbsolutePath()); ++ req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, new Long(0)); ++ req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, new Long(file.length())); ++ file.delete(); ++ } else { ++ byte[] c = new byte[1024]; ++ Random rd = new Random(); ++ rd.nextBytes(c); ++ try { ++ Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ } ++ resp.getOutputStream().write(c); ++ } ++ } ++ ++ } ++ ++ private static final class RequestExecutor implements Runnable { ++ private final String url; ++ private final CountDownLatch latch; ++ private final List exceptions; ++ ++ RequestExecutor(String url, CountDownLatch latch, List exceptions) { ++ this.url = url; ++ this.latch = latch; ++ this.exceptions = exceptions; ++ } ++ ++ @Override ++ public void run() { ++ try { ++ ByteChunk result = new ByteChunk(); ++ int rc = getUrl(url, result, null); ++ Assert.assertEquals(HttpServletResponse.SC_OK, rc); ++ Assert.assertEquals(1024, result.getLength()); ++ } catch (Throwable e) { ++ e.printStackTrace(); ++ exceptions.add(e); ++ } finally { ++ latch.countDown(); ++ } ++ } ++ ++ } + } diff --git a/SPECS/tomcat.spec b/SPECS/tomcat.spec index dcc30ae..5165cfe 100644 --- a/SPECS/tomcat.spec +++ b/SPECS/tomcat.spec @@ -54,7 +54,7 @@ Name: tomcat Epoch: 0 Version: %{major_version}.%{minor_version}.%{micro_version} -Release: 10%{?dist} +Release: 11%{?dist} Summary: Apache Servlet/JSP Engine, RI for Servlet %{servletspec}/JSP %{jspspec} API Group: System Environment/Daemons @@ -90,6 +90,8 @@ Patch2: %{name}-7.0.54-rebase.patch Patch3: %{name}-7.0-catalina-policy.patch Patch4: %{name}-7.0.69-CVE-2016-3092.patch Patch5: %{name}-7.0.69-CVE-2016-5388.patch +Patch6: %{name}-7.0.69-CVE-2016-8745.patch +Patch7: %{name}-7.0.69-CVE-2016-6816.patch BuildArch: noarch @@ -240,6 +242,8 @@ find . -type f \( -name "*.bat" -o -name "*.class" -o -name Thumbs.db -o -name " %patch3 -p0 %patch4 -p0 %patch5 -p0 +%patch6 -p0 +%patch7 -p0 %{__ln_s} $(build-classpath jakarta-taglibs-core) webapps/examples/WEB-INF/lib/jstl.jar %{__ln_s} $(build-classpath jakarta-taglibs-standard) webapps/examples/WEB-INF/lib/standard.jar @@ -679,6 +683,10 @@ fi %attr(0644,root,root) %{_unitdir}/%{name}-jsvc.service %changelog +* Tue Mar 28 2017 Coty Sutherland - 0:7.0.69-11 +- Resolves: rhbz#1413591 CVE-2016-8745 tomcat: information disclosure due to incorrect Processor sharing +- Resolves: rhbz#1402662 CVE-2016-6816 tomcat: HTTP Request smuggling vulnerability due to permitting invalid character in HTTP requests + * Thu Aug 25 2016 Coty Sutherland - 0:7.0.69-10 - Related: rhbz#1368122