Apache Tomcat: HTTP/2 excess header handling DoS
Description
Improper Handling of Exceptional Conditions, Uncontrolled Resource Consumption vulnerability in Apache Tomcat. When processing an HTTP/2 stream, Tomcat did not handle some cases of excessive HTTP headers correctly. This led to a miscounting of active HTTP/2 streams which in turn led to the use of an incorrect infinite timeout which allowed connections to remain open which should have been closed.
This issue affects Apache Tomcat: from 11.0.0-M1 through 11.0.0-M20, from 10.1.0-M1 through 10.1.24, from 9.0.0-M1 through 9.0.89.
The following versions were EOL at the time the CVE was created but are known to be affected: 8.5.0 though 8.5.100. Other EOL versions may also be affected.
Users are recommended to upgrade to version 11.0.0-M21, 10.1.25 or 9.0.90, which fixes the issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Tomcat improperly handles excessive HTTP/2 headers, causing miscounting of active streams and incorrect infinite timeout, leading to resource exhaustion.
Vulnerability
Overview Apache Tomcat versions 11.0.0-M1 through 11.0.0-M20, 10.1.0-M1 through 10.1.24, and 9.0.0-M1 through 9.0.89 are affected by an improper handling of exceptional conditions and uncontrolled resource consumption vulnerability in HTTP/2 stream processing. When processing an HTTP/2 stream with excessive headers, Tomcat fails to correctly count active streams, leading to the use of an incorrect infinite timeout that prevents connections from closing as expected [4].
Attack
Vector An attacker can trigger this vulnerability by sending crafted HTTP/2 requests with an excessive number of headers to a vulnerable Tomcat server. No authentication is required, and the attack can be launched from any network position that can reach the server. The miscounting of active streams occurs during the handling of these exceptional conditions, causing the server to leave connections open indefinitely [4].
Impact
Successful exploitation results in uncontrolled resource consumption, as connections that should be closed remain open. This can lead to exhaustion of server resources (e.g., memory, file descriptors), potentially causing a denial-of-service (DoS) condition. The vulnerability does not provide code execution or data disclosure.
Mitigation
Apache Tomcat has released fixed versions: 11.0.0-M21, 10.1.25, and 9.0.90 [1][2][3]. Users should upgrade immediately. Note that EOL versions (e.g., 8.5.x) are also affected but no longer receive fixes [4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 11.0.0-M1, < 11.0.0-M21 | 11.0.0-M21 |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 10.1.0-M1, < 10.1.25 | 10.1.25 |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 9.0.0-M1, < 9.0.90 | 9.0.90 |
org.apache.tomcat:tomcat-coyoteMaven | >= 11.0.0-M1, < 11.0.0-M21 | 11.0.0-M21 |
org.apache.tomcat:tomcat-coyoteMaven | >= 10.1.0-M1, < 10.1.25 | 10.1.25 |
org.apache.tomcat:tomcat-coyoteMaven | >= 9.0.0-M1, < 9.0.90 | 9.0.90 |
org.apache.tomcat:tomcat-coyoteMaven | >= 8.5.0, <= 8.5.100 | — |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 8.5.0, <= 8.5.100 | — |
Affected products
52- osv-coords51 versionspkg:apk/chainguard/thingsboardpkg:apk/chainguard/thingsboard-tb-js-executorpkg:apk/chainguard/thingsboard-tb-mqtt-transportpkg:apk/chainguard/thingsboard-tb-nodepkg:apk/chainguard/thingsboard-tb-web-uipkg:apk/chainguard/tomcat-9-openjdk-11pkg:apk/chainguard/tomcat-9-openjdk-17pkg:apk/chainguard/tomcat-9-openjdk-21pkg:apk/chainguard/tomcat-9-openjdk-8pkg:apk/wolfi/thingsboardpkg:apk/wolfi/thingsboard-tb-js-executorpkg:apk/wolfi/thingsboard-tb-mqtt-transportpkg:apk/wolfi/thingsboard-tb-nodepkg:apk/wolfi/thingsboard-tb-web-uipkg:bitnami/tomcatpkg:maven/org.apache.tomcat.embed/tomcat-embed-corepkg:maven/org.apache.tomcat/tomcat-coyotepkg:rpm/almalinux/tomcatpkg:rpm/almalinux/tomcat-admin-webappspkg:rpm/almalinux/tomcat-docs-webapppkg:rpm/almalinux/tomcat-el-3.0-apipkg:rpm/almalinux/tomcat-jsp-2.3-apipkg:rpm/almalinux/tomcat-libpkg:rpm/almalinux/tomcat-servlet-4.0-apipkg:rpm/almalinux/tomcat-webappspkg:rpm/opensuse/tomcat10&distro=openSUSE%20Leap%2015.5pkg:rpm/opensuse/tomcat10&distro=openSUSE%20Leap%2015.6pkg:rpm/opensuse/tomcat10&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/tomcat&distro=openSUSE%20Leap%2015.5pkg:rpm/opensuse/tomcat&distro=openSUSE%20Leap%2015.6pkg:rpm/opensuse/tomcat&distro=openSUSE%20Tumbleweedpkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP5pkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP6pkg:rpm/suse/tomcat&distro=SUSE%20Enterprise%20Storage%207.1pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP2-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP3-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP4-ESPOSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP4-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP6pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP5-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP2-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP3-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP4-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2012%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP2pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP3pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP4pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20LTSS%20Extended%20Security%2012%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Manager%20Server%204.3
< 3.7-r2+ 50 more
- (no CPE)range: < 3.7-r2
- (no CPE)range: < 3.7-r2
- (no CPE)range: < 4.2.1-r7
- (no CPE)range: < 3.7-r2
- (no CPE)range: < 3.7-r2
- (no CPE)range: < 9.0.112-r0
- (no CPE)range: < 9.0.112-r0
- (no CPE)range: < 9.0.112-r0
- (no CPE)range: < 9.0.112-r0
- (no CPE)range: < 3.7-r2
- (no CPE)range: < 3.7-r2
- (no CPE)range: < 4.2.1-r7
- (no CPE)range: < 3.7-r2
- (no CPE)range: < 3.7-r2
- (no CPE)range: >= 9.0.0, < 9.0.90
- (no CPE)range: >= 11.0.0-M1, < 11.0.0-M21
- (no CPE)range: >= 11.0.0-M1, < 11.0.0-M21
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 1:9.0.87-1.el9_4.2
- (no CPE)range: < 10.1.25-150200.5.25.1
- (no CPE)range: < 10.1.25-150200.5.25.1
- (no CPE)range: < 10.1.25-1.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-1.1
- (no CPE)range: < 10.1.25-150200.5.25.1
- (no CPE)range: < 10.1.25-150200.5.25.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.36-3.127.1
- (no CPE)range: < 9.0.115-3.160.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.36-3.127.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.91-150200.68.1
- (no CPE)range: < 9.0.115-3.160.1
- (no CPE)range: < 9.0.91-150200.68.1
- Apache Software Foundation/Apache Tomcatv5Range: 11.0.0-M1
Patches
32344a4c0d03eMake counting of active streams more robust
4 files changed · +31 −11
java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java+1 −1 modified@@ -157,7 +157,7 @@ void sendStreamReset(StreamStateMachine state, StreamException se) throws IOExce boolean active = state.isActive(); state.sendReset(); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(getStream(se.getStreamId())); } }
java/org/apache/coyote/http2/Http2UpgradeHandler.java+10 −10 modified@@ -290,8 +290,8 @@ protected void processStreamOnContainerThread(Stream stream) { } - protected void decrementActiveRemoteStreamCount() { - setConnectionTimeoutForStreamCount(activeRemoteStreamCount.decrementAndGet()); + protected void decrementActiveRemoteStreamCount(Stream stream) { + setConnectionTimeoutForStreamCount(stream.decrementAndGetActiveRemoteStreamCount()); } @@ -598,7 +598,7 @@ void sendStreamReset(StreamStateMachine state, StreamException se) throws IOExce boolean active = state.isActive(); state.sendReset(); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(getStream(se.getStreamId())); } } socketWrapper.write(true, rstFrame, 0, rstFrame.length); @@ -825,7 +825,7 @@ void writeBody(Stream stream, ByteBuffer data, int len, boolean finished) throws protected void sentEndOfStream(Stream stream) { stream.sentEndOfStream(); if (!stream.isActive()) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } @@ -1208,7 +1208,7 @@ private int allocate(AbstractStream stream, int allocation) { } - private Stream getStream(int streamId) { + Stream getStream(int streamId) { Integer key = Integer.valueOf(streamId); AbstractStream result = streams.get(key); if (result instanceof Stream) { @@ -1536,6 +1536,7 @@ public HeaderEmitter headersStart(int streamId, boolean headersEndStream) throws Stream stream = getStream(streamId, false); if (stream == null) { stream = createRemoteStream(streamId); + activeRemoteStreamCount.incrementAndGet(); } if (streamId < maxActiveRemoteStreamId) { throw new ConnectionException(sm.getString("upgradeHandler.stream.old", Integer.valueOf(streamId), @@ -1597,9 +1598,8 @@ public void headersEnd(int streamId, boolean endOfStream) throws Http2Exception Stream stream = (Stream) abstractNonZeroStream; if (stream.isActive()) { if (stream.receivedEndOfHeaders()) { - - if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.incrementAndGet()) { - decrementActiveRemoteStreamCount(); + if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.get()) { + decrementActiveRemoteStreamCount(stream); // Ignoring maxConcurrentStreams increases the overhead count increaseOverheadCount(FrameType.HEADERS); throw new StreamException( @@ -1643,7 +1643,7 @@ public void receivedEndOfStream(int streamId) throws ConnectionException { private void receivedEndOfStream(Stream stream) throws ConnectionException { stream.receivedEndOfStream(); if (!stream.isActive()) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } @@ -1669,7 +1669,7 @@ public void reset(int streamId, long errorCode) throws Http2Exception { boolean active = stream.isActive(); stream.receiveReset(errorCode); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } }
java/org/apache/coyote/http2/Stream.java+16 −0 modified@@ -25,6 +25,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; @@ -88,6 +89,7 @@ class Stream extends AbstractNonZeroStream implements HeaderEmitter { private final StreamInputBuffer inputBuffer; private final StreamOutputBuffer streamOutputBuffer = new StreamOutputBuffer(); private final Http2OutputBuffer http2OutputBuffer = new Http2OutputBuffer(coyoteResponse, streamOutputBuffer); + private final AtomicBoolean removedFromActiveCount = new AtomicBoolean(false); // State machine would be too much overhead private int headerState = HEADER_STATE_START; @@ -838,6 +840,20 @@ public void setIncremental(boolean incremental) { } + int decrementAndGetActiveRemoteStreamCount() { + /* + * Protect against mis-counting of active streams. This method should only be called once per stream but since + * the count of active streams is used to enforce the maximum concurrent streams limit, make sure each stream is + * only removed from the active count exactly once. + */ + if (removedFromActiveCount.compareAndSet(false, true)) { + return handler.activeRemoteStreamCount.decrementAndGet(); + } else { + return handler.activeRemoteStreamCount.get(); + } + } + + class StreamOutputBuffer implements HttpOutputBuffer, WriteBuffer.Sink { private final Lock writeLock = new ReentrantLock();
webapps/docs/changelog.xml+4 −0 modified@@ -161,6 +161,10 @@ <code>Connector</code> element, similar to the <code>Executor</code> element, for consistency. (remm) </update> + <fix> + Make counting of active HTTP/2 streams per connection more robust. + (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
9fec9a828878Make counting of active streams more robust
4 files changed · +31 −11
java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java+1 −1 modified@@ -156,7 +156,7 @@ void sendStreamReset(StreamStateMachine state, StreamException se) throws IOExce boolean active = state.isActive(); state.sendReset(); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(getStream(se.getStreamId())); } }
java/org/apache/coyote/http2/Http2UpgradeHandler.java+10 −10 modified@@ -288,8 +288,8 @@ protected void processStreamOnContainerThread(Stream stream) { } - protected void decrementActiveRemoteStreamCount() { - setConnectionTimeoutForStreamCount(activeRemoteStreamCount.decrementAndGet()); + protected void decrementActiveRemoteStreamCount(Stream stream) { + setConnectionTimeoutForStreamCount(stream.decrementAndGetActiveRemoteStreamCount()); } @@ -596,7 +596,7 @@ void sendStreamReset(StreamStateMachine state, StreamException se) throws IOExce boolean active = state.isActive(); state.sendReset(); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(getStream(se.getStreamId())); } } socketWrapper.write(true, rstFrame, 0, rstFrame.length); @@ -839,7 +839,7 @@ void writeBody(Stream stream, ByteBuffer data, int len, boolean finished) throws protected void sentEndOfStream(Stream stream) { stream.sentEndOfStream(); if (!stream.isActive()) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } @@ -1221,7 +1221,7 @@ private int allocate(AbstractStream stream, int allocation) { } - private Stream getStream(int streamId) { + Stream getStream(int streamId) { Integer key = Integer.valueOf(streamId); AbstractStream result = streams.get(key); if (result instanceof Stream) { @@ -1590,6 +1590,7 @@ public HeaderEmitter headersStart(int streamId, boolean headersEndStream) throws Stream stream = getStream(streamId, false); if (stream == null) { stream = createRemoteStream(streamId); + activeRemoteStreamCount.incrementAndGet(); } if (streamId < maxActiveRemoteStreamId) { throw new ConnectionException(sm.getString("upgradeHandler.stream.old", Integer.valueOf(streamId), @@ -1668,9 +1669,8 @@ public void headersEnd(int streamId, boolean endOfStream) throws Http2Exception Stream stream = (Stream) abstractNonZeroStream; if (stream.isActive()) { if (stream.receivedEndOfHeaders()) { - - if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.incrementAndGet()) { - decrementActiveRemoteStreamCount(); + if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.get()) { + decrementActiveRemoteStreamCount(stream); // Ignoring maxConcurrentStreams increases the overhead count increaseOverheadCount(FrameType.HEADERS); throw new StreamException( @@ -1714,7 +1714,7 @@ public void receivedEndOfStream(int streamId) throws ConnectionException { private void receivedEndOfStream(Stream stream) throws ConnectionException { stream.receivedEndOfStream(); if (!stream.isActive()) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } @@ -1740,7 +1740,7 @@ public void reset(int streamId, long errorCode) throws Http2Exception { boolean active = stream.isActive(); stream.receiveReset(errorCode); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } }
java/org/apache/coyote/http2/Stream.java+16 −0 modified@@ -28,6 +28,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; @@ -92,6 +93,7 @@ class Stream extends AbstractNonZeroStream implements HeaderEmitter { private final StreamInputBuffer inputBuffer; private final StreamOutputBuffer streamOutputBuffer = new StreamOutputBuffer(); private final Http2OutputBuffer http2OutputBuffer = new Http2OutputBuffer(coyoteResponse, streamOutputBuffer); + private final AtomicBoolean removedFromActiveCount = new AtomicBoolean(false); // State machine would be too much overhead private int headerState = HEADER_STATE_START; @@ -883,6 +885,20 @@ public void setIncremental(boolean incremental) { } + int decrementAndGetActiveRemoteStreamCount() { + /* + * Protect against mis-counting of active streams. This method should only be called once per stream but since + * the count of active streams is used to enforce the maximum concurrent streams limit, make sure each stream is + * only removed from the active count exactly once. + */ + if (removedFromActiveCount.compareAndSet(false, true)) { + return handler.activeRemoteStreamCount.decrementAndGet(); + } else { + return handler.activeRemoteStreamCount.get(); + } + } + + private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException { if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
webapps/docs/changelog.xml+4 −0 modified@@ -165,6 +165,10 @@ <code>Connector</code> element, similar to the <code>Executor</code> element, for consistency. (remm) </update> + <fix> + Make counting of active HTTP/2 streams per connection more robust. + (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
2afae300c9acMake counting of active streams more robust
4 files changed · +31 −11
java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java+1 −1 modified@@ -157,7 +157,7 @@ void sendStreamReset(StreamStateMachine state, StreamException se) throws IOExce boolean active = state.isActive(); state.sendReset(); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(getStream(se.getStreamId())); } }
java/org/apache/coyote/http2/Http2UpgradeHandler.java+10 −10 modified@@ -291,8 +291,8 @@ protected void processStreamOnContainerThread(Stream stream) { } - protected void decrementActiveRemoteStreamCount() { - setConnectionTimeoutForStreamCount(activeRemoteStreamCount.decrementAndGet()); + protected void decrementActiveRemoteStreamCount(Stream stream) { + setConnectionTimeoutForStreamCount(stream.decrementAndGetActiveRemoteStreamCount()); } @@ -599,7 +599,7 @@ void sendStreamReset(StreamStateMachine state, StreamException se) throws IOExce boolean active = state.isActive(); state.sendReset(); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(getStream(se.getStreamId())); } } socketWrapper.write(true, rstFrame, 0, rstFrame.length); @@ -844,7 +844,7 @@ void writeBody(Stream stream, ByteBuffer data, int len, boolean finished) throws protected void sentEndOfStream(Stream stream) { stream.sentEndOfStream(); if (!stream.isActive()) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } @@ -1227,7 +1227,7 @@ private int allocate(AbstractStream stream, int allocation) { } - private Stream getStream(int streamId) { + Stream getStream(int streamId) { Integer key = Integer.valueOf(streamId); AbstractStream result = streams.get(key); if (result instanceof Stream) { @@ -1596,6 +1596,7 @@ public HeaderEmitter headersStart(int streamId, boolean headersEndStream) throws Stream stream = getStream(streamId, false); if (stream == null) { stream = createRemoteStream(streamId); + activeRemoteStreamCount.incrementAndGet(); } if (streamId < maxActiveRemoteStreamId) { throw new ConnectionException(sm.getString("upgradeHandler.stream.old", Integer.valueOf(streamId), @@ -1674,9 +1675,8 @@ public void headersEnd(int streamId, boolean endOfStream) throws Http2Exception Stream stream = (Stream) abstractNonZeroStream; if (stream.isActive()) { if (stream.receivedEndOfHeaders()) { - - if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.incrementAndGet()) { - decrementActiveRemoteStreamCount(); + if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.get()) { + decrementActiveRemoteStreamCount(stream); // Ignoring maxConcurrentStreams increases the overhead count increaseOverheadCount(FrameType.HEADERS); throw new StreamException( @@ -1720,7 +1720,7 @@ public void receivedEndOfStream(int streamId) throws ConnectionException { private void receivedEndOfStream(Stream stream) throws ConnectionException { stream.receivedEndOfStream(); if (!stream.isActive()) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } @@ -1746,7 +1746,7 @@ public void reset(int streamId, long errorCode) throws Http2Exception { boolean active = stream.isActive(); stream.receiveReset(errorCode); if (active) { - decrementActiveRemoteStreamCount(); + decrementActiveRemoteStreamCount(stream); } } }
java/org/apache/coyote/http2/Stream.java+16 −0 modified@@ -28,6 +28,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; @@ -91,6 +92,7 @@ class Stream extends AbstractNonZeroStream implements HeaderEmitter { private final StreamInputBuffer inputBuffer; private final StreamOutputBuffer streamOutputBuffer = new StreamOutputBuffer(); private final Http2OutputBuffer http2OutputBuffer = new Http2OutputBuffer(coyoteResponse, streamOutputBuffer); + private final AtomicBoolean removedFromActiveCount = new AtomicBoolean(false); // State machine would be too much overhead private int headerState = HEADER_STATE_START; @@ -885,6 +887,20 @@ public void setIncremental(boolean incremental) { } + int decrementAndGetActiveRemoteStreamCount() { + /* + * Protect against mis-counting of active streams. This method should only be called once per stream but since + * the count of active streams is used to enforce the maximum concurrent streams limit, make sure each stream is + * only removed from the active count exactly once. + */ + if (removedFromActiveCount.compareAndSet(false, true)) { + return handler.activeRemoteStreamCount.decrementAndGet(); + } else { + return handler.activeRemoteStreamCount.get(); + } + } + + private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException { if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
webapps/docs/changelog.xml+4 −0 modified@@ -161,6 +161,10 @@ <code>Connector</code> element, similar to the <code>Executor</code> element, for consistency. (remm) </update> + <fix> + Make counting of active HTTP/2 streams per connection more robust. + (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-wm9w-rjj3-j356ghsaADVISORY
- lists.apache.org/thread/4kqf0bc9gxymjc2x7v3p7dvplnl77y8lghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2024-34750ghsaADVISORY
- github.com/apache/tomcat/commit/2344a4c0d03e307ba6b8ab6dc8b894cc8bac63f2ghsaWEB
- github.com/apache/tomcat/commit/2afae300c9ac9c0e516e2e9de580847d925365c3ghsaWEB
- github.com/apache/tomcat/commit/9fec9a82887853402833a80b584e3762c7423f5fghsaWEB
- lists.debian.org/debian-lts-announce/2025/07/msg00009.htmlghsaWEB
- security.netapp.com/advisory/ntap-20240816-0004ghsaWEB
- tomcat.apache.org/security-10.htmlghsaWEB
- tomcat.apache.org/security-11.htmlghsaWEB
- tomcat.apache.org/security-9.htmlghsaWEB
News mentions
0No linked articles in our index yet.