public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/nghttp2] rawhide: Resolves: CVE-2026-58055 - HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests
@ 2026-08-07 10:41 Jan Macku
0 siblings, 0 replies; only message in thread
From: Jan Macku @ 2026-08-07 10:41 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/nghttp2
Branch : rawhide
Commit : b664865eb764ccc93a98c31db2c6fdc3b7a3f75b
Author : Jan Macku <jamacku@redhat.com>
Date : 2026-08-07T10:38:57+02:00
Stats : +322/-1 in 2 file(s)
URL : https://src.fedoraproject.org/rpms/nghttp2/c/b664865eb764ccc93a98c31db2c6fdc3b7a3f75b?branch=rawhide
Log:
Resolves: CVE-2026-58055 - HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests
---
diff --git a/0001-nghttp2-1.69.0-CVE-2026-58055.patch b/0001-nghttp2-1.69.0-CVE-2026-58055.patch
new file mode 100644
index 0000000..70b3f4c
--- /dev/null
+++ b/0001-nghttp2-1.69.0-CVE-2026-58055.patch
@@ -0,0 +1,315 @@
+From 1632ea4fb7929e34749821c6b3aec38cc97d14e2 Mon Sep 17 00:00:00 2001
+From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
+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 | 3 +-
+ src/shrpx_downstream.h | 4 ++
+ src/shrpx_http2_upstream.cc | 8 +++
+ src/shrpx_http3_upstream.cc | 8 +++
+ src/shrpx_http_downstream_connection.cc | 70 +++++++++++++++++++++----
+ src/shrpx_http_downstream_connection.h | 5 ++
+ src/shrpx_https_upstream.cc | 35 +++++++++++--
+ 7 files changed, 117 insertions(+), 16 deletions(-)
+
+diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc
+index f0c3809d..465f9b4c 100644
+--- a/src/shrpx_downstream.cc
++++ b/src/shrpx_downstream.cc
+@@ -1146,7 +1146,8 @@ bool Downstream::can_detach_downstream_connection() const {
+ // state, especially for HTTP/1.1
+ return dconn_ && response_state_ == DownstreamState::MSG_COMPLETE &&
+ request_state_ == DownstreamState::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() {
+diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h
+index 96df96c9..5829db5f 100644
+--- a/src/shrpx_downstream.h
++++ b/src/shrpx_downstream.h
+@@ -231,6 +231,10 @@ struct Request {
+ // orig_authority and orig_path have the authority and path which
+ // are used for the first backend selection.
+ bool forwarded_once;
++ // true if HTTP/1 request message has been completed. This field is
++ // added because Downstream::get_request_state() might be altered
++ // from DownstreamState::MSG_COMPLETE.
++ bool http1_msg_complete{};
+ };
+
+ struct Response {
+diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc
+index 563f38ae..90fd3888 100644
+--- a/src/shrpx_http2_upstream.cc
++++ b/src/shrpx_http2_upstream.cc
+@@ -328,6 +328,14 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
+ return 0;
+ }
+
++ if (method_token == HTTP_CONNECT && content_length) {
++ if (log_enabled(INFO)) {
++ Log{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_http3_upstream.cc b/src/shrpx_http3_upstream.cc
+index 48fdb51c..5f4d1306 100644
+--- a/src/shrpx_http3_upstream.cc
++++ b/src/shrpx_http3_upstream.cc
+@@ -2224,6 +2224,14 @@ int Http3Upstream::http_end_request_headers(Downstream *downstream, int fin) {
+ return 0;
+ }
+
++ if (method_token == HTTP_CONNECT && content_length) {
++ if (log_enabled(INFO)) {
++ Log{INFO, this} << "content-length are not allowed in CONNECT request";
++ }
++
++ return error_reply(downstream, 400);
++ }
++
+ auto faddr = handler_->get_upstream_addr();
+
+ auto config = get_config();
+diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc
+index 346287a9..197aaf3e 100644
+--- a/src/shrpx_http_downstream_connection.cc
++++ b/src/shrpx_http_downstream_connection.cc
+@@ -712,6 +712,34 @@ int HttpDownstreamConnection::push_request_headers() {
+ return 0;
+ }
+
++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();
+
+@@ -741,7 +769,7 @@ int HttpDownstreamConnection::process_blocked_request_buf() {
+
+ int HttpDownstreamConnection::push_upload_data_chunk(
+ std::span<const uint8_t> data) {
+- if (!downstream_->get_request_header_sent()) {
++ if (should_block_request_body()) {
+ auto output = downstream_->get_blocked_request_buf();
+ auto &req = downstream_->request();
+ output->append(data);
+@@ -773,7 +801,7 @@ int HttpDownstreamConnection::push_upload_data_chunk(
+ }
+
+ int HttpDownstreamConnection::end_upload_data() {
+- if (!downstream_->get_request_header_sent()) {
++ if (should_block_request_body()) {
+ downstream_->set_blocked_request_data_eof(true);
+ if (request_header_written_) {
+ signal_write();
+@@ -974,6 +1002,11 @@ int htp_hdrs_completecb(llhttp_t *htp) {
+ // upgrade succeeded, 101 response is treated as final in nghttpx.
+ downstream->check_upgrade_fulfilled_http1();
+
++ 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.
+@@ -995,7 +1028,7 @@ int htp_hdrs_completecb(llhttp_t *htp) {
+ downstream->set_response_state(DownstreamState::HEADER_COMPLETE);
+ downstream->inspect_http1_response();
+
+- if (htp->flags & F_CHUNKED) {
++ if (!downstream->get_upgraded() && (htp->flags & F_CHUNKED)) {
+ downstream->set_chunked_response(true);
+ }
+
+@@ -1010,13 +1043,22 @@ int htp_hdrs_completecb(llhttp_t *htp) {
+ resp.connection_close = true;
+ // transfer-encoding not applied to upgraded connection
+ downstream->set_chunked_response(false);
+- } else if (http2::legacy_http1(req.http_major, req.http_minor)) {
+- if (resp.fs.content_length == -1) {
++
++ static_cast<HttpDownstreamConnection *>(dconn)
++ ->process_blocked_request_buf_on_response();
++ } else {
++ if (req.upgrade_request) {
+ resp.connection_close = true;
+ }
+- downstream->set_chunked_response(false);
+- } else if (!downstream->expect_response_body()) {
+- downstream->set_chunked_response(false);
++
++ if (http2::legacy_http1(req.http_major, req.http_minor)) {
++ 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()) {
+@@ -1194,7 +1236,10 @@ int htp_msg_completecb(llhttp_t *htp) {
+ int HttpDownstreamConnection::write_first() {
+ int rv;
+
+- process_blocked_request_buf();
++ auto should_unblock_req_body = should_unblock_request_body_before_response();
++ if (should_unblock_req_body) {
++ process_blocked_request_buf();
++ }
+
+ if (conn_.tls.ssl) {
+ rv = write_tls();
+@@ -1215,8 +1260,11 @@ int HttpDownstreamConnection::write_first() {
+ first_write_done_ = true;
+ downstream_->set_request_header_sent(true);
+
+- auto buf = downstream_->get_blocked_request_buf();
+- buf->reset();
++ if (should_unblock_req_body) {
++ 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
+diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h
+index d3f27432..eb6ba19e 100644
+--- a/src/shrpx_http_downstream_connection.h
++++ b/src/shrpx_http_downstream_connection.h
+@@ -91,6 +91,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_;
+@@ -117,6 +120,8 @@ private:
+ bool reusable_;
+ // true if request header is written to request buffer.
+ bool request_header_written_;
++ // 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 7b06edc1..d183fc09 100644
+--- a/src/shrpx_https_upstream.cc
++++ b/src/shrpx_https_upstream.cc
+@@ -402,6 +402,17 @@ int htp_hdrs_completecb(llhttp_t *htp) {
+
+ downstream->inspect_http1_request();
+
++ if ((req.upgrade_request || llhttp_get_upgrade(htp)) &&
++ (req.fs.header(http2::HD_TRANSFER_ENCODING) ||
++ req.fs.header(http2::HD_CONTENT_LENGTH))) {
++ if (log_enabled(INFO)) {
++ Log{INFO, upstream} << "transfer-encoding and content-length are not "
++ "allowed in CONNECT or upgrade request";
++ }
++
++ return -1;
++ }
++
+ if (htp->flags & F_CHUNKED) {
+ downstream->set_chunked_request(true);
+ }
+@@ -554,6 +565,16 @@ int htp_bodycb(llhttp_t *htp, const char *data, size_t len) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
++ const auto &req = downstream->request();
++
++ if (req.upgrade_request || llhttp_get_upgrade(htp)) {
++ if (log_enabled(INFO)) {
++ Log{INFO, upstream} << "Request body for Upgrade request is not allowed";
++ }
++
++ return HPE_USER;
++ }
++
+ rv = downstream->push_upload_data_chunk(as_uint8_span(std::span{data, len}));
+ if (rv != 0) {
+ // Ignore error if response has been completed. We will end up in
+@@ -586,6 +607,7 @@ int htp_msg_completecb(llhttp_t *htp) {
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
++ req.http1_msg_complete = true;
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+@@ -626,7 +648,8 @@ int HttpsUpstream::on_read() {
+
+ // downstream can be nullptr here, because it is initialized in the
+ // callback chain called by llhttp_execute()
+- if (downstream && downstream->get_upgraded()) {
++ if (downstream && downstream->request().http1_msg_complete &&
++ downstream->get_upgraded()) {
+ auto rv = downstream->push_upload_data_chunk(rb->peek());
+
+ if (rv != 0) {
+@@ -699,9 +722,13 @@ int HttpsUpstream::on_read() {
+
+ if (htperr != HPE_OK) {
+ if (log_enabled(INFO)) {
+- Log{INFO, this} << "HTTP parse failure: "
+- << "(" << llhttp_errno_name(htperr) << ") "
+- << llhttp_get_error_reason(&htp_);
++ if (htperr == HPE_USER) {
++ Log{INFO, this} << "HTTP callback error";
++ } else {
++ Log{INFO, this} << "HTTP parse failure: "
++ << "(" << llhttp_errno_name(htperr) << ") "
++ << llhttp_get_error_reason(&htp_);
++ }
+ }
+
+ if (downstream &&
+--
+2.55.0
+
diff --git a/nghttp2.spec b/nghttp2.spec
index e2ce8a8..7217c4a 100644
--- a/nghttp2.spec
+++ b/nghttp2.spec
@@ -13,7 +13,7 @@
Summary: Experimental HTTP/2 client, server and proxy
Name: nghttp2
Version: 1.69.0
-Release: 4%{?dist}
+Release: 5%{?dist}
# Parts of ruby bindings are additionally under GPL-2.0-or-later, MIT and
# HPND-Kevlin-Henney but they are NOT shipped.
@@ -24,6 +24,9 @@ Source0: https://github.com/tatsuhiro-t/nghttp2/releases/download/v%{version}/ng
Source1: https://github.com/%{name}/%{name}/releases/download/v%{version}/%{name}-%{version}.tar.xz.asc
Source2: tatsuhiro-t.pgp
+# fix HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests (CVE-2026-58055)
+Patch001: 0001-nghttp2-1.69.0-CVE-2026-58055.patch
+
BuildRequires: CUnit-devel
BuildRequires: c-ares-devel
BuildRequires: gcc-c++
@@ -228,6 +231,9 @@ popd
%changelog
+* Fri Aug 07 2026 Jan Macku <jamacku@redhat.com> - 1.69.0-5
+- fix HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests (CVE-2026-58055)
+
* Thu Jul 16 2026 Fedora Release Engineering <releng@fedoraproject.org> - 1.69.0-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-08-07 10:41 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-07 10:41 [rpms/nghttp2] rawhide: Resolves: CVE-2026-58055 - HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests Jan Macku
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox