File tomcat-8.0-CVE-2023-46589.patch of Package tomcat.37363
Index: apache-tomcat-8.0.53-src/java/org/apache/catalina/connector/InputBuffer.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/connector/InputBuffer.java
+++ apache-tomcat-8.0.53-src/java/org/apache/catalina/connector/InputBuffer.java
@@ -25,6 +25,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;
@@ -326,6 +327,7 @@ public class InputBuffer extends Reader
*
* @throws IOException An underlying IOException occurred
*/
+ @SuppressWarnings("deprecation")
@Override
public int realReadBytes(byte cbuf[], int off, int len)
throws IOException {
@@ -341,19 +343,35 @@ public class InputBuffer extends Reader
state = BYTE_STATE;
}
- int result = coyoteRequest.doRead(bb);
-
- return result;
-
+ try {
+ return coyoteRequest.doRead(bb);
+ } 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) {
+ // 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);
+ }
}
public int readByte()
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
return bb.substract();
}
@@ -362,9 +380,7 @@ public class InputBuffer extends Reader
public int read(byte[] b, int off, int len)
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
return bb.substract(b, off, len);
}
@@ -439,9 +455,7 @@ public class InputBuffer extends Reader
public int read()
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
return cb.substract();
}
@@ -451,9 +465,7 @@ public class InputBuffer extends Reader
public int read(char[] cbuf)
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
return read(cbuf, 0, cbuf.length);
}
@@ -463,9 +475,7 @@ public class InputBuffer extends Reader
public int read(char[] cbuf, int off, int len)
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
return cb.substract(cbuf, off, len);
}
@@ -475,10 +485,7 @@ public class InputBuffer extends Reader
public long skip(long n)
throws IOException {
-
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
if (n < 0) {
throw new IllegalArgumentException();
@@ -514,9 +521,8 @@ public class InputBuffer extends Reader
public boolean ready()
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
+
if (state == INITIAL_STATE) {
state = CHAR_STATE;
}
@@ -534,9 +540,7 @@ public class InputBuffer extends Reader
public void mark(int readAheadLimit)
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
if (cb.getLength() <= 0) {
cb.setOffset(0);
@@ -559,9 +563,7 @@ public class InputBuffer extends Reader
public void reset()
throws IOException {
- if (closed) {
- throw new IOException(sm.getString("inputBuffer.streamClosed"));
- }
+ throwIfClosed();
if (state == CHAR_STATE) {
if (markPos < 0) {
@@ -623,4 +625,11 @@ public class InputBuffer extends Reader
}
+ private void throwIfClosed() throws IOException {
+ if (closed) {
+ IOException ioe = new IOException(sm.getString("inputBuffer.streamClosed"));
+ coyoteRequest.setErrorException(ioe);
+ throw ioe;
+ }
+ }
}
Index: apache-tomcat-8.0.53-src/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
+++ apache-tomcat-8.0.53-src/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
@@ -423,6 +423,82 @@ 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-8.0.53-src/java/org/apache/catalina/connector/BadRequestException.java
===================================================================
--- /dev/null
+++ apache-tomcat-8.0.53-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-8.0.53-src/java/org/apache/catalina/connector/ClientAbortException.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/connector/ClientAbortException.java
+++ apache-tomcat-8.0.53-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
+ * Wrap an BadRequestException 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-8.0.53-src/java/org/apache/catalina/core/ApplicationDispatcher.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/core/ApplicationDispatcher.java
+++ apache-tomcat-8.0.53-src/java/org/apache/catalina/core/ApplicationDispatcher.java
@@ -40,7 +40,7 @@ import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.InstanceEvent;
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;
@@ -718,7 +718,7 @@ final class ApplicationDispatcher implem
// Servlet Service Method is called by the FilterChain
support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
servlet, request, response);
- } catch (ClientAbortException e) {
+ } catch (BadRequestException e) {
support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
servlet, request, response);
ioException = e;
@@ -739,7 +739,7 @@ final class ApplicationDispatcher implem
support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
servlet, request, response);
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-8.0.53-src/java/org/apache/catalina/connector/CoyoteAdapter.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ apache-tomcat-8.0.53-src/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -322,6 +322,10 @@ public class CoyoteAdapter implements Ad
readListener != null) {
readListener.onAllDataRead();
}
+ // User code may have swallowed an IOException
+ if (response.getCoyoteResponse().isExceptionPresent()) {
+ throw response.getCoyoteResponse().getErrorException();
+ }
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
writeListener.onError(t);
@@ -344,6 +348,10 @@ public class CoyoteAdapter implements Ad
if (request.isFinished() && req.sendAllDataReadEvent()) {
readListener.onAllDataRead();
}
+ // User code may have swallowed an IOException
+ if (request.getCoyoteRequest().isExceptionPresent()) {
+ throw request.getCoyoteRequest().getErrorException();
+ }
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
readListener.onError(t);
Index: apache-tomcat-8.0.53-src/java/org/apache/catalina/connector/OutputBuffer.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/connector/OutputBuffer.java
+++ apache-tomcat-8.0.53-src/java/org/apache/catalina/connector/OutputBuffer.java
@@ -393,6 +393,7 @@ public class OutputBuffer extends Writer
// An IOException on a write is almost always due to
// the remote client aborting the request. Wrap this
// so that it can be handled better by the error dispatcher.
+ coyoteResponse.setErrorException(e);
throw new ClientAbortException(e);
}
}
Index: apache-tomcat-8.0.53-src/java/org/apache/coyote/Request.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/coyote/Request.java
+++ apache-tomcat-8.0.53-src/java/org/apache/coyote/Request.java
@@ -142,6 +142,10 @@ public final class Request {
private final RequestInfo reqProcessorMX=new RequestInfo(this);
+ /**
+ * Holds request body reading error exception.
+ */
+ private Exception errorException = null;
volatile ReadListener listener;
@@ -480,6 +484,32 @@ 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 --------------------
Index: apache-tomcat-8.0.53-src/java/org/apache/coyote/Response.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/coyote/Response.java
+++ apache-tomcat-8.0.53-src/java/org/apache/coyote/Response.java
@@ -21,6 +21,7 @@ import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.WriteListener;
@@ -111,15 +112,46 @@ public final class Response {
private long commitTime = -1;
/**
- * Holds request error exception.
+ * Holds response writing error exception.
*/
- Exception errorException = null;
+ private Exception errorException = null;
/**
* Has the charset been explicitly set.
*/
boolean charsetSet = false;
+ /**
+ * With the introduction of async processing and the possibility of
+ * non-container threads calling sendError() tracking the current error
+ * state and ensuring that the correct error page is called becomes more
+ * complicated. This state attribute helps by tracking the current error
+ * state and informing callers that attempt to change state if the change
+ * was successful or if another thread got there first.
+ *
+ * <pre>
+ * The state machine is very simple:
+ *
+ * 0 - NONE
+ * 1 - NOT_REPORTED
+ * 2 - REPORTED
+ *
+ *
+ * -->---->-- >NONE
+ * | | |
+ * | | | setError()
+ * ^ ^ |
+ * | | \|/
+ * | |-<-NOT_REPORTED
+ * | |
+ * ^ | report()
+ * | |
+ * | \|/
+ * |----<----REPORTED
+ * </pre>
+ */
+ private final AtomicInteger errorState = new AtomicInteger(0);
+
Request req;
// ------------------------------------------------------------- Properties
@@ -256,7 +288,37 @@ public final class Response {
public boolean isExceptionPresent() {
- return ( errorException != null );
+ return errorException != null;
+ }
+
+
+ /**
+ * Set the error flag.
+ *
+ * @return <code>false</code> if the error flag was already set
+ */
+ public boolean setError() {
+ return errorState.compareAndSet(0, 1);
+ }
+
+
+ /**
+ * Error flag accessor.
+ *
+ * @return <code>true</code> if the response has encountered an error
+ */
+ public boolean isError() {
+ return errorState.get() > 0;
+ }
+
+
+ public boolean isErrorReportRequired() {
+ return errorState.get() == 1;
+ }
+
+
+ public boolean setErrorReported() {
+ return errorState.compareAndSet(1, 2);
}
@@ -507,6 +569,7 @@ public final class Response {
commited = false;
commitTime = -1;
errorException = null;
+ errorState.set(0);
headers.clear();
// Servlet 3.1 non-blocking write listener
listener = null;
Index: apache-tomcat-8.0.53-src/java/org/apache/catalina/connector/Response.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/connector/Response.java
+++ apache-tomcat-8.0.53-src/java/org/apache/catalina/connector/Response.java
@@ -34,7 +34,6 @@ import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Vector;
-import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletOutputStream;
import javax.servlet.SessionTrackingMode;
@@ -182,38 +181,6 @@ public class Response implements HttpSer
private boolean isCharacterEncodingSet = false;
/**
- * With the introduction of async processing and the possibility of
- * non-container threads calling sendError() tracking the current error
- * state and ensuring that the correct error page is called becomes more
- * complicated. This state attribute helps by tracking the current error
- * state and informing callers that attempt to change state if the change
- * was successful or if another thread got there first.
- *
- * <pre>
- * The state machine is very simple:
- *
- * 0 - NONE
- * 1 - NOT_REPORTED
- * 2 - REPORTED
- *
- *
- * -->---->-- >NONE
- * | | |
- * | | | setError()
- * ^ ^ |
- * | | \|/
- * | |-<-NOT_REPORTED
- * | |
- * ^ | report()
- * | |
- * | \|/
- * |----<----REPORTED
- * </pre>
- */
- private final AtomicInteger errorState = new AtomicInteger(0);
-
-
- /**
* Using output stream flag.
*/
protected boolean usingOutputStream = false;
@@ -251,7 +218,6 @@ public class Response implements HttpSer
usingWriter = false;
appCommitted = false;
included = false;
- errorState.set(0);
isCharacterEncodingSet = false;
if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) {
@@ -399,7 +365,7 @@ public class Response implements HttpSer
* Set the error flag.
*/
public boolean setError() {
- boolean result = errorState.compareAndSet(0, 1);
+ boolean result = getCoyoteResponse().setError();
if (result) {
Wrapper wrapper = getRequest().getWrapper();
if (wrapper != null) {
@@ -414,17 +380,17 @@ public class Response implements HttpSer
* Error flag accessor.
*/
public boolean isError() {
- return errorState.get() > 0;
+ return getCoyoteResponse().isError();
}
public boolean isErrorReportRequired() {
- return errorState.get() == 1;
+ return getCoyoteResponse().isErrorReportRequired();
}
public boolean setErrorReported() {
- return errorState.compareAndSet(1, 2);
+ return getCoyoteResponse().setErrorReported();
}