Version in base suite: 9.0.43-2~deb11u9 Base version: tomcat9_9.0.43-2~deb11u9 Target version: tomcat9_9.0.43-2~deb11u10 Base file: /srv/ftp-master.debian.org/ftp/pool/main/t/tomcat9/tomcat9_9.0.43-2~deb11u9.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/t/tomcat9/tomcat9_9.0.43-2~deb11u10.dsc changelog | 21 + patches/CVE-2023-46589.patch | 621 +++++++++++++++++++++++++++++++++++++++++++ patches/CVE-2024-23672.patch | 220 +++++++++++++++ patches/CVE-2024-24549.patch | 46 +++ patches/series | 3 5 files changed, 911 insertions(+) diff -Nru tomcat9-9.0.43/debian/changelog tomcat9-9.0.43/debian/changelog --- tomcat9-9.0.43/debian/changelog 2023-10-16 12:51:43.000000000 +0000 +++ tomcat9-9.0.43/debian/changelog 2024-04-18 20:17:07.000000000 +0000 @@ -1,3 +1,24 @@ +tomcat9 (9.0.43-2~deb11u10) bullseye-security; urgency=high + + * Team upload. + * Fix CVE-2023-46589: + Improper Input Validation vulnerability in Apache Tomcat. Tomcat 10 did not + correctly parse HTTP trailer headers. A trailer header that exceeded the + header size limit could cause Tomcat to treat a single request as multiple + requests leading to the possibility of request smuggling when behind a + reverse proxy. + * Fix CVE-2024-24549: + Denial of Service due to improper input validation vulnerability for + HTTP/2. When processing an HTTP/2 request, if the request exceeded any of + the configured limits for headers, the associated HTTP/2 stream was not + reset until after all of the headers had been processed. + * Fix CVE-2024-23672: + Denial of Service via incomplete cleanup vulnerability. It was possible for + WebSocket clients to keep WebSocket connections open leading to increased + resource consumption. + + -- Markus Koschany Thu, 18 Apr 2024 22:17:07 +0200 + tomcat9 (9.0.43-2~deb11u9) bullseye-security; urgency=high * More HTTP/2 overhead protection adjustments diff -Nru tomcat9-9.0.43/debian/patches/CVE-2023-46589.patch tomcat9-9.0.43/debian/patches/CVE-2023-46589.patch --- tomcat9-9.0.43/debian/patches/CVE-2023-46589.patch 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.43/debian/patches/CVE-2023-46589.patch 2024-04-18 20:17:07.000000000 +0000 @@ -0,0 +1,621 @@ +From: Markus Koschany +Date: Wed, 3 Apr 2024 17:14:47 +0200 +Subject: CVE-2023-46589 + +Bug-Debian: https://bugs.debian.org/1057082 +Origin: https://github.com/apache/tomcat/commit/b5776d769bffeade865061bc8ecbeb2b56167b08 +--- + .../catalina/connector/BadRequestException.java | 68 +++++++++++++++++++ + .../catalina/connector/ClientAbortException.java | 4 +- + .../apache/catalina/connector/CoyoteAdapter.java | 8 +++ + .../org/apache/catalina/connector/InputBuffer.java | 77 +++++++++++----------- + .../apache/catalina/connector/OutputBuffer.java | 1 + + .../catalina/core/ApplicationDispatcher.java | 10 +-- + .../apache/catalina/core/StandardWrapperValve.java | 12 ++-- + java/org/apache/coyote/Request.java | 33 ++++++++++ + java/org/apache/coyote/Response.java | 11 ++-- + .../http11/filters/TestChunkedInputFilter.java | 77 ++++++++++++++++++++++ + webapps/docs/changelog.xml | 11 ++++ + 11 files changed, 255 insertions(+), 57 deletions(-) + create mode 100644 java/org/apache/catalina/connector/BadRequestException.java + +diff --git a/java/org/apache/catalina/connector/BadRequestException.java b/java/org/apache/catalina/connector/BadRequestException.java +new file mode 100644 +index 0000000..71a792d +--- /dev/null ++++ b/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); ++ } ++} +diff --git a/java/org/apache/catalina/connector/ClientAbortException.java b/java/org/apache/catalina/connector/ClientAbortException.java +index fa469f9..a3ba607 100644 +--- a/java/org/apache/catalina/connector/ClientAbortException.java ++++ b/java/org/apache/catalina/connector/ClientAbortException.java +@@ -16,15 +16,13 @@ + */ + package org.apache.catalina.connector; + +-import java.io.IOException; +- + /** + * Extend IOException to identify 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; + +diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java +index 2288451..577da04 100644 +--- a/java/org/apache/catalina/connector/CoyoteAdapter.java ++++ b/java/org/apache/catalina/connector/CoyoteAdapter.java +@@ -183,6 +183,10 @@ public class CoyoteAdapter implements Adapter { + 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); + // Need to trigger the call to AbstractProcessor.setErrorState() +@@ -210,6 +214,10 @@ public class CoyoteAdapter implements Adapter { + 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); + // Need to trigger the call to AbstractProcessor.setErrorState() +diff --git a/java/org/apache/catalina/connector/InputBuffer.java b/java/org/apache/catalina/connector/InputBuffer.java +index 4da5b22..d320696 100644 +--- a/java/org/apache/catalina/connector/InputBuffer.java ++++ b/java/org/apache/catalina/connector/InputBuffer.java +@@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap; + 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,18 +336,31 @@ 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); + } + } + + + public int readByte() throws IOException { +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + + if (checkByteBufferEof()) { + return -1; +@@ -355,9 +370,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(); + + if (checkByteBufferEof()) { + return -1; +@@ -380,9 +393,7 @@ public class InputBuffer extends Reader + * @throws IOException if an input or output exception has occurred + */ + public int read(ByteBuffer to) throws IOException { +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + + if (checkByteBufferEof()) { + return -1; +@@ -436,10 +447,7 @@ public class InputBuffer extends Reader + + @Override + public int read() throws IOException { +- +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + + if (checkCharBufferEof()) { + return -1; +@@ -450,21 +458,14 @@ public class InputBuffer extends Reader + + @Override + public int read(char[] cbuf) throws IOException { +- +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } +- ++ throwIfClosed(); + return read(cbuf, 0, cbuf.length); + } + + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { +- +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + + if (checkCharBufferEof()) { + return -1; +@@ -477,9 +478,7 @@ public class InputBuffer extends Reader + + @Override + public long skip(long n) throws IOException { +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + + if (n < 0) { + throw new IllegalArgumentException(); +@@ -505,9 +504,7 @@ public class InputBuffer extends Reader + + @Override + public boolean ready() throws IOException { +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + if (state == INITIAL_STATE) { + state = CHAR_STATE; + } +@@ -524,9 +521,7 @@ public class InputBuffer extends Reader + @Override + public void mark(int readAheadLimit) throws IOException { + +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + + if (cb.remaining() <= 0) { + clear(cb); +@@ -544,15 +539,15 @@ public class InputBuffer extends Reader + @Override + public void reset() throws IOException { + +- if (closed) { +- throw new IOException(sm.getString("inputBuffer.streamClosed")); +- } ++ throwIfClosed(); + + if (state == CHAR_STATE) { + if (markPos < 0) { + clear(cb); + markPos = -1; +- throw new IOException(); ++ IOException ioe = new IOException(); ++ coyoteRequest.setErrorException(ioe); ++ throw ioe; + } else { + cb.position(markPos); + } +@@ -562,6 +557,14 @@ 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; ++ } ++ } ++ + public void checkConverter() throws IOException { + if (conv != null) { + return; +diff --git a/java/org/apache/catalina/connector/OutputBuffer.java b/java/org/apache/catalina/connector/OutputBuffer.java +index 5fa8fc2..2d5a6fe 100644 +--- a/java/org/apache/catalina/connector/OutputBuffer.java ++++ b/java/org/apache/catalina/connector/OutputBuffer.java +@@ -348,6 +348,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); + } + } +diff --git a/java/org/apache/catalina/core/ApplicationDispatcher.java b/java/org/apache/catalina/core/ApplicationDispatcher.java +index b846919..dcc23b7 100644 +--- a/java/org/apache/catalina/core/ApplicationDispatcher.java ++++ b/java/org/apache/catalina/core/ApplicationDispatcher.java +@@ -41,7 +41,7 @@ import org.apache.catalina.AsyncDispatcher; + 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; +@@ -710,7 +710,7 @@ final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher + 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", +@@ -723,9 +723,9 @@ final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher + wrapper.unavailable(e); + } catch (ServletException e) { + Throwable rootCause = StandardWrapper.getRootCause(e); +- if (!(rootCause instanceof ClientAbortException)) { +- wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", +- wrapper.getName()), rootCause); ++ if (!(rootCause instanceof BadRequestException)) { ++ wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), ++ rootCause); + } + servletException = e; + } catch (RuntimeException e) { +diff --git a/java/org/apache/catalina/core/StandardWrapperValve.java b/java/org/apache/catalina/core/StandardWrapperValve.java +index 8d2f54b..ce9a548 100644 +--- a/java/org/apache/catalina/core/StandardWrapperValve.java ++++ b/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,11 +240,9 @@ 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)) { +- container.getLogger().error(sm.getString( +- "standardWrapper.serviceExceptionRoot", +- wrapper.getName(), context.getName(), e.getMessage()), +- rootCause); ++ if (!(rootCause instanceof BadRequestException)) { ++ container.getLogger().error(sm.getString("standardWrapper.serviceExceptionRoot", wrapper.getName(), ++ context.getName(), e.getMessage()), rootCause); + } + throwable = e; + exception(request, response, e); +diff --git a/java/org/apache/coyote/Request.java b/java/org/apache/coyote/Request.java +index eea1487..72ca5c9 100644 +--- a/java/org/apache/coyote/Request.java ++++ b/java/org/apache/coyote/Request.java +@@ -161,6 +161,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() { +@@ -565,6 +570,34 @@ public final class Request { + } + + ++ // -------------------- 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 -------------------- + + @Override +diff --git a/java/org/apache/coyote/Response.java b/java/org/apache/coyote/Response.java +index b9f22b7..c7943b4 100644 +--- a/java/org/apache/coyote/Response.java ++++ b/java/org/apache/coyote/Response.java +@@ -125,9 +125,9 @@ public final class Response { + private long commitTime = -1; + + /** +- * Holds request error exception. ++ * Holds response writing error exception. + */ +- Exception errorException = null; ++ private Exception errorException = null; + + /** + * With the introduction of async processing and the possibility of +@@ -275,7 +275,8 @@ public final class Response { + // -----------------Error State -------------------- + + /** +- * Set the error Exception that occurred during request processing. ++ * Set the error Exception that occurred during the writing of the response ++ * processing. + * + * @param ex The exception that occurred + */ +@@ -285,7 +286,7 @@ public final class Response { + + + /** +- * Get the Exception that occurred during request processing. ++ * Get the Exception that occurred during the writing of the response. + * + * @return The exception that occurred + */ +@@ -295,7 +296,7 @@ public final class Response { + + + public boolean isExceptionPresent() { +- return ( errorException != null ); ++ return errorException != null; + } + + +diff --git a/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java b/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java +index 16a5894..4bb896a 100644 +--- a/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java ++++ b/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java +@@ -429,6 +429,83 @@ public class TestChunkedInputFilter extends TomcatBaseTest { + } + } + ++ ++ @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; + +diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml +index 1815ab5..26d2754 100644 +--- a/webapps/docs/changelog.xml ++++ b/webapps/docs/changelog.xml +@@ -1351,6 +1351,12 @@ + + Improve validation of storage location when using FileStore. (markt) + ++ ++ Make the non-blocking I/O error handling more robust by handling the ++ case where the application code swallows an IOException in ++ WriteListener.onWritePossible() and ++ ReadListener.onDataAvailable(). (markt) ++ + + + +@@ -2082,6 +2088,11 @@ + the authenticated Principal is not cached in the session when caching is + disabled. This is the fix for CVE-2019-17563. (markt/kkolinko) + ++ ++ Ensure that an IOException during the reading of the ++ request triggers always error handling, regardless of whether the ++ application swallows the exception. (markt) ++ + + + diff -Nru tomcat9-9.0.43/debian/patches/CVE-2024-23672.patch tomcat9-9.0.43/debian/patches/CVE-2024-23672.patch --- tomcat9-9.0.43/debian/patches/CVE-2024-23672.patch 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.43/debian/patches/CVE-2024-23672.patch 2024-04-18 20:17:07.000000000 +0000 @@ -0,0 +1,220 @@ +From: Markus Koschany +Date: Wed, 3 Apr 2024 17:55:51 +0200 +Subject: CVE-2024-23672 + +Bug-Debian: https://bugs.debian.org/1066877 +Origin: https://github.com/apache/tomcat/commit/0052b374684b613b0c849899b325ebe334ac6501 +--- + java/org/apache/tomcat/websocket/Constants.java | 6 ++ + java/org/apache/tomcat/websocket/WsSession.java | 66 ++++++++++++++++++++-- + .../tomcat/websocket/WsWebSocketContainer.java | 9 ++- + .../tomcat/websocket/server/WsServerContainer.java | 3 +- + webapps/docs/web-socket-howto.xml | 7 +++ + 5 files changed, 82 insertions(+), 9 deletions(-) + +diff --git a/java/org/apache/tomcat/websocket/Constants.java b/java/org/apache/tomcat/websocket/Constants.java +index 466a541..9b76508 100644 +--- a/java/org/apache/tomcat/websocket/Constants.java ++++ b/java/org/apache/tomcat/websocket/Constants.java +@@ -19,6 +19,7 @@ package org.apache.tomcat.websocket; + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; ++import java.util.concurrent.TimeUnit; + + import javax.websocket.Extension; + +@@ -120,6 +121,11 @@ public class Constants { + // Configuration for write idle timeout on WebSocket session + public static final String WRITE_IDLE_TIMEOUT_MS = "org.apache.tomcat.websocket.WRITE_IDLE_TIMEOUT_MS"; + ++ // Configuration for session close timeout ++ public static final String SESSION_CLOSE_TIMEOUT_PROPERTY = "org.apache.tomcat.websocket.SESSION_CLOSE_TIMEOUT"; ++ // Default is 30 seconds - setting is in milliseconds ++ public static final long DEFAULT_SESSION_CLOSE_TIMEOUT = TimeUnit.SECONDS.toMillis(30); ++ + // Configuration for background processing checks intervals + static final int DEFAULT_PROCESS_PERIOD = Integer.getInteger( + "org.apache.tomcat.websocket.DEFAULT_PROCESS_PERIOD", 10) +diff --git a/java/org/apache/tomcat/websocket/WsSession.java b/java/org/apache/tomcat/websocket/WsSession.java +index 1f85100..114ff05 100644 +--- a/java/org/apache/tomcat/websocket/WsSession.java ++++ b/java/org/apache/tomcat/websocket/WsSession.java +@@ -28,6 +28,7 @@ import java.util.List; + import java.util.Map; + import java.util.Set; + import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicLong; + + import javax.websocket.CloseReason; +@@ -100,6 +101,7 @@ public class WsSession implements Session { + private volatile long lastActiveRead = System.currentTimeMillis(); + private volatile long lastActiveWrite = System.currentTimeMillis(); + private Map futures = new ConcurrentHashMap<>(); ++ private volatile Long sessionCloseTimeoutExpiry; + + /** + * Creates a new WebSocket session for communication between the two +@@ -496,10 +498,18 @@ public class WsSession implements Session { + state = State.OUTPUT_CLOSED; + + sendCloseMessage(closeReasonMessage); ++ fireEndpointOnClose(closeReasonLocal); ++ + if (closeSocket) { +- wsRemoteEndpoint.close(); ++ closeConnection(); ++ } else { ++ /* ++ * Set close timeout. If the client fails to send a close message response within the timeout, the session ++ * and the connection will be closed when the timeout expires. ++ */ ++ sessionCloseTimeoutExpiry = ++ Long.valueOf(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(getSessionCloseTimeout())); + } +- fireEndpointOnClose(closeReasonLocal); + } + + IOException ioe = new IOException(sm.getString("wsSession.messageFailed")); +@@ -536,7 +546,48 @@ public class WsSession implements Session { + state = State.CLOSED; + + // Close the socket +- wsRemoteEndpoint.close(); ++ closeConnection(); ++ } ++ } ++ } ++ ++ private void closeConnection() { ++ /* ++ * Close the network connection. ++ */ ++ wsRemoteEndpoint.close(); ++ /* ++ * Don't unregister the session until the connection is fully closed since webSocketContainer is responsible for ++ * tracking the session close timeout. ++ */ ++ webSocketContainer.unregisterSession(getSessionMapKey(), this); ++ } ++ ++ /* ++ * Returns the session close timeout in milliseconds ++ */ ++ protected long getSessionCloseTimeout() { ++ long result = 0; ++ Object obj = userProperties.get(Constants.SESSION_CLOSE_TIMEOUT_PROPERTY); ++ if (obj instanceof Long) { ++ result = ((Long) obj).intValue(); ++ } ++ if (result <= 0) { ++ result = Constants.DEFAULT_SESSION_CLOSE_TIMEOUT; ++ } ++ return result; ++ } ++ ++ ++ protected void checkCloseTimeout() { ++ // Skip the check if no session close timeout has been set. ++ if (sessionCloseTimeoutExpiry != null) { ++ // Check if the timeout has expired. ++ if (System.nanoTime() - sessionCloseTimeoutExpiry.longValue() > 0) { ++ // Check if the session has been closed in another thread while the timeout was being processed. ++ if (state == State.CLOSED) { ++ closeConnection(); ++ } + } + } + } +@@ -617,7 +668,7 @@ public class WsSession implements Session { + if (log.isDebugEnabled()) { + log.debug(sm.getString("wsSession.sendCloseFail", id), e); + } +- wsRemoteEndpoint.close(); ++ closeConnection(); + // Failure to send a close message is not unexpected in the case of + // an abnormal closure (usually triggered by a failure to read/write + // from/to the client. In this case do not trigger the endpoint's +@@ -625,8 +676,6 @@ public class WsSession implements Session { + if (closeCode != CloseCodes.CLOSED_ABNORMALLY) { + localEndpoint.onError(this, e); + } +- } finally { +- webSocketContainer.unregisterSession(getSessionMapKey(), this); + } + } + +@@ -757,6 +806,11 @@ public class WsSession implements Session { + @Override + public Principal getUserPrincipal() { + checkState(); ++ return getUserPrincipalInternal(); ++ } ++ ++ ++ public Principal getUserPrincipalInternal() { + return userPrincipal; + } + +diff --git a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java +index 4bc8f6a..2622911 100644 +--- a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java ++++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java +@@ -634,7 +634,12 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce + synchronized (endPointSessionMapLock) { + Set sessions = endpointSessionMap.get(key); + if (sessions != null) { +- result.addAll(sessions); ++ // Some sessions may be in the process of closing ++ for (WsSession session : sessions) { ++ if (session.isOpen()) { ++ result.add(session); ++ } ++ } + } + } + return result; +@@ -1097,8 +1102,10 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce + if (backgroundProcessCount >= processPeriod) { + backgroundProcessCount = 0; + ++ // Check all registered sessions. + for (WsSession wsSession : sessions.keySet()) { + wsSession.checkExpiration(); ++ wsSession.checkCloseTimeout(); + } + } + +diff --git a/java/org/apache/tomcat/websocket/server/WsServerContainer.java b/java/org/apache/tomcat/websocket/server/WsServerContainer.java +index 3b9708b..0230179 100644 +--- a/java/org/apache/tomcat/websocket/server/WsServerContainer.java ++++ b/java/org/apache/tomcat/websocket/server/WsServerContainer.java +@@ -415,8 +415,7 @@ public class WsServerContainer extends WsWebSocketContainer + */ + @Override + protected void unregisterSession(Object key, WsSession wsSession) { +- if (wsSession.getUserPrincipal() != null && +- wsSession.getHttpSessionId() != null) { ++ if (wsSession.getUserPrincipalInternal() != null && wsSession.getHttpSessionId() != null) { + unregisterAuthenticatedSession(wsSession, + wsSession.getHttpSessionId()); + } +diff --git a/webapps/docs/web-socket-howto.xml b/webapps/docs/web-socket-howto.xml +index 7523c57..e58ce15 100644 +--- a/webapps/docs/web-socket-howto.xml ++++ b/webapps/docs/web-socket-howto.xml +@@ -76,6 +76,13 @@ + Session.setMaxIdleTimeout(long). If the associated property is + not specified, the read and/or write idle timeout will be applied.

