You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2019/07/24 14:51:30 UTC
[tomcat] 10/11: Fix
https://bz.apache.org/bugzilla/show_bug.cgi?id=63578
This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 3de983433578765d52179e4b1c69ad04019673b2
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jul 23 19:07:19 2019 +0100
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=63578
Various fixes to return 400 responses rather than 500 responses when the
provided request is invalid.
---
.../apache/catalina/connector/CoyoteAdapter.java | 11 +++-
.../coyote/http11/AbstractHttp11Processor.java | 46 ++++++++++------
.../apache/coyote/http11/LocalStrings.properties | 4 +-
.../connector/TestCoyoteAdapterRequestFuzzing.java | 63 +++++++++++++++++-----
.../apache/catalina/startup/SimpleHttpClient.java | 12 ++++-
test/org/apache/tomcat/unittest/TesterData.java | 37 +++++++++++++
webapps/docs/changelog.xml | 8 +++
7 files changed, 149 insertions(+), 32 deletions(-)
diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java
index 151fb48..3813073 100644
--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -816,7 +816,16 @@ public class CoyoteAdapter implements Adapter {
}
// Look for session ID in cookies and SSL session
- parseSessionCookiesId(req, request);
+ try {
+ parseSessionCookiesId(req, request);
+ } catch (IllegalArgumentException e) {
+ // Too many cookies
+ if (!response.isError()) {
+ response.setError();
+ response.sendError(400);
+ }
+ return false;
+ }
parseSessionSslId(request);
sessionID = request.getRequestedSessionId();
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Processor.java b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
index ae0bb2f..1bd0adb 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
@@ -1313,7 +1313,7 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
// Check connection header
MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION);
- if (connectionValueMB != null) {
+ if (connectionValueMB != null && !connectionValueMB.isNull()) {
ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
keepAlive = false;
@@ -1323,17 +1323,16 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
}
}
- MessageBytes expectMB = null;
if (http11) {
- expectMB = headers.getValue("expect");
- }
- if (expectMB != null) {
- if (expectMB.indexOfIgnoreCase("100-continue", 0) != -1) {
- getInputBuffer().setSwallowInput(false);
- expectation = true;
- } else {
- response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
- setErrorState(ErrorState.CLOSE_CLEAN, null);
+ MessageBytes expectMB = headers.getValue("expect");
+ if (expectMB != null && !expectMB.isNull()) {
+ if (expectMB.indexOfIgnoreCase("100-continue", 0) != -1) {
+ getInputBuffer().setSwallowInput(false);
+ expectation = true;
+ } else {
+ response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
+ setErrorState(ErrorState.CLOSE_CLEAN, null);
+ }
}
}
@@ -1342,7 +1341,7 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
MessageBytes userAgentValueMB = headers.getValue("user-agent");
// Check in the restricted list, and adjust the http11
// and keepAlive flags accordingly
- if(userAgentValueMB != null) {
+ if(userAgentValueMB != null && !userAgentValueMB.isNull()) {
String userAgentValue = userAgentValueMB.toString();
if (restrictedUserAgents.matcher(userAgentValue).matches()) {
http11 = false;
@@ -1442,8 +1441,16 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
} else {
// Not HTTP/1.1 - no Host header so generate one since
// Tomcat internals assume it is set
- hostValueMB = headers.setValue("host");
- hostValueMB.setBytes(uriB, uriBCStart + pos, slashPos - pos);
+ try {
+ hostValueMB = headers.setValue("host");
+ hostValueMB.setBytes(uriB, uriBCStart + pos, slashPos - pos);
+ } catch (IllegalStateException e) {
+ // Edge case
+ // If the request has too many headers it won't be
+ // possible to create the host header. Ignore this as
+ // processing won't reach the point where the Tomcat
+ // internals expect there to be a host header.
+ }
}
} else {
badRequest("http11processor.request.invalidScheme");
@@ -1467,7 +1474,7 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
if (http11) {
transferEncodingValueMB = headers.getValue("transfer-encoding");
}
- if (transferEncodingValueMB != null) {
+ if (transferEncodingValueMB != null && !transferEncodingValueMB.isNull()) {
String transferEncodingValue = transferEncodingValueMB.toString();
// Parse the comma separated list. "identity" codings are ignored
int startPos = 0;
@@ -1484,7 +1491,14 @@ public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
}
// Parse content-length header
- long contentLength = request.getContentLengthLong();
+ long contentLength = -1;
+ try {
+ contentLength = request.getContentLengthLong();
+ } catch (NumberFormatException e) {
+ badRequest("http11processor.request.nonNumericContentLength");
+ } catch (IllegalArgumentException e) {
+ badRequest("http11processor.request.multipleContentLength");
+ }
if (contentLength >= 0) {
if (contentDelimitation) {
// contentDelimitation being true at this point indicates that
diff --git a/java/org/apache/coyote/http11/LocalStrings.properties b/java/org/apache/coyote/http11/LocalStrings.properties
index e41e9d2..035bf71 100644
--- a/java/org/apache/coyote/http11/LocalStrings.properties
+++ b/java/org/apache/coyote/http11/LocalStrings.properties
@@ -26,10 +26,12 @@ http11processor.regexp.error=Error parsing regular expression {0}
http11processor.request.finish=Error finishing request
http11processor.request.inconsistentHosts=The host specified in the request line is not consistent with the host header
http11processor.request.invalidScheme=The HTTP request contained an absolute URI with an invalid scheme
-http11processor.request.invalidUri==The HTTP request contained an invalid URI
+http11processor.request.invalidUri=The HTTP request contained an invalid URI
http11processor.request.invalidUserInfo=The HTTP request contained an absolute URI with an invalid userinfo
+http11processor.request.multipleContentLength=The request contained multiple content-length headers
http11processor.request.multipleHosts=The request contained multiple host headers
http11processor.request.noHostHeader=The HTTP/1.1 request did not provide a host header
+http11processor.request.nonNumericContentLength=The request contained a content-length header with a non-numeric value
http11processor.request.prepare=Error preparing request
http11processor.request.process=Error processing request
http11processor.response.finish=Error finishing response
diff --git a/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java b/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java
index 453e04a..a34adc5 100644
--- a/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java
+++ b/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java
@@ -27,11 +27,13 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
+import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
import org.apache.catalina.Context;
import org.apache.catalina.servlets.DefaultServlet;
import org.apache.catalina.startup.SimpleHttpClient;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.unittest.TesterData;
/*
* Various requests, usually originating from fuzzing, that have triggered an
@@ -41,21 +43,61 @@ import org.apache.catalina.startup.TomcatBaseTest;
@RunWith(Parameterized.class)
public class TestCoyoteAdapterRequestFuzzing extends TomcatBaseTest {
- @Parameterized.Parameters(name = "{index}: uri[{0}], host[{1}], expected[{2}]")
+ private static final String VALUE_16K = TesterData.string('x', 16 * 1024);
+ // Default max header count is 100
+ private static final String HEADER_150 = TesterData.string("X-Tomcat-Test: a" + CRLF, 150);
+ // Default max header count is 200 (need to keep under maxHeaderCount as well)
+ private static final String COOKIE_250 = TesterData.string("Cookie: a=b;c=d;e=f;g=h" + CRLF, 75);
+
+ @Parameterized.Parameters(name = "{index}: requestline[{0}], expected[{2}]")
public static Collection<Object[]> parameters() {
List<Object[]> parameterSets = new ArrayList<Object[]>();
- parameterSets.add(new Object[] { "/", "lÿ#", "400" } );
- parameterSets.add(new Object[] { "*;", "", "400" } );
+ parameterSets.add(new Object[] { "GET /00 HTTP/1.1",
+ "Host: lÿ#" + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET *; HTTP/1.1",
+ "Host: localhost" + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /02 HTTP/1.1",
+ "Host: localhost" + CRLF +
+ "Content-Length: \u00A0" + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /03 HTTP/1.1",
+ "Content-Length: 1" + CRLF +
+ "Content-Length: 1" + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /04 HTTP/1.1",
+ "Transfer-Encoding: " + VALUE_16K + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /05 HTTP/1.1",
+ "Expect: " + VALUE_16K + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /06 HTTP/1.1",
+ "Connection: " + VALUE_16K + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /07 HTTP/1.1",
+ "User-Agent: " + VALUE_16K + CRLF,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /08 HTTP/1.1",
+ HEADER_150,
+ "400" } );
+ parameterSets.add(new Object[] { "GET http://host/09 HTTP/1.0",
+ HEADER_150,
+ "400" } );
+ parameterSets.add(new Object[] { "GET /10 HTTP/1.1",
+ "Host: localhost" + CRLF +
+ COOKIE_250,
+ "400" } );
return parameterSets;
}
@Parameter(0)
- public String uri;
+ public String requestLine;
@Parameter(1)
- public String host;
+ public String headers;
@Parameter(2)
public String expected;
@@ -64,6 +106,7 @@ public class TestCoyoteAdapterRequestFuzzing extends TomcatBaseTest {
@Test
public void doTest() throws Exception {
Tomcat tomcat = getTomcatInstance();
+ tomcat.getConnector().setAttribute("restrictedUserAgents", "value-not-important");
File appDir = new File("test/webapp");
Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
@@ -72,20 +115,15 @@ public class TestCoyoteAdapterRequestFuzzing extends TomcatBaseTest {
tomcat.start();
- String request =
- "GET " + uri + " HTTP/1.1" + SimpleHttpClient.CRLF +
- "Host: " + host + SimpleHttpClient.CRLF +
- SimpleHttpClient.CRLF;
-
Client client = new Client(tomcat.getConnector().getLocalPort());
- client.setRequest(new String[] {request});
+ client.setRequest(new String[] {requestLine + CRLF, headers + CRLF});
client.connect();
client.processRequest();
// Expected response
String line = client.getResponseLine();
- Assert.assertTrue(line, line.startsWith("HTTP/1.1 " + expected + " "));
+ Assert.assertTrue(line + CRLF + client.getResponseBody(), line.startsWith("HTTP/1.1 " + expected + " "));
}
@@ -93,6 +131,7 @@ public class TestCoyoteAdapterRequestFuzzing extends TomcatBaseTest {
public Client(int port) {
setPort(port);
+ setRequestPause(0);
}
@Override
diff --git a/test/org/apache/catalina/startup/SimpleHttpClient.java b/test/org/apache/catalina/startup/SimpleHttpClient.java
index 7e33c76..dd02566 100644
--- a/test/org/apache/catalina/startup/SimpleHttpClient.java
+++ b/test/org/apache/catalina/startup/SimpleHttpClient.java
@@ -28,6 +28,7 @@ import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
+import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
@@ -305,8 +306,15 @@ public abstract class SimpleHttpClient {
else {
// not using content length, so just read it line by line
String line = null;
- while ((line = readLine()) != null) {
- builder.append(line);
+ try {
+ while ((line = readLine()) != null) {
+ builder.append(line);
+ }
+ } catch (SocketException e) {
+ // Ignore
+ // May see a SocketException if the request hasn't been
+ // fully read when the connection is closed as that may
+ // trigger a TCP reset.
}
}
}
diff --git a/test/org/apache/tomcat/unittest/TesterData.java b/test/org/apache/tomcat/unittest/TesterData.java
new file mode 100644
index 0000000..fbdc72a
--- /dev/null
+++ b/test/org/apache/tomcat/unittest/TesterData.java
@@ -0,0 +1,37 @@
+/*
+ * 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.unittest;
+
+public class TesterData {
+
+ public static String string(char c, int count) {
+ StringBuilder sb = new StringBuilder(count);
+ for (int i = 0; i < count; i++) {
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+
+ public static String string(String str, int count) {
+ StringBuilder sb = new StringBuilder(str.length() * count);
+ for (int i = 0; i < count; i++) {
+ sb.append(str);
+ }
+ return sb.toString();
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 948b825..a262706 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -60,6 +60,14 @@
issues do not "pop up" wrt. others).
-->
<section name="Tomcat 7.0.97 (violetagg)">
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ <bug>63578</bug>: Improve handling of invalid requests so that 400
+ responses are returned to the client rather than 500 responses. (markt)
+ </fix>
+ </changelog>
+ </subsection>
</section>
<section name="Tomcat 7.0.96 (violetagg)">
<subsection name="Catalina">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org