public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/nghttp2] f43: Resolves: CVE-2026-58055 - HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests
@ 2026-08-07 11:30 Jan Macku
  0 siblings, 0 replies; only message in thread
From: Jan Macku @ 2026-08-07 11:30 UTC (permalink / raw)
  To: git-commits

A new commit has been pushed.

Repo   : rpms/nghttp2
Branch : f43
Commit : 4c4521b0e8e9e9b2bdb6f1c965fd0e5ad2a15f11
Author : Jan Macku <jamacku@redhat.com>
Date   : 2026-08-07T13:13:45+02:00
Stats  : +322/-1 in 2 file(s)
URL    : https://src.fedoraproject.org/rpms/nghttp2/c/4c4521b0e8e9e9b2bdb6f1c965fd0e5ad2a15f11?branch=f43

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.66.0-CVE-2026-58055.patch b/0001-nghttp2-1.66.0-CVE-2026-58055.patch
new file mode 100644
index 0000000..3533cd3
--- /dev/null
+++ b/0001-nghttp2-1.66.0-CVE-2026-58055.patch
@@ -0,0 +1,315 @@
+From 455624dc9f1c5f59305f46c2f04b96f0acb0e4b1 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 9cffcd7c..9f08e831 100644
+--- a/src/shrpx_downstream.cc
++++ b/src/shrpx_downstream.cc
+@@ -1148,7 +1148,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 f87c7f49..42dd42af 100644
+--- a/src/shrpx_downstream.h
++++ b/src/shrpx_downstream.h
+@@ -233,6 +233,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 3c3ca0f5..10fdf10b 100644
+--- a/src/shrpx_http2_upstream.cc
++++ b/src/shrpx_http2_upstream.cc
+@@ -330,6 +330,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_http3_upstream.cc b/src/shrpx_http3_upstream.cc
+index e79fb3fa..c5c7cb9f 100644
+--- a/src/shrpx_http3_upstream.cc
++++ b/src/shrpx_http3_upstream.cc
+@@ -2256,6 +2256,14 @@ int Http3Upstream::http_end_request_headers(Downstream *downstream, int fin) {
+     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();
+ 
+   auto config = get_config();
+diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc
+index fb9f201c..4308d926 100644
+--- a/src/shrpx_http_downstream_connection.cc
++++ b/src/shrpx_http_downstream_connection.cc
+@@ -715,6 +715,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();
+ 
+@@ -744,7 +772,7 @@ int HttpDownstreamConnection::process_blocked_request_buf() {
+ 
+ int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+                                                      size_t datalen) {
+-  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, datalen);
+@@ -776,7 +804,7 @@ int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ }
+ 
+ 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();
+@@ -977,6 +1005,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.
+@@ -998,7 +1031,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);
+   }
+ 
+@@ -1013,13 +1046,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()) {
+@@ -1197,7 +1239,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();
+@@ -1218,8 +1263,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 b9f70c74..2f1a1a1c 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 5de140fb..4847c8e2 100644
+--- a/src/shrpx_https_upstream.cc
++++ b/src/shrpx_https_upstream.cc
+@@ -408,6 +408,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)) {
++      ULOG(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);
+   }
+@@ -552,6 +563,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)) {
++      ULOG(INFO, upstream) << "Request body for Upgrade request is not allowed";
++    }
++
++    return HPE_USER;
++  }
++
+   rv = downstream->push_upload_data_chunk(
+     reinterpret_cast<const uint8_t *>(data), len);
+   if (rv != 0) {
+@@ -585,6 +606,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) {
+@@ -625,7 +647,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->pos(), rb->rleft());
+ 
+     if (rv != 0) {
+@@ -698,9 +721,13 @@ int HttpsUpstream::on_read() {
+ 
+   if (htperr != HPE_OK) {
+     if (LOG_ENABLED(INFO)) {
+-      ULOG(INFO, this) << "HTTP parse failure: "
+-                       << "(" << llhttp_errno_name(htperr) << ") "
+-                       << llhttp_get_error_reason(&htp_);
++      if (htperr == HPE_USER) {
++        ULOG(INFO, this) << "HTTP callback error";
++      } else {
++        ULOG(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 2376e32..c399509 100644
--- a/nghttp2.spec
+++ b/nghttp2.spec
@@ -7,7 +7,7 @@
 Summary: Experimental HTTP/2 client, server and proxy
 Name: nghttp2
 Version: 1.66.0
-Release: 2%{?dist}
+Release: 3%{?dist}
 
 # Parts of ruby bindings are additionally under GPL-2.0-or-later, MIT and
 # HPND-Kevlin-Henney but they are NOT shipped.
@@ -18,6 +18,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.66.0-CVE-2026-58055.patch
+
 BuildRequires: CUnit-devel
 BuildRequires: c-ares-devel
 BuildRequires: gcc-c++
@@ -209,6 +212,9 @@ popd
 
 
 %changelog
+* Fri Aug 07 2026 Jan Macku <jamacku@redhat.com> - 1.66.0-3
+- fix HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests (CVE-2026-58055)
+
 * Thu Jul 24 2025 Fedora Release Engineering <releng@fedoraproject.org> - 1.66.0-2
 - Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild
 

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-08-07 11:30 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-07 11:30 [rpms/nghttp2] f43: 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