+ ++

The session close timeout defaults to 30000 milliseconds (30 seconds). This ++ may be changed by setting the property ++ org.apache.tomcat.websocket.SESSION_CLOSE_TIMEOUT in the user ++ properties collection attached to the WebSocket session. The value assigned ++ to this property should be a Long and represents the timeout to ++ use in milliseconds. Values less than or equal to zero will be ignored.

++ +

If the application does not define a MessageHandler.Partial for + incoming binary messages, any incoming binary messages must be buffered so + the entire message can be delivered in a single call to the registered diff -Nru tomcat9-9.0.43/debian/patches/CVE-2024-24549.patch tomcat9-9.0.43/debian/patches/CVE-2024-24549.patch --- tomcat9-9.0.43/debian/patches/CVE-2024-24549.patch 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.43/debian/patches/CVE-2024-24549.patch 2024-04-18 20:17:07.000000000 +0000 @@ -0,0 +1,46 @@ +From: Markus Koschany +Date: Wed, 3 Apr 2024 18:56:12 +0200 +Subject: CVE-2024-24549 + +Bug-Debian: https://bugs.debian.org/1066878 +Origin: https://github.com/apache/tomcat/commit/d07c82194edb69d99b438828fe2cbfadbb207843 +--- + java/org/apache/coyote/http2/Http2Parser.java | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/java/org/apache/coyote/http2/Http2Parser.java b/java/org/apache/coyote/http2/Http2Parser.java +index 4725a1f..84a23c8 100644 +--- a/java/org/apache/coyote/http2/Http2Parser.java ++++ b/java/org/apache/coyote/http2/Http2Parser.java +@@ -270,6 +270,9 @@ class Http2Parser { + + swallow(streamId, padLength, true, buffer); + ++ // Validate the headers so far ++ hpackDecoder.getHeaderEmitter().validateHeaders(); ++ + if (Flags.isEndOfHeaders(flags)) { + onHeadersComplete(streamId); + } else { +@@ -437,6 +440,9 @@ class Http2Parser { + + readHeaderPayload(streamId, payloadSize, buffer); + ++ // Validate the headers so far ++ hpackDecoder.getHeaderEmitter().validateHeaders(); ++ + if (endOfHeaders) { + headersCurrentStream = -1; + onHeadersComplete(streamId); +@@ -586,11 +592,6 @@ class Http2Parser { + Http2Error.COMPRESSION_ERROR); + } + +- // Delay validation (and triggering any exception) until this point +- // since all the headers still have to be read if a StreamException is +- // going to be thrown. +- hpackDecoder.getHeaderEmitter().validateHeaders(); +- + synchronized (output) { + output.headersEnd(streamId); + diff -Nru tomcat9-9.0.43/debian/patches/series tomcat9-9.0.43/debian/patches/series --- tomcat9-9.0.43/debian/patches/series 2023-10-16 12:51:43.000000000 +0000 +++ tomcat9-9.0.43/debian/patches/series 2024-04-17 21:15:06.000000000 +0000 @@ -27,3 +27,6 @@ CVE-2023-42795.patch CVE-2023-44487.patch CVE-2023-45648.patch +CVE-2024-23672.patch +CVE-2024-24549.patch +CVE-2023-46589.patch