File tomcat-8.0.32-CVE-2016-6816.patch of Package tomcat.4188
Index: java/org/apache/coyote/http11/LocalStrings.properties
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/LocalStrings.properties (date 1454441552000)
+++ java/org/apache/coyote/http11/LocalStrings.properties (revision )
@@ -33,8 +33,10 @@
iib.eof.error=Unexpected EOF read on the socket
iib.failedread.apr=Read failed with APR/native error code [{0}]
iib.filter.npe=You may not add a null filter
-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.readtimeout=Timeout attempting to read data from the socket
iib.requestheadertoolarge.error=Request header is too large
Index: test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- test/org/apache/catalina/valves/rewrite/TestRewriteValve.java (date 1454441552000)
+++ test/org/apache/catalina/valves/rewrite/TestRewriteValve.java (revision )
@@ -16,6 +16,8 @@
*/
package org.apache.catalina.valves.rewrite;
+import java.nio.charset.StandardCharsets;
+
import org.junit.Assert;
import org.junit.Test;
@@ -60,7 +62,62 @@
"RewriteRule /b/(.*).html$ /c/${mapa:$1|dd}", "/b/x.html", "/c/dd");
}
+ @Test
+ public void testUtf8WithBothQsFlagsRNE() throws Exception {
+ // Note %C2%A1 == \u00A1
+ // Failing to escape the redirect means UTF-8 bytes in the Location
+ // header which will be treated as if they are ISO-8859-1
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]",
+ "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null);
+ }
+
+ @Test
+ public void testUtf8WithBothQsFlagsRNEQSA() throws Exception {
+ // Note %C2%A1 == \u00A1
+ // Failing to escape the redirect means UTF-8 bytes in the Location
+ // header which will be treated as if they are ISO-8859-1
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE,QSA]",
+ "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null);
+ }
+
+ @Test
+ public void testUtf8WithOriginalQsFlagsRNE() throws Exception {
+ // Note %C2%A1 == \u00A1
+ // Failing to escape the redirect means UTF-8 bytes in the Location
+ // header which will be treated as if they are ISO-8859-1
+ doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]",
+ "/b/%C2%A1?id=%C2%A1", null);
+ }
+
+ @Test
+ public void testUtf8WithRewriteQsFlagsRNE() throws Exception {
+ // Note %C2%A1 == \u00A1
+ // Failing to escape the redirect means UTF-8 bytes in the Location
+ // header which will be treated as if they are ISO-8859-1
+ doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]",
+ "/b/%C2%A1/id=%C2%A1", null);
+ }
+
+ @Test
+ public void testUtf8FlagsRNE() throws Exception {
+ // Note %C2%A1 == \u00A1
+ // Failing to escape the redirect means UTF-8 bytes in the Location
+ // header which will be treated as if they are ISO-8859-1
+ doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", "/b/%C2%A1", null);
+ }
+
private void doTestRewrite(String config, String request, String expectedURI) throws Exception {
+ doTestRewrite(config, request, expectedURI, null);
+ }
+
+
+ private void doTestRewrite(String config, String request, String expectedURI,
+ String expectedQueryString) throws Exception {
+ doTestRewrite(config, request, expectedURI, expectedQueryString, null);
+ }
+
+ private void doTestRewrite(String config, String request, String expectedURI,
+ String expectedQueryString, String expectedAttributeValue) throws Exception {
Tomcat tomcat = getTomcatInstance();
// No file system docBase required
@@ -81,11 +138,29 @@
tomcat.start();
- ByteChunk res = getUrl("http://localhost:" + getPort() + request);
+ ByteChunk res = new ByteChunk();
+ int rc = getUrl("http://localhost:" + getPort() + request, res, null);
+ res.setCharset(StandardCharsets.UTF_8);
+ if (expectedURI == null) {
+ // Rewrite is expected to fail. Probably because invalid characters
+ // were written into the request target
+ Assert.assertEquals(400, rc);
+ } else {
- String body = res.toString();
- RequestDescriptor requestDesc = SnoopResult.parse(body);
- String requestURI = requestDesc.getRequestInfo("REQUEST-URI");
- Assert.assertEquals(expectedURI, requestURI);
+ String body = res.toString();
+ RequestDescriptor requestDesc = SnoopResult.parse(body);
+ String requestURI = requestDesc.getRequestInfo("REQUEST-URI");
+ Assert.assertEquals(expectedURI, requestURI);
+
+ if (expectedQueryString != null) {
+ String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING");
+ Assert.assertEquals(expectedQueryString, queryString);
+ }
+
+ if (expectedAttributeValue != null) {
+ String attributeValue = requestDesc.getAttribute("X-Test");
+ Assert.assertEquals(expectedAttributeValue, attributeValue);
+ }
+ }
}
}
Index: java/org/apache/coyote/http11/InternalAprInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/InternalAprInputBuffer.java (date 1454441552000)
+++ java/org/apache/coyote/http11/InternalAprInputBuffer.java (revision )
@@ -32,6 +32,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;
@@ -237,6 +238,8 @@
} else if ((buf[pos] == Constants.QUESTION)
&& (questionPos == -1)) {
questionPos = pos;
+ } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+ throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
}
pos++;
@@ -289,6 +292,8 @@
if (end == 0)
end = pos;
eol = true;
+ } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+ throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
}
pos++;
@@ -387,7 +392,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);
Index: java/org/apache/tomcat/util/http/parser/HttpParser.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/tomcat/util/http/parser/HttpParser.java (date 1454441552000)
+++ java/org/apache/tomcat/util/http/parser/HttpParser.java (revision )
@@ -34,33 +34,57 @@
*/
public class HttpParser {
- // Arrays used by isToken(), isHex()
- private static final boolean isToken[] = new boolean[128];
- private static final boolean isHex[] = new boolean[128];
+ 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];
+
static {
- // 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 == '@' ||
+ 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 == '\\' || i == '\"' ||
+ i == '/' || i == '[' || i == ']' || i == '?' || i == '=' ||
- i == '{' || i == '}') {
- isToken[i] = false;
- } else {
- isToken[i] = true;
+ i == '{' || i == '}' || i == ' ' || i == '\t') {
+ IS_SEPARATOR[i] = true;
}
- if (i >= '0' && i <= '9' || i >= 'A' && i <= 'F' ||
- i >= 'a' && i <= 'f') {
- isHex[i] = true;
- } else {
- isHex[i] = false;
+ // 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 == '}') {
+ 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;
+ }
+ }
+ }
+
public static String unquote(String input) {
if (input == null || input.length() < 2 || input.charAt(0) != '"') {
return input;
@@ -79,19 +103,19 @@
return result.toString();
}
- 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;
}
}
- static boolean isHex(int c) {
+ public static boolean isHex(int c) {
// Fast for correct values, slower for incorrect ones
try {
- return isHex[c];
+ return IS_HEX[c];
} catch (ArrayIndexOutOfBoundsException ex) {
return false;
}
@@ -383,6 +407,27 @@
return result;
}
+
+ public static boolean isNotRequestTarget(int c) {
+ // Fast for valid request target characters, slower for some incorrect
+ // ones
+ try {
+ 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;
+ }
+ }
/**
* Skips all characters until EOF or the specified target is found. Normally
Index: java/org/apache/coyote/http11/AbstractNioInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/AbstractNioInputBuffer.java (date 1454441552000)
+++ java/org/apache/coyote/http11/AbstractNioInputBuffer.java (revision )
@@ -21,6 +21,7 @@
import org.apache.coyote.Request;
import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.parser.HttpParser;
public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> {
@@ -231,6 +232,8 @@
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++;
}
@@ -280,6 +283,8 @@
} else if ((buf[pos] == Constants.QUESTION)
&& (parsingRequestLineQPos == -1)) {
parsingRequestLineQPos = pos;
+ } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+ throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
}
pos++;
}
@@ -331,6 +336,8 @@
if (end == 0)
end = pos;
parsingRequestLineEol = true;
+ } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+ throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
}
pos++;
}
@@ -471,7 +478,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;
Index: java/org/apache/coyote/http11/InternalInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/InternalInputBuffer.java (date 1454441552000)
+++ java/org/apache/coyote/http11/InternalInputBuffer.java (revision )
@@ -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;
@@ -199,6 +200,8 @@
} else if ((buf[pos] == Constants.QUESTION)
&& (questionPos == -1)) {
questionPos = pos;
+ } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+ throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
}
pos++;
@@ -250,6 +253,8 @@
if (end == 0)
end = pos;
eol = true;
+ } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+ throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
}
pos++;
@@ -348,7 +353,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);
Index: java/org/apache/coyote/http11/AbstractInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/AbstractInputBuffer.java (date 1454441552000)
+++ java/org/apache/coyote/http11/AbstractInputBuffer.java (revision )
@@ -30,62 +30,10 @@
public abstract class AbstractInputBuffer<S> 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);
/**