[Git][java-team/tomcat10][debian/10.1.6-1+deb12u2] 7 commits: Fix CVE-2023-44487: DoS caused by HTTP/2 frame overhead (Rapid Reset Attack)

Emmanuel Bourg (@ebourg) gitlab at salsa.debian.org
Wed Jul 3 23:28:59 BST 2024



Emmanuel Bourg pushed to tag debian/10.1.6-1+deb12u2 at Debian Java Maintainers / tomcat10


Commits:
367c53c0 by Emmanuel Bourg at 2023-10-10T18:47:21+02:00
Fix CVE-2023-44487: DoS caused by HTTP/2 frame overhead (Rapid Reset Attack)

- - - - -
2a7b45b5 by Emmanuel Bourg at 2023-10-10T18:47:26+02:00
Fix CVE-2023-42795: Information disclosure caused by recycling errors

- - - - -
0976941a by Emmanuel Bourg at 2023-10-10T18:47:26+02:00
Fix CVE-2023-41080: Open redirect from the default web application

- - - - -
a4a37d41 by Emmanuel Bourg at 2023-10-10T18:47:26+02:00
Fix CVE-2023-28709: Denial of Service caused by flawed parameter counting logic

- - - - -
e0ac0dd9 by Emmanuel Bourg at 2023-10-10T18:47:26+02:00
Fix CVE-2023-45648: Request smuggling caused by invalid trailer header

- - - - -
c11fa52e by Emmanuel Bourg at 2023-10-10T20:12:16+02:00
Adjust the version suffix

- - - - -
fb895c4e by Markus Koschany at 2024-07-04T00:26:21+02:00
Import Debian changes 10.1.6-1+deb12u2

tomcat10 (10.1.6-1+deb12u2) bookworm-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.

- - - - -


10 changed files:

- debian/changelog
- + debian/patches/CVE-2023-28709.patch
- + debian/patches/CVE-2023-41080.patch
- + debian/patches/CVE-2023-42795.patch
- + debian/patches/CVE-2023-44487.patch
- + debian/patches/CVE-2023-45648.patch
- + 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,49 @@
+tomcat10 (10.1.6-1+deb12u2) bookworm-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>  Mon, 15 Apr 2024 22:05:02 +0200
+
+tomcat10 (10.1.6-1+deb12u1) bookworm-security; urgency=high
+
+  * Fix CVE-2023-45648: Request smuggling. Tomcat did not correctly parse HTTP
+    trailer headers. A specially crafted, invalid trailer header 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-2023-44487: DoS caused by HTTP/2 frame overhead (Rapid Reset Attack)
+  * Fix CVE-2023-42795: Information Disclosure. When recycling various internal
+    objects, including the request and the response, prior to re-use by the next
+    request/response, an error could cause Tomcat to skip some parts of the
+    recycling process leading to information leaking from the current
+    request/response to the next.
+  * Fix CVE-2023-41080: Open redirect. If the ROOT (default) web application
+    is configured to use FORM authentication then it is possible that a
+    specially crafted URL could be used to trigger a redirect to an URL of
+    the attackers choice.
+  * Fix CVE-2023-28709: Denial of Service. If non-default HTTP connector
+    settings were used such that the maxParameterCount could be reached using
+    query string parameters and a request was submitted that supplied exactly
+    maxParameterCount parameters in the query string, the limit for uploaded
+    request parts could be bypassed with the potential for a denial of service
+    to occur.
+
+ -- Emmanuel Bourg <ebourg at apache.org>  Tue, 10 Oct 2023 18:33:08 +0200
+
 tomcat10 (10.1.6-1) unstable; urgency=medium
 
   * New upstream version 10.1.6


=====================================
debian/patches/CVE-2023-28709.patch
=====================================
@@ -0,0 +1,21 @@
+Description: Fix parameter counting logic
+Origin: upstream, https://github.com/apache/tomcat/commit/ba848da71c523d94950d3c53c19ea155189df9dc
+--- a/java/org/apache/tomcat/util/http/Parameters.java
++++ b/java/org/apache/tomcat/util/http/Parameters.java
+@@ -203,14 +203,14 @@
+             return;
+         }
+ 
+-        parameterCount ++;
+-        if (limit > -1 && parameterCount > limit) {
++        if (limit > -1 && parameterCount >= limit) {
+             // Processing this parameter will push us over the limit. ISE is
+             // what Request.parseParts() uses for requests that are too big
+             setParseFailedReason(FailReason.TOO_MANY_PARAMETERS);
+             throw new IllegalStateException(sm.getString(
+                     "parameters.maxCountFail", Integer.valueOf(limit)));
+         }
++        parameterCount++;
+ 
+         paramHashValues.computeIfAbsent(key, k -> new ArrayList<>(1)).add(value);
+     }


