Skip to content

Commit 26cb4aa

Browse files
committed
* Fixed a bug that prevented immediate memory release of reset streams
on rare occasions. Memory was reclaimed instead at connection close. This could be exploited, unless nghttp2 v1.57.0 is used, in a variation of the Rapid Reset attack. Apache httpd issued CVE-2023-45802 for this. * Synchronized with Apache httpd svn trunk
1 parent 0a86478 commit 26cb4aa

10 files changed

Lines changed: 83 additions & 18 deletions

File tree

ChangeLog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
* Fixed a bug that prevented immediate memory release of reset streams
2+
on rare occasions. Memory was reclaimed instead at connection close.
3+
This could be exploited, unless nghttp2 v1.57.0 is used, in a variation
4+
of the Rapid Reset attack. Apache httpd issued CVE-2023-45802 for this.
5+
* Synchronized with Apache httpd svn trunk
6+
17
v2.0.24
28
--------------------------------------------------------------------------------
39
* fixed a compile time issue for Windows builds.

mod_http2/h2.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include <apr_version.h>
2121
#include <ap_mmn.h>
2222

23+
#include <nghttp2/nghttp2ver.h>
24+
2325
struct h2_session;
2426
struct h2_stream;
2527

@@ -39,7 +41,9 @@ struct h2_stream;
3941
#define H2_USE_POLLFD_FROM_CONN 0
4042
#endif
4143

42-
#if H2_USE_PIPES
44+
/* WebSockets support requires apr 1.7.0 for apr_encode.h, plus the
45+
* WebSockets features of nghttp2 1.34.0 and later. */
46+
#if H2_USE_PIPES && defined(NGHTTP2_VERSION_NUM) && NGHTTP2_VERSION_NUM >= 0x012200 && APR_VERSION_AT_LEAST(1,7,0)
4347
#define H2_USE_WEBSOCKETS 1
4448
#else
4549
#define H2_USE_WEBSOCKETS 0

mod_http2/h2_mplx.c

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,11 @@ apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
384384
{
385385
stream_iter_ctx_t x;
386386

387-
H2_MPLX_ENTER(m);
388-
389387
x.cb = cb;
390388
x.ctx = ctx;
389+
390+
H2_MPLX_ENTER(m);
391+
391392
h2_ihash_iter(m->streams, m_stream_iter_wrap, &x);
392393

393394
H2_MPLX_LEAVE(m);
@@ -1119,14 +1120,32 @@ static int reset_is_acceptable(h2_stream *stream)
11191120
return 1; /* otherwise, be forgiving */
11201121
}
11211122

1122-
apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id)
1123+
apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id, h2_stream *stream)
11231124
{
1124-
h2_stream *stream;
11251125
apr_status_t status = APR_SUCCESS;
1126+
int registered;
11261127

11271128
H2_MPLX_ENTER_ALWAYS(m);
1128-
stream = h2_ihash_get(m->streams, stream_id);
1129-
if (stream && !reset_is_acceptable(stream)) {
1129+
registered = (h2_ihash_get(m->streams, stream_id) != NULL);
1130+
if (!stream) {
1131+
/* a RST might arrive so late, we have already forgotten
1132+
* about it. Seems ok. */
1133+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
1134+
H2_MPLX_MSG(m, "RST on unknown stream %d"), stream_id);
1135+
AP_DEBUG_ASSERT(!registered);
1136+
}
1137+
else if (!registered) {
1138+
/* a RST on a stream that mplx has not been told about, but
1139+
* which the session knows. Very early and annoying. */
1140+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
1141+
H2_STRM_MSG(stream, "very early RST, drop"));
1142+
h2_stream_set_monitor(stream, NULL);
1143+
h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
1144+
h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
1145+
m_stream_cleanup(m, stream);
1146+
m_be_annoyed(m);
1147+
}
1148+
else if (!reset_is_acceptable(stream)) {
11301149
m_be_annoyed(m);
11311150
}
11321151
H2_MPLX_LEAVE(m);

