Description: Fix HTTP/2 RST stream causing deferred memory reclamation
 When an HTTP/2 stream is reset by a client before it is registered in the
 connection's stream map, memory resources are not reclaimed immediately.
 An attacker can keep the connection busy sending requests and resets,
 growing memory until connection close. Backport of upstream fix to 2.4.29.
Origin: backport, https://github.com/apache/httpd/commit/decce82a706abd78dfc32821a03ad93841d7758a
Bug: https://www.cve.org/CVERecord?id=CVE-2023-45802
Bug-Ubuntu: https://launchpad.net/bugs/usn-7554-1
Author: Allen Huang <allen.huang@canonical.com>
Last-Update: 2025-05-20

Index: apache2-2.4.29/modules/http2/h2_mplx.c
===================================================================
--- apache2-2.4.29.orig/modules/http2/h2_mplx.c	2026-05-20 18:28:07.066258521 +0100
+++ apache2-2.4.29/modules/http2/h2_mplx.c	2026-05-20 21:11:22.578741707 +0100
@@ -1133,14 +1133,31 @@
     return waiting;
 }
 
-apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id)
+apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id, h2_stream *stream)
 {
-    h2_stream *stream;
     apr_status_t status = APR_SUCCESS;
-    
+    int registered;
+
     H2_MPLX_ENTER_ALWAYS(m);
-    stream = h2_ihash_get(m->streams, stream_id);
-    if (stream && stream->task) {
+    registered = (h2_ihash_get(m->streams, stream_id) != NULL);
+    if (!stream) {
+        /* a RST might arrive so late, we have already forgotten
+         * about it. Seems ok. */
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+                      H2_MPLX_MSG(m, "RST on unknown stream %d"), stream_id);
+    }
+    else if (!registered) {
+        /* a RST on a stream that mplx has not been told about, but
+         * which the session knows. Very early and annoying. */
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+                      H2_STRM_MSG(stream, "very early RST, drop"));
+        h2_stream_set_monitor(stream, NULL);
+        h2_stream_rst(stream, H2_ERR_NO_ERROR);
+        h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
+        m_stream_cleanup(m, stream);
+        status = m_be_annoyed(m);
+    }
+    else if (stream && stream->task) {
         status = m_be_annoyed(m);
     }
     H2_MPLX_LEAVE(m);
Index: apache2-2.4.29/modules/http2/h2_mplx.h
===================================================================
--- apache2-2.4.29.orig/modules/http2/h2_mplx.h	2026-05-20 18:28:06.347257165 +0100
+++ apache2-2.4.29/modules/http2/h2_mplx.h	2026-05-20 21:11:11.791739175 +0100
@@ -187,7 +187,8 @@
 
 apr_status_t h2_mplx_m_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
 
-apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id);
+apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id,
+                                   struct h2_stream *stream);
 
 /**
  * Master connection has entered idle mode.
Index: apache2-2.4.29/modules/http2/h2_session.c
===================================================================
--- apache2-2.4.29.orig/modules/http2/h2_session.c	2026-05-20 18:28:09.623263338 +0100
+++ apache2-2.4.29/modules/http2/h2_session.c	2026-05-20 21:11:25.995742509 +0100
@@ -419,7 +419,7 @@
             else {
                 /* A stream reset on a request it sent us. Could happen in a browser
                  * when the user navigates away or cancels loading - maybe. */
-                h2_mplx_m_client_rst(session->mplx, frame->hd.stream_id);
+                h2_mplx_m_client_rst(session->mplx, frame->hd.stream_id, stream);
                 ++session->streams_reset;
             }
             break;
