new file mode 100644
@@ -0,0 +1,181 @@
+From 8442ca4c699566cdd7369e09690926f403b54fc9 Mon Sep 17 00:00:00 2001
+From: Glenn Strauss <gstrauss@gluelogic.com>
+Date: Wed, 13 Aug 2025 08:52:15 -0400
+Subject: [PATCH] [h2] attempt to detect HTTP/2 MadeYouReset DoS
+
+attempt to detect HTTP/2 MadeYouReset DoS attack VU#767506 CVE-2025-8671
+
+Upstream-Status: Backport
+
+x-ref:
+ https://kb.cert.org/vuls/id/767506
+ https://www.cve.org/CVERecord?id=CVE-2025-8671
+---
+ src/h2.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
+ src/h2.h | 1 +
+ 2 files changed, 103 insertions(+), 4 deletions(-)
+
+diff --git a/src/h2.c b/src/h2.c
+index 7926a5e9..b4299453 100644
+--- a/src/h2.c
++++ b/src/h2.c
+@@ -349,6 +349,11 @@ h2_send_rst_stream_state (request_st * const r, h2con * const h2c)
+ }
+
+
++__attribute_cold__
++static void
++h2_send_goaway_e (connection * const con, const request_h2error_t e);
++
++
+ __attribute_cold__
+ static void
+ h2_send_rst_stream (request_st * const r, connection * const con, const request_h2error_t e)
+@@ -356,6 +361,97 @@ h2_send_rst_stream (request_st * const r, connection * const con, const request_
+ /*(set r->x.h2.state=H2_STATE_CLOSED)*/
+ h2_send_rst_stream_state(r, (h2con *)con->hx);
+ h2_send_rst_stream_id(r->x.h2.id, con, e);
++
++ /* attempt to detect HTTP/2 MadeYouReset DoS attack VU#767506 CVE-2025-8671
++ * heuristic to detect excessive err sent by client to cause reset by server
++ * Ignore H2_E_NO_ERROR and H2_E_INTERNAL_ERROR.
++ * Were H2_E_INTERNAL_ERROR to be included, there might be false positives
++ * (not attacks) in the count. Ignoring H2_E_INTERNAL_ERROR here does not
++ * count *response* headers too long, but that is not a client error.
++ * Ignore H2_E_REFUSED_STREAM, which is counted separately, elsewhere,
++ * but not listed in conditional below since H2_E_REFUSED_STREAM is sent
++ * directly via h2_send_rst_stream_id(), not h2_send_rst_stream()
++ * Include all other errors, though some are more prevalent than others:
++ * H2_E_PROTOCOL_ERROR, H2_E_FLOW_CONTROL_ERROR, H2_E_STREAM_CLOSED,
++ * H2_E_FRAME_SIZE_ERROR, H2_E_COMPRESSION_ERROR, ...
++ * Many such errors are sent with GOAWAY, so not as relevant to count here.
++ * If r->x.h2.state is not H2_STATE_CLOSED, include H2_E_STREAM_CLOSED here.
++ *
++ * Errors for unrecognized (not currently active) stream id are not counted
++ * here, but also do not affect potentially in-progress streams which are
++ * consuming resources in lighttpd and/or backends, e.g. if request headers
++ * are not yet complete, a backend to handle request has not been started.
++ *
++ * Similar to h2_recv_rst_stream() for HTTP/2 Rapid Reset attack,
++ * send GOAWAY with H2_E_NO_ERROR if count exceeds the policy limit since if
++ * peer is triggering server to send RST_STREAM, the peer is misbehaving,
++ * whether or not it is multiplexing requests from different clients, but a
++ * naive peer multiplexing requests from different clients could result in
++ * more reset (failed) streams of valid streams if one client could trigger
++ * too many resets sent by server on a single multiplexed connection, and
++ * server resets all streams and sends GOAWAY w/ error (not H2_E_NO_ERROR).
++ * log watchers such as fail2ban could watch for error log trace indicating
++ * detection of this attack, and could respond accordingly, across multiple
++ * servers. In lighttpd, a client could trigger server-sent reset stream w/
++ * e.g. mismatch between received data and Content-Length, when provided.
++ */
++ if (e != H2_E_NO_ERROR && e != H2_E_INTERNAL_ERROR) {
++ /* simulate receiving TCP FIN from client to trigger imminent shutdown()
++ * on socket connection to backend, indicating request terminated.
++ * Note: mod_cgi must be configured for this to have any effect,
++ * e.g. cgi.limits += ("tcp-fin-propagate" => "SIGTERM")
++ * Regardless of whether or not this optimization is performed,
++ * lighttpd will schedule close() on backend socket (or CGI pipe)
++ * and will close() backend socket (or kill CGI) upon next poll cycle */
++ /*r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_TCP_FIN;*/
++ if (r->handler_module)
++ joblist_append(con); /*(cause short poll for next poll cycle)*/
++
++ /* increment h2c->n_send_rst_stream_err and check for policy violation
++ *
++ * time step interval currently 2 secs: (log_monotonic_secs >> 1)
++ * store time bits in upper nibble of h2c->n_send_rst_stream_err
++ * (32-second time slice: ((log_monotonic_secs >> 1) & 0xF))
++ * time_bits are only 4 bits, so repeated time_bits could cause false
++ * positive and not decay the counter, but well-behaved peers should
++ * not trigger *any* RST_STREAM, so tripping the policy sooner is ok.
++ * (rather than potentially missing policy violation (false negative))
++ * decay counter (divide by 2 (>> 1)) when time step interval changes
++ * (any time interval change; not shifting by (cur_bits - time_bits))
++ * counter is 4 bits, so max is 15 (0xF) unless bit masks are adjusted
++ *
++ * XXX: server triggered to send RST_STREAM w/ error is unexpected
++ * A stricter implementation might send GOAWAY H2_E_NO_ERROR
++ * upon first occurrence.
++ */
++ h2con * const h2c = (h2con *)con->hx;
++ uint8_t cur_bits = (log_monotonic_secs >> 1) & 0xF;
++ uint8_t time_bits = h2c->n_send_rst_stream_err >> 4;
++ if (cur_bits != time_bits)
++ h2c->n_send_rst_stream_err =
++ (cur_bits << 4) | ((h2c->n_send_rst_stream_err & 0xF) >> 1);
++ if (!h2c->sent_goaway && (++h2c->n_send_rst_stream_err & 0xF) > 4) {
++ log_error(NULL, __FILE__, __LINE__,
++ "h2: %s triggered too many RST_STREAM too quickly (xaddr:%s)",
++ con->request.dst_addr_buf->ptr, r->dst_addr_buf->ptr);
++ h2_send_goaway_e(con, H2_E_NO_ERROR);
++ /* h2_send_goaway_e w/ H2_E_PROTOCOL_ERROR or H2_E_ENHANCE_YOUR_CALM
++ * would cause other request streams to be reset (and would have to
++ * check h2c->send_goaway <= 0 above instead of !h2c->sent_goaway)*/
++ }
++ }
++}
++
++
++__attribute_cold__
++__attribute_noinline__
++static void
++h2_send_rst_stream_closed (request_st * const r, connection * const con)
++{
++ if (r->x.h2.state == H2_STATE_CLOSED) /*already closed; rst_stream_id only*/
++ h2_send_rst_stream_id(r->x.h2.id, con, H2_E_STREAM_CLOSED);
++ else /*(r->x.h2.state == H2_STATE_HALF_CLOSED_REMOTE)*/
++ h2_send_rst_stream(r, con, H2_E_STREAM_CLOSED);
+ }
+
+
+@@ -593,6 +689,8 @@ h2_recv_rst_stream (connection * const con, const uint8_t * const s, const uint3
+ /* XXX: ? add debug trace including error code from RST_STREAM ? */
+ r->state = CON_STATE_ERROR;
+ r->x.h2.state = H2_STATE_CLOSED;
++ if (r->handler_module)
++ joblist_append(con); /*(cause short poll for next poll cycle)*/
+
+ /* attempt to detect HTTP/2 rapid reset attack (CVE-2023-44487)
+ * Send GOAWAY if 17 or more requests in recent batch of up to 32
+@@ -608,8 +706,8 @@ h2_recv_rst_stream (connection * const con, const uint8_t * const s, const uint3
+ (h2c->n_recv_rst_stream >> 4) + (h2c->n_recv_rst_stream & 0xf);
+ if (n_recv_rst_stream > 16) {
+ log_error(NULL, __FILE__, __LINE__,
+- "h2: %s sent too many RST_STREAM too quickly",
+- con->request.dst_addr_buf->ptr);
++ "h2: %s sent too many RST_STREAM too quickly (xaddr:%s)",
++ con->request.dst_addr_buf->ptr, r->dst_addr_buf->ptr);
+ h2_send_goaway_e(con, H2_E_NO_ERROR);
+ }
+ }
+@@ -1137,7 +1235,7 @@ h2_recv_data (connection * const con, const uint8_t * const s, const uint32_t le
+
+ if (r->x.h2.state == H2_STATE_CLOSED
+ || r->x.h2.state == H2_STATE_HALF_CLOSED_REMOTE) {
+- h2_send_rst_stream_id(id, con, H2_E_STREAM_CLOSED);
++ h2_send_rst_stream_closed(r, con); /* H2_E_STREAM_CLOSED */
+ chunkqueue_mark_written(cq, 9+len);
+ h2_send_window_update_unit(con, h2r, len); /*(h2r->x.h2.rwin)*/
+ return 1;
+@@ -1515,7 +1613,7 @@ h2_recv_trailers_r (connection * const con, h2con * const h2c, const uint32_t id
+ }
+ if (r->x.h2.state != H2_STATE_OPEN
+ && r->x.h2.state != H2_STATE_HALF_CLOSED_LOCAL) {
+- h2_send_rst_stream(r, con, H2_E_STREAM_CLOSED);
++ h2_send_rst_stream_closed(r, con); /* H2_E_STREAM_CLOSED */
+ return NULL;
+ }
+ /* RFC 7540 is not explicit in restricting HEADERS (trailers) following
+diff --git a/src/h2.h b/src/h2.h
+index 2112c637..53541fe5 100644
+--- a/src/h2.h
++++ b/src/h2.h
+@@ -91,6 +91,7 @@ struct h2con {
+ uint8_t n_refused_stream;
+ uint8_t n_discarded_headers;
+ uint8_t n_recv_rst_stream;
++ uint8_t n_send_rst_stream_err;
+ };
+ typedef struct h2con h2con;
+
@@ -14,6 +14,7 @@ SRC_URI = "http://download.lighttpd.net/lighttpd/releases-1.4.x/lighttpd-${PV}.t
file://index.html.lighttpd \
file://lighttpd.conf \
file://lighttpd \
+ file://CVE-2025-8671.patch \
"
SRC_URI[sha256sum] = "5c08736e83088f7e019797159f306e88ec729abe976dc98fb3bed71b9d3e53b5"
Fixes: CVE-2025-8671 From Glenn Strauss <gstrauss@gluelogic.com> CVE: CVE-2025-8671 Lighttpd is prone to a denial of service (DoS) vulnerability in the HTTP/2 protocol dubbed 'MadeYouReset' applying commit https://git.lighttpd.net/lighttpd/lighttpd1.4/commit/8442ca4c699566cdd7369e09690926f403b54fc9 Signed-off-by: Karim Harouat <karim.harouat@ekinops.com> --- .../lighttpd/lighttpd/CVE-2025-8671.patch | 181 ++++++++++++++++++ .../lighttpd/lighttpd_1.4.74.bb | 1 + 2 files changed, 182 insertions(+) create mode 100644 meta/recipes-extended/lighttpd/lighttpd/CVE-2025-8671.patch