VYPR
High severityNVD Advisory· Published Jul 3, 2024· Updated Nov 3, 2025

Apache Tomcat: HTTP/2 excess header handling DoS

CVE-2024-34750

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.

PackageAffected versionsPatched versions
org.apache.tomcat.embed:tomcat-embed-coreMaven
>= 11.0.0-M1, < 11.0.0-M2111.0.0-M21
org.apache.tomcat.embed:tomcat-embed-coreMaven
>= 10.1.0-M1, < 10.1.2510.1.25
org.apache.tomcat.embed:tomcat-embed-coreMaven
>= 9.0.0-M1, < 9.0.909.0.90
org.apache.tomcat:tomcat-coyoteMaven
>= 11.0.0-M1, < 11.0.0-M2111.0.0-M21
org.apache.tomcat:tomcat-coyoteMaven
>= 10.1.0-M1, < 10.1.2510.1.25
org.apache.tomcat:tomcat-coyoteMaven
>= 9.0.0-M1, < 9.0.909.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

Patches

3
2344a4c0d03e

Make counting of active streams more robust

https://github.com/apache/tomcatMark ThomasMay 8, 2024via ghsa
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">
    
9fec9a828878

Make counting of active streams more robust

https://github.com/apache/tomcatMark ThomasMay 8, 2024via ghsa
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">
    
2afae300c9ac

Make counting of active streams more robust

https://github.com/apache/tomcatMark ThomasMay 8, 2024via ghsa
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

News mentions

0

No linked articles in our index yet.