mod_http2/h2_mplx.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ int h2_mplx_c1_all_streams_want_send_data(h2_mplx *m);
201201
* any processing going on and remove from processing
202202
* queue.
203203
*/
204-
apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id);
204+
apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id,
205+
struct h2_stream *stream);
205206

206207
/**
207208
* Get readonly access to a stream for a secondary connection.

mod_http2/h2_proxy_session.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ static apr_status_t session_start(h2_proxy_session *session)
789789
apr_socket_t *s;
790790

791791
s = ap_get_conn_socket(session->c);
792-
#if (!defined(WIN32) && !defined(NETWARE)) || defined(DOXYGEN)
792+
#if !defined(WIN32) && !defined(NETWARE)
793793
if (s) {
794794
ap_sock_disable_nagle(s);
795795
}
@@ -1681,7 +1681,17 @@ static int done_iter(void *udata, void *val)
16811681
h2_proxy_stream *stream = val;
16821682
int touched = (stream->data_sent || stream->data_received ||
16831683
stream->id <= ctx->session->last_stream_id);
1684-
ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched, stream->error_code);
1684+
if (touched && stream->output) {
1685+
apr_bucket *b = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL,
1686+
stream->r->pool,
1687+
stream->cfront->bucket_alloc);
1688+
APR_BRIGADE_INSERT_TAIL(stream->output, b);
1689+
b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
1690+
APR_BRIGADE_INSERT_TAIL(stream->output, b);
1691+
ap_pass_brigade(stream->r->output_filters, stream->output);
1692+
}
1693+
ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched,
1694+
stream->error_code);
16851695
return 1;
16861696
}
16871697

mod_http2/h2_session.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,10 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
402402
H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
403403
"RST_STREAM by client, error=%d"),
404404
(int)frame->rst_stream.error_code);
405+
if (stream) {
406+
rv = h2_stream_recv_frame(stream, NGHTTP2_RST_STREAM, frame->hd.flags,
407+
frame->hd.length + H2_FRAME_HDR_LEN);
408+
}
405409
if (stream && stream->initiated_on) {
406410
/* A stream reset on a request we sent it. Normal, when the
407411
* client does not want it. */
@@ -410,7 +414,8 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
410414
else {
411415
/* A stream reset on a request it sent us. Could happen in a browser
412416
* when the user navigates away or cancels loading - maybe. */
413-
h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id);
417+
h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id,
418+
stream);
414419
}
415420
++session->streams_reset;
416421
break;
@@ -812,6 +817,17 @@ static apr_status_t session_cleanup(h2_session *session, const char *trigger)
812817
"goodbye, clients will be confused, should not happen"));
813818
}
814819

820+
if (!h2_iq_empty(session->ready_to_process)) {
821+
int sid;
822+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
823+
H2_SSSN_LOG(APLOGNO(10485), session,
824+
"cleanup, resetting %d streams in ready-to-process"),
825+
h2_iq_count(session->ready_to_process));
826+
while ((sid = h2_iq_shift(session->ready_to_process)) > 0) {
827+
h2_mplx_c1_client_rst(session->mplx, sid, get_stream(session, sid));
828+
}
829+
}
830+
815831
transit(session, trigger, H2_SESSION_ST_CLEANUP);
816832
h2_mplx_c1_destroy(session->mplx);
817833
session->mplx = NULL;
@@ -1069,11 +1085,13 @@ static apr_status_t h2_session_start(h2_session *session, int *rv)
10691085
settings[slen].value = win_size;
10701086
++slen;
10711087
}
1088+
#if H2_USE_WEBSOCKETS
10721089
if (h2_config_sgeti(session->s, H2_CONF_WEBSOCKETS)) {
10731090
settings[slen].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
10741091
settings[slen].value = 1;
10751092
++slen;
10761093
}
1094+
#endif
10771095

