File tomcat-9.0-CVE-2023-46589.patch of Package tomcat.32131
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/BadRequestException.java
===================================================================
--- /dev/null
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/BadRequestException.java
@@ -0,0 +1,68 @@
+/*
+ * 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.catalina.connector;
+
+import java.io.IOException;
+
+/**
+ * Extend IOException to identify it as being caused by a bad request from a remote client.
+ */
+public class BadRequestException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ------------------------------------------------------------ Constructors
+
+ /**
+ * Construct a new BadRequestException with no other information.
+ */
+ public BadRequestException() {
+ super();
+ }
+
+
+ /**
+ * Construct a new BadRequestException for the specified message.
+ *
+ * @param message Message describing this exception
+ */
+ public BadRequestException(String message) {
+ super(message);
+ }
+
+
+ /**
+ * Construct a new BadRequestException for the specified throwable.
+ *
+ * @param throwable Throwable that caused this exception
+ */
+ public BadRequestException(Throwable throwable) {
+ super(throwable);
+ }
+
+
+ /**
+ * Construct a new BadRequestException for the specified message and throwable.
+ *
+ * @param message Message describing this exception
+ * @param throwable Throwable that caused this exception
+ */
+ public BadRequestException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+}
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/ClientAbortException.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/connector/ClientAbortException.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/ClientAbortException.java
@@ -16,15 +16,13 @@
*/
package org.apache.catalina.connector;
-import java.io.IOException;
-
/**
* Wrap an IOException identifying it as being caused by an abort
* of a request by a remote client.
*
* @author Glenn L. Nielsen
*/
-public final class ClientAbortException extends IOException {
+public final class ClientAbortException extends BadRequestException {
private static final long serialVersionUID = 1L;
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/InputBuffer.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/connector/InputBuffer.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/InputBuffer.java
@@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHa
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ReadListener;
+import javax.servlet.RequestDispatcher;
import org.apache.catalina.security.SecurityUtil;
import org.apache.coyote.ActionCode;
@@ -319,6 +320,7 @@ public class InputBuffer extends Reader
*
* @throws IOException An underlying IOException occurred
*/
+ @SuppressWarnings("deprecation")
@Override
public int realReadBytes() throws IOException {
if (closed) {
@@ -334,9 +336,24 @@ public class InputBuffer extends Reader
try {
return coyoteRequest.doRead(this);
+ } catch (BadRequestException bre) {
+ // Set flag used by asynchronous processing to detect errors on non-container threads
+ coyoteRequest.setErrorException(bre);
+ // In synchronous processing, this exception may be swallowed by the application so set error flags here.
+ coyoteRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, bre);
+ coyoteRequest.getResponse().setStatus(400);
+ coyoteRequest.getResponse().setError();
+ // Make the exception visible to the application
+ throw bre;
} catch (IOException ioe) {
- // An IOException on a read is almost always due to
- // the remote client aborting the request.
+ // Set flag used by asynchronous processing to detect errors on non-container threads
+ coyoteRequest.setErrorException(ioe);
+ // In synchronous processing, this exception may be swallowed by the application so set error flags here.
+ coyoteRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
+ coyoteRequest.getResponse().setStatus(400);
+ coyoteRequest.getResponse().setError();
+ // Any other IOException on a read is almost always due to the remote client aborting the request.
+ // Make the exception visible to the application
throw new ClientAbortException(ioe);
}
}
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/core/ApplicationDispatcher.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/core/ApplicationDispatcher.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/core/ApplicationDispatcher.java
@@ -41,7 +41,7 @@ import org.apache.catalina.AsyncDispatch
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Wrapper;
-import org.apache.catalina.connector.ClientAbortException;
+import org.apache.catalina.connector.BadRequestException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
@@ -712,7 +712,7 @@ final class ApplicationDispatcher implem
filterChain.doFilter(request, response);
}
// Servlet Service Method is called by the FilterChain
- } catch (ClientAbortException e) {
+ } catch (BadRequestException e) {
ioException = e;
} catch (IOException e) {
wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
@@ -725,7 +725,7 @@ final class ApplicationDispatcher implem
wrapper.unavailable(e);
} catch (ServletException e) {
Throwable rootCause = StandardWrapper.getRootCause(e);
- if (!(rootCause instanceof ClientAbortException)) {
+ if (!(rootCause instanceof BadRequestException)) {
wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
wrapper.getName()), rootCause);
}
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/core/StandardWrapperValve.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/core/StandardWrapperValve.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/core/StandardWrapperValve.java
@@ -33,7 +33,7 @@ import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.LifecycleException;
-import org.apache.catalina.connector.ClientAbortException;
+import org.apache.catalina.connector.BadRequestException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
@@ -204,7 +204,7 @@ final class StandardWrapperValve
}
}
- } catch (ClientAbortException | CloseNowException e) {
+ } catch (BadRequestException | CloseNowException e) {
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
@@ -240,7 +240,7 @@ final class StandardWrapperValve
// do not want to do exception(request, response, e) processing
} catch (ServletException e) {
Throwable rootCause = StandardWrapper.getRootCause(e);
- if (!(rootCause instanceof ClientAbortException)) {
+ if (!(rootCause instanceof BadRequestException)) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceExceptionRoot",
wrapper.getName(), context.getName(), e.getMessage()),
Index: apache-tomcat-9.0.36-src/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
+++ apache-tomcat-9.0.36-src/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
@@ -429,6 +429,83 @@ public class TestChunkedInputFilter exte
}
}
+
+ @Test
+ public void testTrailerHeaderNameNotTokenThrowException() throws Exception {
+ doTestTrailerHeaderNameNotToken(false);
+ }
+
+ @Test
+ public void testTrailerHeaderNameNotTokenSwallowException() throws Exception {
+ doTestTrailerHeaderNameNotToken(true);
+ }
+
+ private void doTestTrailerHeaderNameNotToken(boolean swallowException) throws Exception {
+
+ // Setup Tomcat instance
+ Tomcat tomcat = getTomcatInstance();
+
+ // No file system docBase required
+ Context ctx = tomcat.addContext("", null);
+
+ Tomcat.addServlet(ctx, "servlet", new SwallowBodyServlet(swallowException));
+ ctx.addServletMappingDecoded("/", "servlet");
+
+ tomcat.start();
+
+ String[] request = new String[]{
+ "POST / HTTP/1.1" + SimpleHttpClient.CRLF +
+ "Host: localhost" + SimpleHttpClient.CRLF +
+ "Transfer-encoding: chunked" + SimpleHttpClient.CRLF +
+ "Content-Type: application/x-www-form-urlencoded" + SimpleHttpClient.CRLF +
+ "Connection: close" + SimpleHttpClient.CRLF +
+ SimpleHttpClient.CRLF +
+ "3" + SimpleHttpClient.CRLF +
+ "a=0" + SimpleHttpClient.CRLF +
+ "4" + SimpleHttpClient.CRLF +
+ "&b=1" + SimpleHttpClient.CRLF +
+ "0" + SimpleHttpClient.CRLF +
+ "x@trailer: Test" + SimpleHttpClient.CRLF +
+ SimpleHttpClient.CRLF };
+
+ TrailerClient client = new TrailerClient(tomcat.getConnector().getLocalPort());
+ client.setRequest(request);
+
+ client.connect();
+ client.processRequest();
+ // Expected to fail because of invalid trailer header name
+ Assert.assertTrue(client.getResponseLine(), client.isResponse400());
+ }
+
+ private static class SwallowBodyServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ private final boolean swallowException;
+
+ SwallowBodyServlet(boolean swallowException) {
+ this.swallowException = swallowException;
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ resp.setContentType("text/plain");
+ PrintWriter pw = resp.getWriter();
+
+ // Read the body
+ InputStream is = req.getInputStream();
+ try {
+ while (is.read() > -1) {
+ }
+ pw.write("OK");
+ } catch (IOException ioe) {
+ if (!swallowException) {
+ throw ioe;
+ }
+ }
+ }
+ }
+
private static class EchoHeaderServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
Index: apache-tomcat-9.0.36-src/webapps/docs/changelog.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/webapps/docs/changelog.xml
+++ apache-tomcat-9.0.36-src/webapps/docs/changelog.xml
@@ -115,6 +115,11 @@
Improve handling of failures within <code>recycle()</code> methods.
(markt)
</add>
+ <fix>
+ Ensure that an <code>IOException</code> during the reading of the
+ request triggers always error handling, regardless of whether the
+ application swallows the exception. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
Index: apache-tomcat-9.0.36-src/java/org/apache/coyote/Request.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/coyote/Request.java
+++ apache-tomcat-9.0.36-src/java/org/apache/coyote/Request.java
@@ -160,6 +160,11 @@ public final class Request {
private boolean sendfile = true;
+ /**
+ * Holds request body reading error exception.
+ */
+ private Exception errorException = null;
+
volatile ReadListener listener;
public ReadListener getReadListener() {
@@ -555,6 +560,33 @@ public final class Request {
return n;
}
+ // -------------------- Error tracking --------------------
+
+ /**
+ * Set the error Exception that occurred during the writing of the response
+ * processing.
+ *
+ * @param ex The exception that occurred
+ */
+ public void setErrorException(Exception ex) {
+ errorException = ex;
+ }
+
+
+ /**
+ * Get the Exception that occurred during the writing of the response.
+ *
+ * @return The exception that occurred
+ */
+ public Exception getErrorException() {
+ return errorException;
+ }
+
+
+ public boolean isExceptionPresent() {
+ return errorException != null;
+ }
+
// -------------------- debug --------------------