[Git][java-team/tomcat9][bullseye] Import Debian changes 9.0.43-2~deb11u10
Markus Koschany (@apo)
gitlab at salsa.debian.org
Thu Apr 18 21:24:05 BST 2024
Markus Koschany pushed to branch bullseye at Debian Java Maintainers / tomcat9
Commits:
a958a919 by Markus Koschany at 2024-04-18T22:23:36+02:00
Import Debian changes 9.0.43-2~deb11u10
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.
- - - - -
5 changed files:
- debian/changelog
- + debian/patches/CVE-2023-46589.patch
- + debian/patches/CVE-2024-23672.patch
- + debian/patches/CVE-2024-24549.patch
- debian/patches/series
Changes:
=====================================
debian/changelog
=====================================
@@ -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 <apo at debian.org> Thu, 18 Apr 2024 22:17:07 +0200
+
tomcat9 (9.0.43-2~deb11u9) bullseye-security; urgency=high
* More HTTP/2 overhead protection adjustments
=====================================
debian/patches/CVE-2023-46589.patch
=====================================
@@ -0,0 +1,621 @@
+From: Markus Koschany <apo at debian.org>
+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 at 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 @@
+ <add>
+ Improve validation of storage location when using FileStore. (markt)
+ </add>
++ <add>
++ Make the non-blocking I/O error handling more robust by handling the
++ case where the application code swallows an <code>IOException</code> in
++ <code>WriteListener.onWritePossible()</code> and
++ <code>ReadListener.onDataAvailable()</code>. (markt)
++ </add>
+ </changelog>
+ </subsection>
+ <subsection name="Coyote">
+@@ -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)
+ </fix>
++ <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">
=====================================
debian/patches/CVE-2024-23672.patch
=====================================
@@ -0,0 +1,220 @@
+From: Markus Koschany <apo at debian.org>
+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<FutureToSendHandler, FutureToSendHandler> 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<WsSession> 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 @@
+ <code>Session.setMaxIdleTimeout(long)</code>. If the associated property is
+ not specified, the read and/or write idle timeout will be applied.</p>
+
++<p>The session close timeout defaults to 30000 milliseconds (30 seconds). This
++ may be changed by setting the property
++ <code>org.apache.tomcat.websocket.SESSION_CLOSE_TIMEOUT</code> in the user
++ properties collection attached to the WebSocket session. The value assigned
++ to this property should be a <code>Long</code> and represents the timeout to
++ use in milliseconds. Values less than or equal to zero will be ignored.</p>
++
+ <p>If the application does not define a <code>MessageHandler.Partial</code> 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
=====================================
debian/patches/CVE-2024-24549.patch
=====================================
@@ -0,0 +1,46 @@
+From: Markus Koschany <apo at debian.org>
+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);
+
=====================================
debian/patches/series
=====================================
@@ -27,3 +27,6 @@ CVE-2023-41080.patch
CVE-2023-42795.patch
CVE-2023-44487.patch
CVE-2023-45648.patch
+CVE-2024-23672.patch
+CVE-2024-24549.patch
+CVE-2023-46589.patch
View it on GitLab: https://salsa.debian.org/java-team/tomcat9/-/commit/a958a9194502095072d18482f6032e5baa54c0a3
--
View it on GitLab: https://salsa.debian.org/java-team/tomcat9/-/commit/a958a9194502095072d18482f6032e5baa54c0a3
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20240418/0b293cd0/attachment.htm>
More information about the pkg-java-commits
mailing list