10781096
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
10791097
H2_SSSN_LOG(APLOGNO(03201), session,

mod_http2/h2_stream.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ static int trans_on_event[][H2_SS_MAX] = {
125125
{ S_XXX, S_ERR, S_ERR, S_CL_L, S_CLS, S_XXX, S_XXX, S_XXX, },/* EV_CLOSED_L*/
126126
{ S_ERR, S_ERR, S_ERR, S_CL_R, S_ERR, S_CLS, S_NOP, S_NOP, },/* EV_CLOSED_R*/
127127
{ S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_NOP, S_NOP, },/* EV_CANCELLED*/
128-
{ S_NOP, S_XXX, S_XXX, S_XXX, S_XXX, S_CLS, S_CLN, S_XXX, },/* EV_EOS_SENT*/
128+
{ S_NOP, S_XXX, S_XXX, S_XXX, S_XXX, S_CLS, S_CLN, S_NOP, },/* EV_EOS_SENT*/
129129
{ S_NOP, S_XXX, S_CLS, S_XXX, S_XXX, S_CLS, S_XXX, S_XXX, },/* EV_IN_ERROR*/
130130
};
131131

mod_http2/h2_ws.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include "apr.h"
2020
#include "apr_strings.h"
2121
#include "apr_lib.h"
22-
#include "apr_encode.h"
2322
#include "apr_sha1.h"
2423
#include "apr_strmatch.h"
2524

@@ -45,6 +44,8 @@
4544

4645
#if H2_USE_WEBSOCKETS
4746

47+
#include "apr_encode.h" /* H2_USE_WEBSOCKETS is conditional on APR 1.7+ */
48+
4849
static ap_filter_rec_t *c2_ws_out_filter_handle;
4950

5051
struct ws_filter_ctx {
@@ -238,7 +239,7 @@ static void ws_handle_resp(conn_rec *c2, h2_conn_ctx_t *conn_ctx,
238239
* or in the request processings implementation of WebSockets */
239240
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c2, APLOGNO(10463)
240241
"h2_c2(%s-%d): websocket CONNECT, 101 response "
241-
"with 'Sec-WebSocket-Accept: %s' but expected %s",
242+
"without 'Sec-WebSocket-Accept: %s' but expected %s",
242243
conn_ctx->id, conn_ctx->stream_id, hd,
243244
ws_ctx->ws_accept_base64);
244245
}

mod_http2/h2_ws.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* Rewrite a websocket request.
2424
*
2525
* @param req the h2 request to rewrite
26-
* @param c2 the connection to process the request on
26+
* @param conn the connection to process the request on
2727
* @param no_body != 0 iff the request is known to have no body
2828
* @return the websocket request for internal submit
2929
*/

mod_http2/mod_proxy_http2.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ typedef struct h2_proxy_ctx {
6565
unsigned is_ssl : 1;
6666

6767
request_rec *r; /* the request processed in this ctx */
68-
apr_status_t r_status; /* status of request work */
68+
int r_status; /* status of request work */
6969
int r_done; /* request was processed, not necessarily successfully */
7070
int r_may_retry; /* request may be retried */
7171
int has_reusable_session; /* http2 session is live and clean */
@@ -413,7 +413,7 @@ static int proxy_http2_handler(request_rec *r,
413413
"setup new connection: is_ssl=%d %s %s %s",
414414
ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname,
415415
locurl, ctx->p_conn->hostname);
416-
ctx->r_status = status;
416+
ctx->r_status = ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE);
417417
goto cleanup;
418418
}
419419

@@ -427,7 +427,7 @@ static int proxy_http2_handler(request_rec *r,
427427
if (ctx->cfront->aborted) goto cleanup;
428428
status = ctx_run(ctx);
429429

430-
if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->cfront->aborted) {
430+
if (ctx->r_status != OK && ctx->r_may_retry && !ctx->cfront->aborted) {
431431
/* Not successfully processed, but may retry, tear down old conn and start over */
432432
if (ctx->p_conn) {
433433
ctx->p_conn->close = 1;
@@ -463,6 +463,12 @@ static int proxy_http2_handler(request_rec *r,
463463
ap_set_module_config(ctx->cfront->conn_config, &proxy_http2_module, NULL);
464464
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
465465
APLOGNO(03377) "leaving handler");
466+
if (ctx->r_status != OK) {
467+
ap_die(ctx->r_status, r);
468+
}
469+
else if (status != APR_SUCCESS) {
470+
ap_die(ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE), r);
471+
}
466472
return ctx->r_status;
467473
}
468474

0 commit comments

Comments
 (0)