=====================================
debian/patches/CVE-2023-41080.patch
=====================================
@@ -0,0 +1,17 @@
+Description: Avoid protocol relative redirects
+Origin: upstream, https://github.com/apache/tomcat/commit/bb4624a9f3e69d495182ebfa68d7983076407a27
+--- a/java/org/apache/catalina/authenticator/FormAuthenticator.java
++++ b/java/org/apache/catalina/authenticator/FormAuthenticator.java
+@@ -704,6 +704,12 @@
+             sb.append('?');
+             sb.append(saved.getQueryString());
+         }
++
++        // Avoid protocol relative redirects
++        while (sb.length() > 1 && sb.charAt(1) == '/') {
++            sb.deleteCharAt(0);
++        }
++
+         return sb.toString();
+     }
+ }


=====================================
debian/patches/CVE-2023-42795.patch
=====================================
@@ -0,0 +1,218 @@
+Description: Improve handling of failures during recycle() methods
+Origin: upstream, https://github.com/apache/tomcat/commit/9375d67106f8df9eb9d7b360b2bef052fe67d3d4
+--- a/java/org/apache/catalina/connector/LocalStrings.properties
++++ b/java/org/apache/catalina/connector/LocalStrings.properties
+@@ -45,6 +45,7 @@
+ coyoteRequest.authenticate.ise=Cannot call authenticate() after the response has been committed
+ coyoteRequest.changeSessionId=Cannot change session ID. There is no session associated with this request.
+ coyoteRequest.chunkedPostTooLarge=Parameters were not parsed because the size of the posted data was too big. Because this request was a chunked request, it could not be processed further. Use the maxPostSize attribute of the connector to resolve this if the application should accept large POSTs.
++coyoteRequest.deletePartFailed=Failed to deleted temporary file used for part [{0}]
+ coyoteRequest.filterAsyncSupportUnknown=Unable to determine if any filters do not support async processing
+ coyoteRequest.getContextPath.ise=Unable to find match between the canonical context path [{0}] and the URI presented by the user agent [{1}]
+ coyoteRequest.getInputStream.ise=getReader() has already been called for this request
+--- a/java/org/apache/catalina/connector/Request.java
++++ b/java/org/apache/catalina/connector/Request.java
+@@ -450,8 +450,9 @@
+             for (Part part: parts) {
+                 try {
+                     part.delete();
+-                } catch (IOException ignored) {
+-                    // ApplicationPart.delete() never throws an IOEx
++                } catch (Throwable t) {
++                    ExceptionUtils.handleThrowable(t);
++                    log.warn(sm.getString("coyoteRequest.deletePartFailed", part.getName()), t);
+                 }
+             }
+             parts = null;
+@@ -504,8 +505,8 @@
+         asyncSupported = null;
+         if (asyncContext!=null) {
+             asyncContext.recycle();
++            asyncContext = null;
+         }
+-        asyncContext = null;
+     }
+ 
+ 
+--- a/java/org/apache/catalina/core/ApplicationHttpRequest.java
++++ b/java/org/apache/catalina/core/ApplicationHttpRequest.java
+@@ -48,6 +48,7 @@
+ import org.apache.catalina.util.ParameterMap;
+ import org.apache.catalina.util.RequestUtil;
+ import org.apache.catalina.util.URLEncoder;
++import org.apache.tomcat.util.ExceptionUtils;
+ import org.apache.tomcat.util.buf.B2CConverter;
+ import org.apache.tomcat.util.buf.MessageBytes;
+ import org.apache.tomcat.util.http.Parameters;
+@@ -618,7 +619,12 @@
+      */
+     public void recycle() {
+         if (session != null) {
+-            session.endAccess();
++            try {
++                session.endAccess();
++            } catch (Throwable t) {
++                ExceptionUtils.handleThrowable(t);
++                context.getLogger().warn(sm.getString("applicationHttpRequest.sessionEndAccessFail"), t);
++            }
+         }
+     }
+ 
+--- a/java/org/apache/catalina/core/LocalStrings.properties
++++ b/java/org/apache/catalina/core/LocalStrings.properties
+@@ -59,6 +59,7 @@
+ applicationFilterRegistration.nullInitParams=Unable to set initialisation parameters for filter due to null name and/or value. Name [{0}], Value [{1}]
+ 
+ applicationHttpRequest.fragmentInDispatchPath=The fragment in dispatch path [{0}] has been removed
++applicationHttpRequest.sessionEndAccessFail=Exception triggered ending access to session while recycling request
+ 
+ applicationPushBuilder.methodInvalid=The HTTP method for a push request must be both cacheable and safe but [{0}] is not
+ applicationPushBuilder.methodNotToken=HTTP methods must be tokens but [{0}] contains a non-token character
+--- a/java/org/apache/catalina/core/LocalStrings_cs.properties
++++ b/java/org/apache/catalina/core/LocalStrings_cs.properties
+@@ -24,6 +24,8 @@
+ 
+ applicationFilterRegistration.nullInitParams=Není možné nastavit inicializační parametry pro filtr kvůli hodnotě null ve jménu či hodnotě. Jméno [{0}], Hodnota [{1}]
+ 
++applicationHttpRequest.sessionEndAccessFail=Výjimka vyvolala ukončení přístupu k session během recykllování dotazu
++
+ aprListener.initializingFIPS=Inicializace FIPS módu...
+ 
+ containerBase.backgroundProcess.cluster=Výjimka při zpracování procesu na pozadí v clusteru [{0}]
+--- a/java/org/apache/catalina/core/LocalStrings_es.properties
++++ b/java/org/apache/catalina/core/LocalStrings_es.properties
+@@ -52,6 +52,8 @@
+ applicationFilterRegistration.nullInitParam=No puedo poner el parámetro de inicialización para el filtro debido a un nombre nulo y/o valor. Nombre [{0}], Valor [{1}]
+ applicationFilterRegistration.nullInitParams=No puedo poner los parámetros de inicialización para el filtro debido a un nombre nulo y/o valor. Nombre [{0}], Valor [{1}]
+ 
++applicationHttpRequest.sessionEndAccessFail=Excepción disparada acabando acceso a sesión mientras se reciclaba el requerimiento
++
+ applicationServletRegistration.setServletSecurity.iae=Se ha especificado restricción Null para el servlet [{0}] desplegado en el contexto con el nombre [{1}]
+ applicationServletRegistration.setServletSecurity.ise=No se pueden añadir restricciones de seguridad al servlet [{0}] desplegado en el contexto con el nombre [{1}] ya que el contexto ya ha sido inicializado.
+ 
+--- a/java/org/apache/catalina/core/LocalStrings_fr.properties
++++ b/java/org/apache/catalina/core/LocalStrings_fr.properties
+@@ -59,6 +59,7 @@
+ applicationFilterRegistration.nullInitParams=Impossible de fixer les paramètres d''initialisation du filtre, à cause d''un nom ou d''une valeur nulle, nom [{0}], valeur [{1}]
+ 
+ applicationHttpRequest.fragmentInDispatchPath=Le fragment dans le chemin de dispatch [{0}] a été enlevé
++applicationHttpRequest.sessionEndAccessFail=Exception lancée durant l'arrêt de l'accès à la session durant le recyclage de la requête
+ 
+ applicationPushBuilder.methodInvalid=La méthode HTTP pour une requête push doit être à la fois être sans danger et pouvoir être mise en cache, mais [{0}] ne correspond pas
+ applicationPushBuilder.methodNotToken=Les méthodes HTTP doivent être des "token", mais [{0}] contient un caractère invalide dans un token.
+--- a/java/org/apache/catalina/core/LocalStrings_ja.properties
++++ b/java/org/apache/catalina/core/LocalStrings_ja.properties
+@@ -59,6 +59,7 @@
+ applicationFilterRegistration.nullInitParams=キー [{0}] または値 [{1}] のいずれかが null のためフィルターの初期化パラメータを設定できません。
+ 
+ applicationHttpRequest.fragmentInDispatchPath=ディスパッチパス [{0}] 中のフラグメントは除去されました
++applicationHttpRequest.sessionEndAccessFail=リクエストの再利用中に行ったセッションへのアクセス終了処理で例外が送出されました。
+ 
+ applicationPushBuilder.methodInvalid=プッシュリクエストの HTTP メソッドはキャッシュ可能、かつ、安全でなければなりません。[{0}] は指定できません。
+ applicationPushBuilder.methodNotToken=HTTP メソッド [{0}] にトークンとして利用できない文字が含まれています。
+--- a/java/org/apache/catalina/core/LocalStrings_ko.properties
++++ b/java/org/apache/catalina/core/LocalStrings_ko.properties
+@@ -59,6 +59,7 @@
+ applicationFilterRegistration.nullInitParams=널인 이름 또는 값 때문에, 필터의 초기화 파라미터를 설정할 수 없습니다. 이름: [{0}], 값: [{1}]
+ 
+ applicationHttpRequest.fragmentInDispatchPath=디스패치 경로 [{0}](으)로부터 URI fragment를 제거했습니다.
++applicationHttpRequest.sessionEndAccessFail=요청을 참조 해제하는 과정에서, 세션에 대한 접근을 종료시키려 개시하는 중 예외 발생
+ 
+ applicationPushBuilder.methodInvalid=PUSH 요청을 위한 HTTP 메소드는 반드시 캐시 가능하고 안전해야 하는데, [{0}]은(는) 그렇지 않습니다.
+ applicationPushBuilder.methodNotToken=HTTP 메소드들은 토큰들이어야 하지만, [{0}]은(는) 토큰이 아닌 문자를 포함하고 있습니다.
+--- a/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
++++ b/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
+@@ -60,6 +60,7 @@
+ applicationFilterRegistration.nullInitParams=由于name和(或)value为null,无法为过滤器设置初始化参数。name为 [{0}],value为 [{1}]
+ 
+ applicationHttpRequest.fragmentInDispatchPath=调度路径[{0}]中的片段已被删除
++applicationHttpRequest.sessionEndAccessFail=在回收请求时,异常触发了对会话的结束访问。
+ 
+ applicationPushBuilder.methodInvalid=推送请求的HTTP方法必须既可缓存又安全,但是[{0}]不是
+ applicationPushBuilder.methodNotToken=HTTP方法必须是令牌(token),但 [{0}] 包含非令牌字符
+--- a/java/org/apache/tomcat/util/buf/B2CConverter.java
++++ b/java/org/apache/tomcat/util/buf/B2CConverter.java
+@@ -26,6 +26,9 @@
+ import java.nio.charset.CodingErrorAction;
+ import java.util.Locale;
+ 
++import org.apache.juli.logging.Log;
++import org.apache.juli.logging.LogFactory;
++import org.apache.tomcat.util.ExceptionUtils;
+ import org.apache.tomcat.util.res.StringManager;
+ 
+ /**
+@@ -33,6 +36,7 @@
+  */
+ public class B2CConverter {
+ 
++    private static final Log log = LogFactory.getLog(B2CConverter.class);
+     private static final StringManager sm = StringManager.getManager(B2CConverter.class);
+ 
+     private static final CharsetCache charsetCache = new CharsetCache();
+@@ -98,7 +102,12 @@
+      * Reset the decoder state.
+      */
+     public void recycle() {
+-        decoder.reset();
++        try {
++            decoder.reset();
++        } catch (Throwable t) {
++            ExceptionUtils.handleThrowable(t);
++            log.warn(sm.getString("b2cConverter.decoderResetFail", decoder.charset()), t);
++        }
+         leftovers.position(0);
+     }
+ 
+--- a/java/org/apache/tomcat/util/buf/C2BConverter.java
++++ b/java/org/apache/tomcat/util/buf/C2BConverter.java
+@@ -24,11 +24,19 @@
+ import java.nio.charset.CoderResult;
+ import java.nio.charset.CodingErrorAction;
+ 
++import org.apache.juli.logging.Log;
++import org.apache.juli.logging.LogFactory;
++import org.apache.tomcat.util.ExceptionUtils;
++import org.apache.tomcat.util.res.StringManager;
++
+ /**
+  * NIO based character encoder.
+  */
+ public final class C2BConverter {
+ 
++    private static final Log log = LogFactory.getLog(C2BConverter.class);
++    private static final StringManager sm = StringManager.getManager(C2BConverter.class);
++
+     private final CharsetEncoder encoder;
+     private ByteBuffer bb = null;
+     private CharBuffer cb = null;
+@@ -50,7 +58,12 @@
+      * Reset the encoder state.
+      */
+     public void recycle() {
+-        encoder.reset();
++        try {
++            encoder.reset();
++        } catch (Throwable t) {
++            ExceptionUtils.handleThrowable(t);
++            log.warn(sm.getString("c2bConverter.decoderResetFail", encoder.charset()), t);
++        }
+         leftovers.position(0);
+     }
+ 
+--- a/java/org/apache/tomcat/util/buf/LocalStrings.properties
++++ b/java/org/apache/tomcat/util/buf/LocalStrings.properties
+@@ -16,10 +16,13 @@
+ asn1Parser.lengthInvalid=Invalid length [{0}] bytes reported when the input data length is [{1}] bytes
+ asn1Parser.tagMismatch=Expected to find value [{0}] but found value [{1}]
+ 
++b2cConverter.decoderResetFail=Failed to reset instance of decoder for character set [{0}]
+ b2cConverter.unknownEncoding=The character encoding [{0}] is not supported
+ 
+ byteBufferUtils.cleaner=Cannot use direct ByteBuffer cleaner, memory leaking may occur
+ 
++c2bConverter.encoderResetFail=Failed to reset instance of encoder for character set [{0}]
++
+ chunk.overflow=Buffer overflow and no sink is set, limit [{0}] and buffer length [{1}]
+ 
+ encodedSolidusHandling.invalid=The value [{0}] is not recognised


=====================================
debian/patches/CVE-2023-44487.patch
=====================================
@@ -0,0 +1,167 @@
+Description: Improvements to HTTP/2 overhead protection.
+Origin: upstream, https://github.com/apache/tomcat/commit/76bb4bfbfeae827dce896f650655bbf6e251ed49
+--- a/java/org/apache/coyote/http2/Http2AsyncParser.java
++++ b/java/org/apache/coyote/http2/Http2AsyncParser.java
+@@ -281,36 +281,39 @@
+                                     readUnknownFrame(streamId, frameTypeId, flags, payloadSize, payload);
+                             }
+                         }
+-                        // See if there is a new 9 byte header and continue parsing if possible
+-                        if (payload.remaining() >= 9) {
+-                            int position = payload.position();
+-                            payloadSize = ByteUtil.getThreeBytes(payload, position);
+-                            frameTypeId = ByteUtil.getOneByte(payload, position + 3);
+-                            frameType = FrameType.valueOf(frameTypeId);
+-                            flags = ByteUtil.getOneByte(payload, position + 4);
+-                            streamId = ByteUtil.get31Bits(payload, position + 5);
+-                            streamException = false;
+-                            if (payload.remaining() - 9 >= payloadSize) {
+-                                continueParsing = true;
+-                                // Now go over frame header
+-                                payload.position(payload.position() + 9);
+-                                try {
+-                                    validateFrame(null, frameType, streamId, flags, payloadSize);
+-                                } catch (StreamException e) {
+-                                    error = e;
+-                                    streamException = true;
+-                                } catch (Http2Exception e) {
+-                                    error = e;
+-                                    continueParsing = false;
++                        if (!upgradeHandler.isOverheadLimitExceeded()) {
++                            // See if there is a new 9 byte header and continue parsing if possible
++                            if (payload.remaining() >= 9) {
++                                int position = payload.position();
++                                payloadSize = ByteUtil.getThreeBytes(payload, position);
++                                frameTypeId = ByteUtil.getOneByte(payload, position + 3);
++                                frameType = FrameType.valueOf(frameTypeId);
++                                flags = ByteUtil.getOneByte(payload, position + 4);
++                                streamId = ByteUtil.get31Bits(payload, position + 5);
++                                streamException = false;
++                                if (payload.remaining() - 9 >= payloadSize) {
++                                    continueParsing = true;
++                                    // Now go over frame header
++                                    payload.position(payload.position() + 9);
++                                    try {
++                                        validateFrame(null, frameType, streamId, flags, payloadSize);
++                                    } catch (StreamException e) {
++                                        error = e;
++                                        streamException = true;
++                                    } catch (Http2Exception e) {
++                                        error = e;
++                                        continueParsing = false;
++                                    }
+                                 }
+                             }
+                         }
+                     } while (continueParsing);
+                 } catch (RuntimeException | IOException | Http2Exception e) {
+                     error = e;
+-                }
+-                if (payload.hasRemaining()) {
+-                    socketWrapper.unRead(payload);
++                } finally {
++                    if (payload.hasRemaining()) {
++                        socketWrapper.unRead(payload);
++                    }
+                 }
+             }
+             if (state == CompletionState.DONE) {
+--- a/java/org/apache/coyote/http2/Http2Protocol.java
++++ b/java/org/apache/coyote/http2/Http2Protocol.java
+@@ -53,8 +53,10 @@
+     // Maximum amount of streams which can be concurrently executed over
+     // a single connection
+     static final int DEFAULT_MAX_CONCURRENT_STREAM_EXECUTION = 20;
+-
++    // Default factor used when adjusting overhead count for overhead frames
+     static final int DEFAULT_OVERHEAD_COUNT_FACTOR = 10;
++    // Default factor used when adjusting overhead count for reset frames
++    static final int DEFAULT_OVERHEAD_RESET_FACTOR = 50;
+     // Not currently configurable. This makes the practical limit for
+     // overheadCountFactor to be ~20. The exact limit will vary with traffic
+     // patterns.
+@@ -85,6 +87,7 @@
+     private int maxHeaderCount = Constants.DEFAULT_MAX_HEADER_COUNT;
+     private int maxTrailerCount = Constants.DEFAULT_MAX_TRAILER_COUNT;
+     private int overheadCountFactor = DEFAULT_OVERHEAD_COUNT_FACTOR;
++    private int overheadResetFactor = DEFAULT_OVERHEAD_RESET_FACTOR;
+     private int overheadContinuationThreshold = DEFAULT_OVERHEAD_CONTINUATION_THRESHOLD;
+     private int overheadDataThreshold = DEFAULT_OVERHEAD_DATA_THRESHOLD;
+     private int overheadWindowUpdateThreshold = DEFAULT_OVERHEAD_WINDOW_UPDATE_THRESHOLD;
+@@ -290,6 +293,20 @@
+     }
+ 
+ 
++    public int getOverheadResetFactor() {
++        return overheadResetFactor;
++    }
++
++
++    public void setOverheadResetFactor(int overheadResetFactor) {
++        if (overheadResetFactor < 0) {
++            this.overheadResetFactor = 0;
++        } else {
++            this.overheadResetFactor = overheadResetFactor;
++        }
++    }
++
++
+     public int getOverheadContinuationThreshold() {
+         return overheadContinuationThreshold;
+     }
+--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
++++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+@@ -378,7 +378,7 @@
+                                     stream.close(se);
+                                 }
+                             } finally {
+-                                if (overheadCount.get() > 0) {
++                                if (isOverheadLimitExceeded()) {
+                                     throw new ConnectionException(
+                                             sm.getString("upgradeHandler.tooMuchOverhead", connectionId),
+                                             Http2Error.ENHANCE_YOUR_CALM);
+@@ -1502,6 +1502,11 @@
+     }
+ 
+ 
++    boolean isOverheadLimitExceeded() {
++        return overheadCount.get() > 0;
++    }
++
++
+     // ----------------------------------------------- Http2Parser.Input methods
+ 
+     @Override
+@@ -1772,6 +1777,7 @@
+             log.debug(sm.getString("upgradeHandler.reset.receive", getConnectionId(), Integer.toString(streamId),
+                     Long.toString(errorCode)));
+         }
++        increaseOverheadCount(FrameType.RST, getProtocol().getOverheadResetFactor());
+         AbstractNonZeroStream abstractNonZeroStream = getAbstractNonZeroStream(streamId, true);
+         abstractNonZeroStream.checkState(FrameType.RST);
+         if (abstractNonZeroStream instanceof Stream) {
+--- a/webapps/docs/config/http2.xml
++++ b/webapps/docs/config/http2.xml
+@@ -139,7 +139,7 @@
+       count starts at <code>-10 * overheadCountFactor</code>. The count is
+       decreased by 20 for each data frame sent or received and each headers frame
+       received. The count is increased by the <code>overheadCountFactor</code>
+-      for each setting received, priority frame received and ping received. If
++      for each setting, priority, priority update and ping frame received. If
+       the overhead count exceeds zero, the connection is closed. A value of less
+       than <code>1</code> disables this protection. In normal usage a value of
+       approximately <code>20</code> or higher will close the connection before
+@@ -147,6 +147,13 @@
+       <code>10</code> will be used.</p>
+     </attribute>
+ 
++    <attribute name="overheadResetFactor" required="false">
++      <p>The amount by which the overhead count (see
++      <strong>overheadCountFactor</strong>) will be increased for each reset
++      frame received. If not specified, a default value of <code>50</code> will
++      be used. A value of less than zero will be treated as zero.</p>
++    </attribute>
++
+     <attribute name="overheadDataThreshold" required="false">
+       <p>The threshold below which the average payload size of the current and
+       previous non-final <code>DATA</code> frames will trigger an increase in


=====================================
debian/patches/CVE-2023-45648.patch
=====================================
@@ -0,0 +1,82 @@
+Description: Align processing of trailer headers with standard processing
+Origin: upstream, https://github.com/apache/tomcat/commit/8ecff306507be8e4fd3adee1ae5de1ea6661a8f4
+--- a/java/org/apache/coyote/http11/Http11InputBuffer.java
++++ b/java/org/apache/coyote/http11/Http11InputBuffer.java
+@@ -851,6 +851,12 @@
+      */
+     private HeaderParseStatus parseHeader() throws IOException {
+ 
++        /*
++         * Implementation note: Any changes to this method probably need to be echoed in
++         * ChunkedInputFilter.parseHeader(). Why not use a common implementation? In short, this code uses non-blocking
++         * reads whereas ChunkedInputFilter using blocking reads. The code is just different enough that a common
++         * implementation wasn't viewed as practical.
++         */
+         while (headerParsePos == HeaderParsePosition.HEADER_START) {
+ 
+             // Read new bytes if needed
+@@ -989,7 +995,7 @@
+                     } else if (prevChr == Constants.CR) {
+                         // Invalid value - also need to delete header
+                         return skipLine(true);
+-                    } else if (chr != Constants.HT && HttpParser.isControl(chr)) {
++                    } else if (HttpParser.isControl(chr) && chr != Constants.HT) {
+                         // Invalid value - also need to delete header
+                         return skipLine(true);
+                     } else if (chr == Constants.SP || chr == Constants.HT) {
+--- a/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
++++ b/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
+@@ -30,6 +30,7 @@
+ import org.apache.coyote.http11.InputFilter;
+ import org.apache.tomcat.util.buf.ByteChunk;
+ import org.apache.tomcat.util.buf.HexUtils;
++import org.apache.tomcat.util.http.parser.HttpParser;
+ import org.apache.tomcat.util.net.ApplicationBufferHandler;
+ import org.apache.tomcat.util.res.StringManager;
+ 
+@@ -444,6 +445,13 @@
+ 
+     private boolean parseHeader() throws IOException {
+ 
++        /*
++         * Implementation note: Any changes to this method probably need to be echoed in
++         * Http11InputBuffer.parseHeader(). Why not use a common implementation? In short, this code uses blocking
++         * reads whereas Http11InputBuffer using non-blocking reads. The code is just different enough that a common
++         * implementation wasn't viewed as practical.
++         */
++
+         Map<String,String> headers = request.getTrailerFields();
+ 
+         byte chr = 0;
+@@ -490,6 +498,9 @@
+ 
+             if (chr == Constants.COLON) {
+                 colon = true;
++            } else if (!HttpParser.isToken(chr)) {
++                // Non-token characters are illegal in header names
++                throw new IOException(sm.getString("chunkedInputFilter.invalidTrailerHeaderName"));
+             } else {
+                 trailingHeaders.append(chr);
+             }
+@@ -551,7 +562,9 @@
+                 if (chr == Constants.CR || chr == Constants.LF) {
+                     parseCRLF(true);
+                     eol = true;
+-                } else if (chr == Constants.SP) {
++                } else if (HttpParser.isControl(chr) && chr != Constants.HT) {
++                    throw new IOException(sm.getString("chunkedInputFilter.invalidTrailerHeaderValue"));
++                } else if (chr == Constants.SP || chr == Constants.HT) {
+                     trailingHeaders.append(chr);
+                 } else {
+                     trailingHeaders.append(chr);
+--- a/java/org/apache/coyote/http11/filters/LocalStrings.properties
++++ b/java/org/apache/coyote/http11/filters/LocalStrings.properties
+@@ -21,6 +21,8 @@
+ chunkedInputFilter.invalidCrlfNoCR=Invalid end of line sequence (No CR before LF)
+ chunkedInputFilter.invalidCrlfNoData=Invalid end of line sequence (no data available to read)
+ chunkedInputFilter.invalidHeader=Invalid chunk header
++chunkedInputFilter.invalidTrailerHeaderName=Invalid trailer header name (non-token character in name)
++chunkedInputFilter.invalidTrailerHeaderValue=Invalid trailer header value (control character in value)
+ chunkedInputFilter.maxExtension=maxExtensionSize exceeded
+ chunkedInputFilter.maxTrailer=maxTrailerSize exceeded
+ 


=====================================
debian/patches/CVE-2023-46589.patch
=====================================
@@ -0,0 +1,317 @@
+From: Markus Koschany <apo at debian.org>
+Date: Wed, 10 Apr 2024 17:02:36 +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 +-
+ .../org/apache/catalina/connector/InputBuffer.java | 19 +++++-
+ .../catalina/core/ApplicationDispatcher.java       |  6 +-
+ .../apache/catalina/core/StandardWrapperValve.java |  6 +-
+ .../http11/filters/TestChunkedInputFilter.java     | 77 ++++++++++++++++++++++
+ webapps/docs/changelog.xml                         |  5 ++
+ 7 files changed, 174 insertions(+), 11 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/InputBuffer.java b/java/org/apache/catalina/connector/InputBuffer.java
+index da736cc..7ed372f 100644
+--- a/java/org/apache/catalina/connector/InputBuffer.java
++++ b/java/org/apache/catalina/connector/InputBuffer.java
+@@ -29,6 +29,7 @@ import java.util.Map;
+ import java.util.concurrent.ConcurrentHashMap;
+ 
+ import jakarta.servlet.ReadListener;
++import jakarta.servlet.RequestDispatcher;
+ 
+ import org.apache.catalina.security.SecurityUtil;
+ import org.apache.coyote.ActionCode;
+@@ -314,10 +315,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) {
++            // Set flag used by asynchronous processing to detect errors on non-container threads
+             coyoteRequest.setErrorException(ioe);
+-            // An IOException on a read is almost always due to
+-            // the remote client aborting the request.
++            // 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);
+         }
+     }
+diff --git a/java/org/apache/catalina/core/ApplicationDispatcher.java b/java/org/apache/catalina/core/ApplicationDispatcher.java
+index f9b82d5..9e0fd51 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;
+@@ -642,7 +642,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", wrapper.getName()), e);
+@@ -653,7 +653,7 @@ final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher
+             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);
+             }
+diff --git a/java/org/apache/catalina/core/StandardWrapperValve.java b/java/org/apache/catalina/core/StandardWrapperValve.java
+index bbbda3c..02054aa 100644
+--- a/java/org/apache/catalina/core/StandardWrapperValve.java
++++ b/java/org/apache/catalina/core/StandardWrapperValve.java
+@@ -31,7 +31,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;
+@@ -168,7 +168,7 @@ final class StandardWrapperValve extends ValveBase {
+                 }
+ 
+             }
+-        } catch (ClientAbortException | CloseNowException e) {
++        } catch (BadRequestException | CloseNowException e) {
+             if (container.getLogger().isDebugEnabled()) {
+                 container.getLogger().debug(
+                         sm.getString("standardWrapper.serviceException", wrapper.getName(), context.getName()), e);
+@@ -189,7 +189,7 @@ final class StandardWrapperValve extends ValveBase {
+             // 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()), rootCause);
+             }
+diff --git a/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java b/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
+index 2230fe9..3454d8d 100644
+--- a/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
++++ b/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
+@@ -428,6 +428,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 329d923..0fb33f4 100644
+--- a/webapps/docs/changelog.xml
++++ b/webapps/docs/changelog.xml
+@@ -145,6 +145,11 @@
+         <code>RemoteIpFilter</code> determines that this request was submitted
+         via a secure channel. (lihan)
+       </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, 10 Apr 2024 21:54:01 +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 c83ab4c..325014b 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 jakarta.websocket.ClientEndpointConfig;
+ import jakarta.websocket.Extension;
+@@ -130,6 +131,11 @@ public class Constants {
+     // Milliseconds so this is 20 seconds
+     public static final long DEFAULT_BLOCKING_SEND_TIMEOUT = 20 * 1000;
+ 
++    // 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 read idle timeout on WebSocket session
+     public static final String READ_IDLE_TIMEOUT_MS = "org.apache.tomcat.websocket.READ_IDLE_TIMEOUT_MS";
+ 
+diff --git a/java/org/apache/tomcat/websocket/WsSession.java b/java/org/apache/tomcat/websocket/WsSession.java
+index c7e3319..2c38a41 100644
+--- a/java/org/apache/tomcat/websocket/WsSession.java
++++ b/java/org/apache/tomcat/websocket/WsSession.java
+@@ -27,6 +27,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.naming.NamingException;
+@@ -115,6 +116,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;
+ 
+ 
+     /**
+@@ -616,10 +618,17 @@ 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);
+             }
+         }
+ 
+@@ -657,7 +666,49 @@ 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();
++                }
+             }
+         }
+     }
+@@ -735,7 +786,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
+@@ -743,8 +794,6 @@ public class WsSession implements Session {
+             if (closeCode != CloseCodes.CLOSED_ABNORMALLY) {
+                 localEndpoint.onError(this, e);
+             }
+-        } finally {
+-            webSocketContainer.unregisterSession(getSessionMapKey(), this);
+         }
+     }
+ 
+@@ -875,6 +924,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 bed9590..f00cfb7 100644
+--- a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
++++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
+@@ -641,7 +641,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;
+@@ -1110,8 +1115,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 27533e9..0b38b47 100644
+--- a/java/org/apache/tomcat/websocket/server/WsServerContainer.java
++++ b/java/org/apache/tomcat/websocket/server/WsServerContainer.java
+@@ -383,8 +383,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 20cf2ca..2aaa808 100644
+--- a/webapps/docs/web-socket-howto.xml
++++ b/webapps/docs/web-socket-howto.xml
+@@ -64,6 +64,13 @@
+    the timeout to use in milliseconds. For an infinite timeout, use
+    <code>-1</code>.</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>In addition to the <code>Session.setMaxIdleTimeout(long)</code> method which
+    is part of the Jakarta WebSocket API, Tomcat provides greater control of the
+    timing out the session due to lack of activity. Setting the property


=====================================
debian/patches/CVE-2024-24549.patch
=====================================
@@ -0,0 +1,46 @@
+From: Markus Koschany <apo at debian.org>
+Date: Fri, 5 Apr 2024 15:54:58 +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 c9cee2e..207eefb 100644
+--- a/java/org/apache/coyote/http2/Http2Parser.java
++++ b/java/org/apache/coyote/http2/Http2Parser.java
+@@ -280,6 +280,9 @@ class Http2Parser {
+ 
+         swallowPayload(streamId, FrameType.HEADERS.getId(), padLength, true, buffer);
+ 
++        // Validate the headers so far
++        hpackDecoder.getHeaderEmitter().validateHeaders();
++
+         if (Flags.isEndOfHeaders(flags)) {
+             onHeadersComplete(streamId);
+         } else {
+@@ -452,6 +455,9 @@ class Http2Parser {
+ 
+         readHeaderPayload(streamId, payloadSize, buffer);
+ 
++        // Validate the headers so far
++        hpackDecoder.getHeaderEmitter().validateHeaders();
++
+         if (endOfHeaders) {
+             headersCurrentStream = -1;
+             onHeadersComplete(streamId);
+@@ -613,11 +619,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
=====================================
@@ -12,3 +12,11 @@
 0021-dont-test-unsupported-ciphers.patch
 exclude-TestJNDIRealmIntegration.patch
 disable-jacoco.patch
+CVE-2023-28709.patch
+CVE-2023-41080.patch
+CVE-2023-42795.patch
+CVE-2023-44487.patch
+CVE-2023-45648.patch
+CVE-2024-24549.patch
+CVE-2024-23672.patch
+CVE-2023-46589.patch



View it on GitLab: https://salsa.debian.org/java-team/tomcat10/-/compare/5c5fc7a66cfe04e6c9ce6c2272c388f9c702d559...fb895c4e3f36747f0d78f549dce3cfd8bfcddb88

-- 
This project does not include diff previews in email notifications.
View it on GitLab: https://salsa.debian.org/java-team/tomcat10/-/compare/5c5fc7a66cfe04e6c9ce6c2272c388f9c702d559...fb895c4e3f36747f0d78f549dce3cfd8bfcddb88
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/20240703/714e5793/attachment.htm>


More information about the pkg-java-commits mailing list