From 52f85a0e1fd885d5bf9cbb6de74a146aa0d6c843 Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Fri, 23 Apr 2021 19:06:27 -0400 Subject: [PATCH] lighttpd: patches from upstream - ignore Content-Length from backend if 101 Switching Protocols - close HTTP/2 connection after bad password - skip cert chain build for self-issued certs - meson zstd fix - ls-hpack upstream update - discard some HTTP/2 DATA frames received after response Signed-off-by: Glenn Strauss --- net/lighttpd/Makefile | 2 +- net/lighttpd/files/lighttpd.conf | 2 - .../030-101-upgrade-w-content-length.patch | 31 ++++ ...-mod_auth-close-http2-after-bad-pass.patch | 143 +++++++++++++++++ ...openssl-skip-chain-build-self-issued.patch | 45 ++++++ net/lighttpd/patches/060-meson-zstd.patch | 27 ++++ .../patches/070-ls-hpack-update.patch | 56 +++++++ .../080-http2-data-after-response.patch | 145 ++++++++++++++++++ 8 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 net/lighttpd/patches/030-101-upgrade-w-content-length.patch create mode 100644 net/lighttpd/patches/040-mod_auth-close-http2-after-bad-pass.patch create mode 100644 net/lighttpd/patches/050-openssl-skip-chain-build-self-issued.patch create mode 100644 net/lighttpd/patches/060-meson-zstd.patch create mode 100644 net/lighttpd/patches/070-ls-hpack-update.patch create mode 100644 net/lighttpd/patches/080-http2-data-after-response.patch diff --git a/net/lighttpd/Makefile b/net/lighttpd/Makefile index 75947fb95..56e7b8161 100644 --- a/net/lighttpd/Makefile +++ b/net/lighttpd/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=lighttpd PKG_VERSION:=1.4.59 -PKG_RELEASE:=1 +PKG_RELEASE:=2 # release candidate ~rcX testing; remove for release #PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-1.4.59 diff --git a/net/lighttpd/files/lighttpd.conf b/net/lighttpd/files/lighttpd.conf index 3846acf74..079d2a688 100644 --- a/net/lighttpd/files/lighttpd.conf +++ b/net/lighttpd/files/lighttpd.conf @@ -13,8 +13,6 @@ static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" ) ### Features #https://redmine.lighttpd.net/projects/lighttpd/wiki/Server_feature-flagsDetails -server.feature-flags += ("server.h2proto" => "enable") -server.feature-flags += ("server.h2c" => "enable") server.feature-flags += ("server.graceful-shutdown-timeout" => 5) #server.feature-flags += ("server.graceful-restart-bg" => "enable") diff --git a/net/lighttpd/patches/030-101-upgrade-w-content-length.patch b/net/lighttpd/patches/030-101-upgrade-w-content-length.patch new file mode 100644 index 000000000..d4619325a --- /dev/null +++ b/net/lighttpd/patches/030-101-upgrade-w-content-length.patch @@ -0,0 +1,31 @@ +From 1ca25d4e2cfeb83c844ad52b9c94eac218c71379 Mon Sep 17 00:00:00 2001 +From: Glenn Strauss +Date: Thu, 4 Feb 2021 00:22:12 -0500 +Subject: [PATCH] [core] 101 upgrade fails if Content-Length incl (fixes #3063) + +(thx daimh) + +commit 903024d7 in lighttpd 1.4.57 fixed issue #3046 but in the process +broke HTTP/1.1 101 Switching Protocols which included Content-Length: 0 +in the response headers. Content-Length response header is permitted +by the RFCs, but not necessary with HTTP status 101 Switching Protocols. + +x-ref: + "websocket proxy fails if 101 Switching Protocols from backend includes Content-Length" + https://redmine.lighttpd.net/issues/3063 + +Signed-off-by: Glenn Strauss +--- + src/http-header-glue.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/src/http-header-glue.c ++++ b/src/http-header-glue.c +@@ -961,6 +961,7 @@ void http_response_upgrade_read_body_unk + (FDEVENT_STREAM_RESPONSE_BUFMIN | FDEVENT_STREAM_RESPONSE); + r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; + r->reqbody_length = -2; ++ r->resp_body_scratchpad = -1; + r->keep_alive = 0; + } + diff --git a/net/lighttpd/patches/040-mod_auth-close-http2-after-bad-pass.patch b/net/lighttpd/patches/040-mod_auth-close-http2-after-bad-pass.patch new file mode 100644 index 000000000..69c98f70e --- /dev/null +++ b/net/lighttpd/patches/040-mod_auth-close-http2-after-bad-pass.patch @@ -0,0 +1,143 @@ +From 4a600dabd5e2799bf0c3048859ee4f00808b7d89 Mon Sep 17 00:00:00 2001 +From: Glenn Strauss +Date: Sat, 6 Feb 2021 08:29:41 -0500 +Subject: [PATCH] [mod_auth] close HTTP/2 connection after bad pass + +mitigation slows down brute force password attacks + +x-ref: + "Possible feature: authentication brute force hardening" + https://redmine.lighttpd.net/boards/3/topics/8885 + +Signed-off-by: Glenn Strauss +--- + src/connections.c | 22 +++++++++++++++++++++- + src/mod_accesslog.c | 2 +- + src/mod_auth.c | 6 +++--- + src/reqpool.c | 1 + + src/request.h | 2 +- + src/response.c | 4 ++-- + 6 files changed, 29 insertions(+), 8 deletions(-) + +--- a/src/connections.c ++++ b/src/connections.c +@@ -228,7 +228,7 @@ static void connection_handle_response_e + } + } + +- if (r->keep_alive) { ++ if (r->keep_alive > 0) { + request_reset(r); + config_reset_config(r); + con->is_readable = 1; /* potentially trigger optimistic read */ +@@ -1265,6 +1265,19 @@ connection_set_fdevent_interest (request + } + + ++__attribute_cold__ ++static void ++connection_request_end_h2 (request_st * const h2r, connection * const con) ++{ ++ if (h2r->keep_alive >= 0) { ++ h2r->keep_alive = -1; ++ h2_send_goaway(con, H2_E_NO_ERROR); ++ } ++ else /*(abort connection upon second request to close h2 connection)*/ ++ h2_send_goaway(con, H2_E_ENHANCE_YOUR_CALM); ++} ++ ++ + static void + connection_state_machine_h2 (request_st * const h2r, connection * const con) + { +@@ -1359,8 +1372,15 @@ connection_state_machine_h2 (request_st + && !chunkqueue_is_empty(con->read_queue)) + resched |= 1; + h2_send_end_stream(r, con); ++ const int alive = r->keep_alive; + h2_retire_stream(r, con);/*r invalidated;removed from h2c->r[]*/ + --i;/* adjust loop i; h2c->rused was modified to retire r */ ++ /*(special-case: allow *stream* to set r->keep_alive = -1 to ++ * trigger goaway on h2 connection, e.g. after mod_auth failure ++ * in attempt to mitigate brute force attacks by forcing a ++ * reconnect and (somewhat) slowing down retries)*/ ++ if (alive < 0) ++ connection_request_end_h2(h2r, con); + } + } + } +--- a/src/mod_accesslog.c ++++ b/src/mod_accesslog.c +@@ -1108,7 +1108,7 @@ static int log_access_record (const requ + break; + case FORMAT_CONNECTION_STATUS: + if (r->state == CON_STATE_RESPONSE_END) { +- if (0 == r->keep_alive) { ++ if (r->keep_alive <= 0) { + buffer_append_string_len(b, CONST_STR_LEN("-")); + } else { + buffer_append_string_len(b, CONST_STR_LEN("+")); +--- a/src/mod_auth.c ++++ b/src/mod_auth.c +@@ -828,7 +828,7 @@ static handler_t mod_auth_check_basic(re + log_error(r->conf.errh, __FILE__, __LINE__, + "password doesn't match for %s username: %s IP: %s", + r->uri.path.ptr, username->ptr, r->con->dst_addr_buf->ptr); +- r->keep_alive = 0; /*(disable keep-alive if bad password)*/ ++ r->keep_alive = -1; /*(disable keep-alive if bad password)*/ + rc = HANDLER_UNSET; + break; + } +@@ -1461,7 +1461,7 @@ static handler_t mod_auth_check_digest(r + return HANDLER_FINISHED; + case HANDLER_ERROR: + default: +- r->keep_alive = 0; /*(disable keep-alive if unknown user)*/ ++ r->keep_alive = -1; /*(disable keep-alive if unknown user)*/ + buffer_free(b); + return mod_auth_send_401_unauthorized_digest(r, require, 0); + } +@@ -1482,7 +1482,7 @@ static handler_t mod_auth_check_digest(r + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: auth failed for %s: wrong password, IP: %s", + username, r->con->dst_addr_buf->ptr); +- r->keep_alive = 0; /*(disable keep-alive if bad password)*/ ++ r->keep_alive = -1; /*(disable keep-alive if bad password)*/ + + buffer_free(b); + return mod_auth_send_401_unauthorized_digest(r, require, 0); +--- a/src/reqpool.c ++++ b/src/reqpool.c +@@ -58,6 +58,7 @@ request_reset (request_st * const r) + http_response_reset(r); + + r->loops_per_request = 0; ++ r->keep_alive = 0; + + r->h2state = 0; /* H2_STATE_IDLE */ + r->h2id = 0; +--- a/src/request.h ++++ b/src/request.h +@@ -175,7 +175,7 @@ struct request_st { + char resp_header_repeated; + + char loops_per_request; /* catch endless loops in a single request */ +- char keep_alive; /* only request.c can enable it, all other just disable */ ++ int8_t keep_alive; /* only request.c can enable it, all other just disable */ + char async_callback; + + buffer *tmp_buf; /* shared; same as srv->tmp_buf */ +--- a/src/response.c ++++ b/src/response.c +@@ -103,9 +103,9 @@ http_response_write_header (request_st * + if (light_btst(r->resp_htags, HTTP_HEADER_UPGRADE) + && r->http_version == HTTP_VERSION_1_1) { + http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("upgrade")); +- } else if (0 == r->keep_alive) { ++ } else if (r->keep_alive <= 0) { + http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("close")); +- } else if (r->http_version == HTTP_VERSION_1_0) {/*(&& r->keep_alive != 0)*/ ++ } else if (r->http_version == HTTP_VERSION_1_0) {/*(&& r->keep_alive > 0)*/ + http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive")); + } + diff --git a/net/lighttpd/patches/050-openssl-skip-chain-build-self-issued.patch b/net/lighttpd/patches/050-openssl-skip-chain-build-self-issued.patch new file mode 100644 index 000000000..9577858c9 --- /dev/null +++ b/net/lighttpd/patches/050-openssl-skip-chain-build-self-issued.patch @@ -0,0 +1,45 @@ +From aa81834bc3ff47aa5cc66b6763678d3cf47a3d54 Mon Sep 17 00:00:00 2001 +From: Glenn Strauss +Date: Fri, 12 Mar 2021 20:03:38 -0500 +Subject: [PATCH] [mod_openssl] skip cert chain build if self-issued + +If cert is self-issued, then do not attempt to build certificate chain. + +(Attempting to build certificate chain when chain is not provided, but + ssl.ca-file is specified, is provided as backward compatible behavior + from lighttpd versions prior to lighttpd 1.4.56) + +Signed-off-by: Glenn Strauss +--- + src/mod_openssl.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +--- a/src/mod_openssl.c ++++ b/src/mod_openssl.c +@@ -103,6 +103,7 @@ typedef struct { + time_t ssl_stapling_loadts; + time_t ssl_stapling_nextts; + char must_staple; ++ char self_issued; + } plugin_cert; + + typedef struct { +@@ -1081,7 +1082,7 @@ mod_openssl_cert_cb (SSL *ssl, void *arg + #if !defined(BORINGSSL_API_VERSION) \ + && !defined(LIBRESSL_VERSION_NUMBER) + /* (missing SSL_set1_chain_cert_store() and SSL_build_cert_chain()) */ +- else if (hctx->conf.ssl_ca_file) { ++ else if (hctx->conf.ssl_ca_file && !pc->self_issued) { + /* preserve legacy behavior whereby openssl will reuse CAs trusted for + * certificate verification (set by SSL_CTX_load_verify_locations() in + * SSL_CTX) in order to build certificate chain for server certificate +@@ -1671,6 +1672,9 @@ network_openssl_load_pemfile (server *sr + #else + pc->must_staple = 0; + #endif ++ pc->self_issued = ++ (0 == X509_NAME_cmp(X509_get_subject_name(ssl_pemfile_x509), ++ X509_get_issuer_name(ssl_pemfile_x509))); + + if (!buffer_string_is_empty(pc->ssl_stapling_file)) { + #ifndef OPENSSL_NO_OCSP diff --git a/net/lighttpd/patches/060-meson-zstd.patch b/net/lighttpd/patches/060-meson-zstd.patch new file mode 100644 index 000000000..138b44351 --- /dev/null +++ b/net/lighttpd/patches/060-meson-zstd.patch @@ -0,0 +1,27 @@ +From c41ebea4bb220c8fe252f472eec836c691734690 Mon Sep 17 00:00:00 2001 +From: Glenn Strauss +Date: Fri, 2 Apr 2021 01:01:02 -0400 +Subject: [PATCH] [build] fix zstd option in meson (fixes #3076) + +(thx KimonHoffmann) + +x-ref: + "Fix zstd dependency handling in meson build" + https://redmine.lighttpd.net/issues/3076 + +Signed-off-by: Glenn Strauss +--- + src/meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/meson.build ++++ b/src/meson.build +@@ -685,7 +685,7 @@ endif + + libzstd = [] + if get_option('with_zstd') +- libz = dependency('zstd', required: false) ++ libzstd = dependency('zstd', required: false) + if libzstd.found() + libzstd = [ libzstd ] + else diff --git a/net/lighttpd/patches/070-ls-hpack-update.patch b/net/lighttpd/patches/070-ls-hpack-update.patch new file mode 100644 index 000000000..1267fa996 --- /dev/null +++ b/net/lighttpd/patches/070-ls-hpack-update.patch @@ -0,0 +1,56 @@ +From 3392e8fb11de35778cad1fb112e6eb5916aa7de0 Mon Sep 17 00:00:00 2001 +From: Glenn Strauss +Date: Tue, 20 Apr 2021 22:04:56 -0400 +Subject: [PATCH] [core] update ls-hpack + +LiteSpeed ls-hpack v2.3.0 + +Signed-off-by: Glenn Strauss +--- + src/ls-hpack/README.md | 2 +- + src/ls-hpack/lshpack.c | 4 +++- + src/ls-hpack/lshpack.h | 6 +++--- + 3 files changed, 7 insertions(+), 5 deletions(-) + +--- a/src/ls-hpack/lshpack.c ++++ b/src/ls-hpack/lshpack.c +@@ -1,7 +1,7 @@ + /* + MIT License + +-Copyright (c) 2018 LiteSpeed Technologies Inc ++Copyright (c) 2018 - 2021 LiteSpeed Technologies Inc + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal +@@ -1549,6 +1549,8 @@ lshpack_dec_push_entry (struct lshpack_d + #endif + memcpy(DTE_NAME(entry), lsxpack_header_get_name(xhdr), name_len); + memcpy(DTE_VALUE(entry), lsxpack_header_get_value(xhdr), val_len); ++ ++ hdec_remove_overflow_entries(dec); + return 0; + } + +--- a/src/ls-hpack/lshpack.h ++++ b/src/ls-hpack/lshpack.h +@@ -1,7 +1,7 @@ + /* + MIT License + +-Copyright (c) 2018 - 2020 LiteSpeed Technologies Inc ++Copyright (c) 2018 - 2021 LiteSpeed Technologies Inc + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal +@@ -34,8 +34,8 @@ extern "C" { + #include "lsxpack_header.h" + + #define LSHPACK_MAJOR_VERSION 2 +-#define LSHPACK_MINOR_VERSION 2 +-#define LSHPACK_PATCH_VERSION 1 ++#define LSHPACK_MINOR_VERSION 3 ++#define LSHPACK_PATCH_VERSION 0 + + #define lshpack_strlen_t lsxpack_strlen_t + #define LSHPACK_MAX_STRLEN LSXPACK_MAX_STRLEN diff --git a/net/lighttpd/patches/080-http2-data-after-response.patch b/net/lighttpd/patches/080-http2-data-after-response.patch new file mode 100644 index 000000000..397aa27a4 --- /dev/null +++ b/net/lighttpd/patches/080-http2-data-after-response.patch @@ -0,0 +1,145 @@ +From 81d18a8e359685c169cfd30e6a1574b98aedbaeb Mon Sep 17 00:00:00 2001 +From: Glenn Strauss +Date: Thu, 22 Apr 2021 01:11:47 -0400 +Subject: [PATCH] [core] discard some HTTP/2 DATA after response (fixes #3078) + +(thx oldium) + +improve handling of HTTP/2 DATA frames received +a short time after sending response + +x-ref: + "POST request DATA part for non-existing URI closes HTTP/2 connection prematurely" + https://redmine.lighttpd.net/issues/3078 + +Signed-off-by: Glenn Strauss +--- + src/h2.c | 64 ++++++++++++++++++++++++++++++++++++++++++-------------- + src/h2.h | 1 + + 2 files changed, 49 insertions(+), 16 deletions(-) + +--- a/src/h2.c ++++ b/src/h2.c +@@ -272,10 +272,23 @@ h2_send_rst_stream_id (uint32_t h2id, co + + __attribute_cold__ + static void +-h2_send_rst_stream (request_st * const r, connection * const con, const request_h2error_t e) ++h2_send_rst_stream_state (request_st * const r, h2con * const h2c) + { ++ if (r->h2state != H2_STATE_HALF_CLOSED_REMOTE ++ && r->h2state != H2_STATE_CLOSED) { ++ /* set timestamp for comparison; not tracking individual stream ids */ ++ h2c->half_closed_ts = log_epoch_secs; ++ } + r->state = CON_STATE_ERROR; + r->h2state = H2_STATE_CLOSED; ++} ++ ++ ++__attribute_cold__ ++static void ++h2_send_rst_stream (request_st * const r, connection * const con, const request_h2error_t e) ++{ ++ h2_send_rst_stream_state(r, con->h2);/*(sets r->h2state = H2_STATE_CLOSED)*/ + h2_send_rst_stream_id(r->h2id, con, e); + } + +@@ -289,13 +302,10 @@ h2_send_goaway_rst_stream (connection * + for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) { + request_st * const r = h2c->r[i]; + if (r->h2state == H2_STATE_CLOSED) continue; ++ h2_send_rst_stream_state(r, h2c);/*(sets r->h2state = H2_STATE_CLOSED)*/ + /*(XXX: might consider always sending RST_STREAM)*/ +- if (!sent_goaway) { +- r->state = CON_STATE_ERROR; +- r->h2state = H2_STATE_CLOSED; +- } +- else /*(also sets r->h2state = H2_STATE_CLOSED)*/ +- h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR); ++ if (sent_goaway) ++ h2_send_rst_stream_id(r->h2id, con, H2_E_PROTOCOL_ERROR); + } + } + +@@ -780,14 +790,27 @@ h2_recv_data (connection * const con, co + } + chunkqueue * const cq = con->read_queue; + if (NULL == r) { +- /* XXX: TODO: might need to keep a list of recently retired streams +- * for a few seconds so that if we send RST_STREAM, then we ignore +- * further DATA and do not send connection error, though recv windows +- * still must be updated. */ +- if (h2c->h2_cid < id || (!h2c->sent_goaway && 0 != alen)) +- h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR); ++ /* simplistic heuristic to discard additional DATA from recently-closed ++ * streams (or half-closed (local)), where recently-closed here is ++ * within 2-3 seconds of any (other) stream being half-closed (local) ++ * or reset before that (other) stream received END_STREAM from peer. ++ * (e.g. clients might fire off POST request followed by DATA, ++ * and a response might be sent before processing DATA frames) ++ * (id <= h2c->h2_cid) already checked above, else H2_E_PROTOCOL_ERROR ++ * If the above conditions do not hold, then send GOAWAY to attempt to ++ * reduce the chance of becoming an infinite data sink for misbehaving ++ * clients, though remaining streams are still handled before the ++ * connection is closed. */ + chunkqueue_mark_written(cq, 9+len); +- return 0; ++ if (h2c->half_closed_ts + 2 >= log_epoch_secs) { ++ h2_send_window_update(con, 0, len); /*(h2r->h2_rwin)*/ ++ return 1; ++ } ++ else { ++ if (!h2c->sent_goaway && 0 != alen) ++ h2_send_goaway_e(con, H2_E_NO_ERROR); ++ return 0; ++ } + } + + if (r->h2state == H2_STATE_CLOSED +@@ -808,7 +831,7 @@ h2_recv_data (connection * const con, co + } + } + /*(allow h2r->h2_rwin to dip below 0 so that entire frame is processed)*/ +- /*(undeflow will not occur (with reasonable SETTINGS_MAX_FRAME_SIZE used) ++ /*(underflow will not occur (with reasonable SETTINGS_MAX_FRAME_SIZE used) + * since windows updated elsewhere and data is streamed to temp files if + * not FDEVENT_STREAM_REQUEST_BUFMIN)*/ + /*r->h2_rwin -= (int32_t)len;*/ +@@ -2347,16 +2370,25 @@ h2_send_end_stream_data (request_st * co + } }; + + dataframe.u[2] = htonl(r->h2id); +- r->h2state = H2_STATE_CLOSED; + /*(ignore window updates when sending 0-length DATA frame with END_STREAM)*/ + chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align pad)*/ + (const char *)dataframe.c+3, sizeof(dataframe)-3); ++ ++ if (r->h2state != H2_STATE_HALF_CLOSED_REMOTE) { ++ /* set timestamp for comparison; not tracking individual stream ids */ ++ h2con * const h2c = con->h2; ++ h2c->half_closed_ts = log_epoch_secs; ++ /* indicate to peer that no more DATA should be sent from peer */ ++ h2_send_rst_stream_id(r->h2id, con, H2_E_NO_ERROR); ++ } ++ r->h2state = H2_STATE_CLOSED; + } + + + void + h2_send_end_stream (request_st * const r, connection * const con) + { ++ if (r->h2state == H2_STATE_CLOSED) return; + if (r->state != CON_STATE_ERROR && r->resp_body_finished) { + /* CON_STATE_RESPONSE_END */ + if (r->gw_dechunk && r->gw_dechunk->done +--- a/src/h2.h ++++ b/src/h2.h +@@ -92,6 +92,7 @@ struct h2con { + uint32_t s_max_header_list_size; /* SETTINGS_MAX_HEADER_LIST_SIZE */ + struct lshpack_dec decoder; + struct lshpack_enc encoder; ++ time_t half_closed_ts; + }; + + void h2_send_goaway (connection *con, request_h2error_t e);