import UBI nghttp2-1.68.0-3.el10_2.2

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-13 19:15:38 -04:00
parent 0f655ecd72
commit 65a2f44fc5
2 changed files with 322 additions and 1 deletions

View File

@ -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

View File

@ -7,7 +7,7 @@
Summary: Experimental HTTP/2 client, server and proxy
Name: nghttp2
Version: 1.68.0
Release: 3%{?dist}.1
Release: 3%{?dist}.2
# Parts of ruby bindings are additionally under GPL-2.0-or-later, MIT and
# HPND-Kevlin-Henney but they are NOT shipped.
@ -22,6 +22,9 @@ Source2: tatsuhiro-t.pgp
Patch001: 0001-nghttp2-1.68.0-Check-nghttp2_is_fatal-first.patch
Patch002: 0002-nghttp2-1.68.0-Fix-missing-iframe-state-validations-to-avoid-assert.patch
# fix HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests (CVE-2026-58055)
Patch003: 0003-nghttp2-1.68.0-CVE-2026-58055.patch
# Make X25519MLKEM768 the default TLS key exchange group in nghttpd and nghttpx
# https://issues.redhat.com/browse/RHEL-103655
Patch100: 0100-nghttp2-1.64.0-pqc-add-X25519MLKEM768-as-the-default-TLS-key-exchan.patch
@ -217,6 +220,9 @@ popd
%changelog
* Mon Aug 10 2026 Jan Macku <jamacku@redhat.com> 1.68.0-3.2
- fix HTTP Request/Response Smuggling and Response-Queue Poisoning via ambiguous HTTP/1.1 Upgrade requests (CVE-2026-58055)
* Tue Mar 31 2026 Jan Macku <jamacku@redhat.com> 1.68.0-3.1
- fix Denial of service: Assertion failure due to the missing state validation (CVE-2026-27135)