From 977525a3ebc89ab6aef625d0c2403fae4772446c Mon Sep 17 00:00:00 2001 From: Jan Macku Date: Fri, 7 Aug 2026 12:56:11 +0200 Subject: [PATCH] CVE-2026-58055 - HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests Resolves: RHEL-219140 --- 0006-nghttp2-1.33.0-CVE-2026-58055.patch | 321 +++++++++++++++++++++++ nghttp2.spec | 9 +- 2 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 0006-nghttp2-1.33.0-CVE-2026-58055.patch diff --git a/0006-nghttp2-1.33.0-CVE-2026-58055.patch b/0006-nghttp2-1.33.0-CVE-2026-58055.patch new file mode 100644 index 0000000..91f3e8c --- /dev/null +++ b/0006-nghttp2-1.33.0-CVE-2026-58055.patch @@ -0,0 +1,321 @@ +From eda33c2454fc05f43394aa7624b6b19b6f712f03 Mon Sep 17 00:00:00 2001 +From: Tatsuhiro Tsujikawa +Date: Fri, 22 May 2026 21:26:44 +0900 +Subject: [PATCH] nghttpx: Tighten up CONNECT and HTTP Upgrade handling + +(cherry picked from commit ab28105c4a0197da24f8bfc414bc116055249e1e) +--- + src/shrpx_downstream.cc | 7 +- + src/shrpx_downstream.h | 8 ++- + src/shrpx_http2_upstream.cc | 8 +++ + src/shrpx_http_downstream_connection.cc | 85 ++++++++++++++++++++++++- + src/shrpx_http_downstream_connection.h | 5 ++ + src/shrpx_https_upstream.cc | 26 +++++++- + 6 files changed, 133 insertions(+), 6 deletions(-) + +diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc +index b561d2ff..e6cff150 100644 +--- a/src/shrpx_downstream.cc ++++ b/src/shrpx_downstream.cc +@@ -1058,7 +1058,8 @@ bool Downstream::can_detach_downstream_connection() const { + // state, especially for HTTP/1.1 + return dconn_ && response_state_ == Downstream::MSG_COMPLETE && + request_state_ == Downstream::MSG_COMPLETE && !upgraded_ && +- !resp_.connection_close && request_buf_.rleft() == 0; ++ !resp_.connection_close && blocked_request_buf_.rleft() == 0 && ++ request_buf_.rleft() == 0; + } + + DefaultMemchunks Downstream::pop_response_buf() { +@@ -1109,6 +1110,10 @@ bool Downstream::get_blocked_request_data_eof() const { + return blocked_request_data_eof_; + } + ++void Downstream::set_blocked_request_data_eof(bool f) { ++ blocked_request_data_eof_ = f; ++} ++ + bool Downstream::get_expect_100_continue() const { + return expect_100_continue_; + } +diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h +index 1b12ebcd..02e63882 100644 +--- a/src/shrpx_downstream.h ++++ b/src/shrpx_downstream.h +@@ -146,7 +146,8 @@ struct Request { + http2_upgrade_seen(false), + connection_close(false), + http2_expect_body(false), +- no_authority(false) {} ++ no_authority(false), ++ http1_msg_complete(false) {} + + void consume(size_t len) { + assert(unconsumed_body_length >= len); +@@ -190,6 +191,10 @@ struct Request { + // This happens when: For HTTP/2 request, :authority is missing. + // For HTTP/1 request, origin or asterisk form is used. + bool no_authority; ++ // true if HTTP/1 request message has been completed. This field is ++ // added because Downstream::get_request_state() might be altered ++ // from Downstream::MSG_COMPLETE. ++ bool http1_msg_complete; + }; + + struct Response { +@@ -358,6 +363,7 @@ public: + + DefaultMemchunks *get_blocked_request_buf(); + bool get_blocked_request_data_eof() const; ++ void set_blocked_request_data_eof(bool f); + + // downstream response API + const Response &response() const { return resp_; } +diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc +index fdf988cc..4a11775e 100644 +--- a/src/shrpx_http2_upstream.cc ++++ b/src/shrpx_http2_upstream.cc +@@ -355,6 +355,14 @@ int Http2Upstream::on_request_headers(Downstream *downstream, + return 0; + } + ++ if (method_token == HTTP_CONNECT && content_length) { ++ if (LOG_ENABLED(INFO)) { ++ ULOG(INFO, this) << "content-length are not allowed in CONNECT request"; ++ } ++ ++ return error_reply(downstream, 400); ++ } ++ + auto faddr = handler_->get_upstream_addr(); + + // For HTTP/2 proxy, we require :authority. +diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc +index 47366b31..5deab304 100644 +--- a/src/shrpx_http_downstream_connection.cc ++++ b/src/shrpx_http_downstream_connection.cc +@@ -206,7 +206,8 @@ HttpDownstreamConnection::HttpDownstreamConnection( + response_htp_{0}, + initial_addr_idx_(initial_addr_idx), + reuse_first_write_done_(true), +- reusable_(true) {} ++ reusable_(true), ++ blocked_request_buf_processed_(false) {} + + HttpDownstreamConnection::~HttpDownstreamConnection() { + if (LOG_ENABLED(INFO)) { +@@ -706,6 +707,34 @@ int HttpDownstreamConnection::push_request_headers() { + return process_blocked_request_buf(); + } + ++bool HttpDownstreamConnection::should_block_request_body() const { ++ const auto &req = downstream_->request(); ++ ++ return !downstream_->get_request_header_sent() || ++ (req.upgrade_request && !downstream_->get_upgraded()); ++} ++ ++bool HttpDownstreamConnection::should_unblock_request_body_before_response() ++ const { ++ const auto &req = downstream_->request(); ++ ++ return !req.upgrade_request; ++} ++ ++void HttpDownstreamConnection::process_blocked_request_buf_on_response() { ++ if (blocked_request_buf_processed_) { ++ return; ++ } ++ ++ process_blocked_request_buf(); ++ ++ auto buf = downstream_->get_blocked_request_buf(); ++ buf->reset(); ++ blocked_request_buf_processed_ = true; ++ ++ signal_write(); ++} ++ + int HttpDownstreamConnection::process_blocked_request_buf() { + auto src = downstream_->get_blocked_request_buf(); + +@@ -734,6 +763,15 @@ int HttpDownstreamConnection::process_blocked_request_buf() { + + int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data, + size_t datalen) { ++ if (should_block_request_body()) { ++ auto output = downstream_->get_blocked_request_buf(); ++ auto &req = downstream_->request(); ++ output->append(data, datalen); ++ req.unconsumed_body_length += datalen; ++ signal_write(); ++ return 0; ++ } ++ + auto chunked = downstream_->get_chunked_request(); + auto output = downstream_->get_request_buf(); + +@@ -755,6 +793,12 @@ int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data, + } + + int HttpDownstreamConnection::end_upload_data() { ++ if (should_block_request_body()) { ++ downstream_->set_blocked_request_data_eof(true); ++ signal_write(); ++ return 0; ++ } ++ + signal_write(); + + if (!downstream_->get_chunked_request()) { +@@ -946,6 +990,11 @@ int htp_hdrs_completecb(http_parser *htp) { + // upgrade succeeded, 101 response is treated as final in nghttpx. + downstream->check_upgrade_fulfilled(); + ++ if (req.method == HTTP_CONNECT && resp.http_status / 100 == 2 && ++ !downstream->get_upgraded()) { ++ resp.http_status = 502; ++ } ++ + if (downstream->get_non_final_response()) { + // Reset content-length because we reuse same Downstream for the + // next response. +@@ -966,14 +1015,38 @@ int htp_hdrs_completecb(http_parser *htp) { + resp.connection_close = !http_should_keep_alive(htp); + downstream->set_response_state(Downstream::HEADER_COMPLETE); + downstream->inspect_http1_response(); ++ ++ if (!downstream->get_upgraded() && (htp->flags & F_CHUNKED)) { ++ downstream->set_chunked_response(true); ++ } ++ ++ auto transfer_encoding = resp.fs.header(http2::HD_TRANSFER_ENCODING); ++ if (transfer_encoding && !downstream->get_chunked_response()) { ++ resp.connection_close = true; ++ } ++ + if (downstream->get_upgraded()) { + // content-length must be ignored for upgraded connection. + resp.fs.content_length = -1; + resp.connection_close = true; + // transfer-encoding not applied to upgraded connection + downstream->set_chunked_response(false); +- } else if (!downstream->expect_response_body()) { +- downstream->set_chunked_response(false); ++ ++ static_cast(dconn) ++ ->process_blocked_request_buf_on_response(); ++ } else { ++ if (req.upgrade_request) { ++ resp.connection_close = true; ++ } ++ ++ if (req.http_major <= 0 || (req.http_major == 1 && req.http_minor == 0)) { ++ if (resp.fs.content_length == -1) { ++ resp.connection_close = true; ++ } ++ downstream->set_chunked_response(false); ++ } else if (!downstream->expect_response_body()) { ++ downstream->set_chunked_response(false); ++ } + } + + if (loggingconf.access.write_early && downstream->accesslog_ready()) { +@@ -1173,6 +1246,12 @@ int HttpDownstreamConnection::write_reuse_first() { + + reuse_first_write_done_ = true; + ++ if (should_unblock_request_body_before_response()) { ++ auto buf = downstream_->get_blocked_request_buf(); ++ buf->reset(); ++ blocked_request_buf_processed_ = true; ++ } ++ + // upstream->resume_read() might be called in + // write_tls()/write_clear(), but before blocked_request_buf_ is + // reset. So upstream read might still be blocked. Let's do it +diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h +index 554e9b94..5bb16fe4 100644 +--- a/src/shrpx_http_downstream_connection.h ++++ b/src/shrpx_http_downstream_connection.h +@@ -90,6 +90,9 @@ public: + int noop(); + + int process_blocked_request_buf(); ++ void process_blocked_request_buf_on_response(); ++ bool should_unblock_request_body_before_response() const; ++ bool should_block_request_body() const; + + private: + Connection conn_; +@@ -118,6 +121,8 @@ private: + bool reuse_first_write_done_; + // true if this object can be reused + bool reusable_; ++ // true if blocked request buffer has been processed. ++ bool blocked_request_buf_processed_; + }; + + } // namespace shrpx +diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc +index b2523b80..afd3c198 100644 +--- a/src/shrpx_https_upstream.cc ++++ b/src/shrpx_https_upstream.cc +@@ -360,6 +360,17 @@ int htp_hdrs_completecb(http_parser *htp) { + + downstream->inspect_http1_request(); + ++ if ((req.upgrade_request || htp->upgrade) && ++ (req.fs.header(http2::HD_TRANSFER_ENCODING) || ++ req.fs.header(http2::HD_CONTENT_LENGTH))) { ++ if (LOG_ENABLED(INFO)) { ++ ULOG(INFO, upstream) << "transfer-encoding and content-length are not " ++ "allowed in CONNECT or upgrade request"; ++ } ++ ++ return -1; ++ } ++ + auto faddr = handler->get_upstream_addr(); + auto &balloc = downstream->get_block_allocator(); + auto config = get_config(); +@@ -484,6 +495,16 @@ int htp_bodycb(http_parser *htp, const char *data, size_t len) { + int rv; + auto upstream = static_cast(htp->data); + auto downstream = upstream->get_downstream(); ++ const auto &req = downstream->request(); ++ ++ if (req.upgrade_request || htp->upgrade) { ++ if (LOG_ENABLED(INFO)) { ++ ULOG(INFO, upstream) << "Request body for Upgrade request is not allowed"; ++ } ++ ++ return -1; ++ } ++ + rv = downstream->push_upload_data_chunk( + reinterpret_cast(data), len); + if (rv != 0) { +@@ -508,7 +529,9 @@ int htp_msg_completecb(http_parser *htp) { + } + auto handler = upstream->get_client_handler(); + auto downstream = upstream->get_downstream(); ++ auto &req = downstream->request(); + downstream->set_request_state(Downstream::MSG_COMPLETE); ++ req.http1_msg_complete = true; + rv = downstream->end_upload_data(); + if (rv != 0) { + if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { +@@ -565,7 +588,8 @@ int HttpsUpstream::on_read() { + + // downstream can be nullptr here, because it is initialized in the + // callback chain called by http_parser_execute() +- if (downstream && downstream->get_upgraded()) { ++ if (downstream && downstream->request().http1_msg_complete && ++ downstream->get_upgraded()) { + + auto rv = downstream->push_upload_data_chunk(rb->pos(), rb->rleft()); + +-- +2.55.0 + diff --git a/nghttp2.spec b/nghttp2.spec index a8721d1..3faed69 100644 --- a/nghttp2.spec +++ b/nghttp2.spec @@ -1,7 +1,7 @@ Summary: Experimental HTTP/2 client, server and proxy Name: nghttp2 Version: 1.33.0 -Release: 6%{?dist}.2 +Release: 6%{?dist}.3 License: MIT Group: Applications/Internet URL: https://nghttp2.org/ @@ -22,6 +22,9 @@ Patch4: 0004-nghttp2-1.33.0-CVE-2024-28182.patch # fix Denial of service: Assertion failure due to the missing state validation (CVE-2026-27135) Patch5: 0005-nghttp2-1.33.0-CVE-2026-27135.patch +# fix HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests (CVE-2026-58055) +Patch6: 0006-nghttp2-1.33.0-CVE-2026-58055.patch + BuildRequires: automake BuildRequires: libtool @@ -68,6 +71,7 @@ for building applications with libnghttp2. %patch3 -p1 %patch4 -p1 %patch5 -p1 +%patch6 -p1 autoreconf -fiv # make fetch-ocsp-response use Python 3 @@ -139,6 +143,9 @@ make %{?_smp_mflags} check %changelog +* Wed Aug 12 2026 Jan Macku - 1.33.0-6.3 +- fix HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests (CVE-2026-58055) + * Thu Apr 09 2026 Jan Macku - 1.33.0-6.2 - fix Denial of service: Assertion failure due to the missing state validation (CVE-2026-27135)