Chrome · Network
CVE-2026-14054
Logic Error in Network
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fnet/http/http_stream_pool_attempt_manager_unittest.cc |
modified |
Files Changed
net/http/http_stream_pool_attempt_manager_unittest.ccnet/http/http_stream_pool_job_controller.ccnet/log/net_log_event_type_list.h
Patch
From f40c463966b285f51660b4c7a2758f27590f49f0 Mon Sep 17 00:00:00 2001
From: Kenichi Ishibashi <bashi@chromium.org>
Date: Wed, 13 May 2026 16:21:40 -0700
Subject: [PATCH] net: Validate restricted ports for Alt-Svc in HttpStreamPool
When HappyEyeballsV3 is enabled, HttpStreamPool::JobController starts
alternative service jobs without validating whether the destination port
advertised by Alt-Svc is restricted. This allows a malicious origin to
bypass cross-protocol attack mitigations by redirecting traffic to
restricted ports (e.g., port 25, 10080).
This CL adds IsPortAllowedForScheme() check to
HttpStreamPool::JobController::MaybeStartAlternativeJob(). If the
alternative service's port is restricted, the connection attempt to the
alternative service is skipped, and the request safely falls back to the
origin connection.
Bug: 501851312
Change-Id: Ie7913f5559372801379d5010529a8974af10c3be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7839160
Commit-Queue: Kenichi Ishibashi <bashi@chromium.org>
Reviewed-by: mmenke <mmenke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1630303}
---
diff --git a/net/http/http_stream_pool_attempt_manager_unittest.cc b/net/http/http_stream_pool_attempt_manager_unittest.cc
index fb464d2a9..6029fba 100644
--- a/net/http/http_stream_pool_attempt_manager_unittest.cc
+++ b/net/http/http_stream_pool_attempt_manager_unittest.cc
@@ -562,6 +562,8 @@
return quic::ParsedQuicVersion::RFCv1();
}
+ RecordingNetLogObserver& net_log_observer() { return net_log_observer_; }
+
base::WeakPtr<SpdySession> CreateFakeSpdySession(
const HttpStreamKey& stream_key,
IPEndPoint peer_addr = IPEndPoint(IPAddress(192, 0, 2, 1), 443)) {
@@ -6846,6 +6848,48 @@
alternative_service, NetworkAnonymizationKey()));
}
+// Tests that if an alternative service destination uses a restricted port,
+// connection attempt to the alternative service is skipped and the request
+// falls back to the origin connection.
+// Regression test for crbug.com/501851312
+TEST_F(HttpStreamPoolAttemptManagerTest, AltSvcH2UnsafePort) {
+ const url::SchemeHostPort kOrigin(url::kHttpsScheme, "origin.example.org",
+ 443);
+ const HostPortPair kAlternative("alt.example.org", 10080);
+
+ const AlternativeService alternative_service(NextProto::kProtoHTTP2,
+ kAlternative);
+ const base::Time expiration = base::Time::Now() + base::Days(1);
+
+ StreamRequester requester;
+ requester.set_destination(kOrigin).set_alternative_service_info(
+ AlternativeServiceInfo::CreateHttp2AlternativeServiceInfo(
+ alternative_service, expiration));
+
+ resolver()
+ ->AddFakeRequest()
+ ->add_endpoint(ServiceEndpointBuilder().add_v4("192.0.2.1").endpoint())
+ .CompleteStartSynchronously(OK);
+
+ // For the origin. The connection is refused.
+ StaticSocketDataProvider origin_data;
+ origin_data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
+ socket_factory()->AddSocketDataProvider(&origin_data);
+
+ requester.RequestStream(pool());
+ requester.WaitForResult();
+
+ // The alternative job is not started because of the unsafe port,
+ // and the origin job fails with ERR_CONNECTION_REFUSED.
+ EXPECT_THAT(requester.result(), Optional(IsError(ERR_CONNECTION_REFUSED)));
+
+ auto entries = net_log_observer().GetEntriesWithType(
+ NetLogEventType::
+ HTTP_STREAM_POOL_JOB_CONTROLLER_SKIPPED_ALTSVC_RESTRICTED_PORT);
+ ASSERT_EQ(entries.size(), 1u);
+ EXPECT_THAT(entries[0].params.FindInt("port"), Optional(10080));
+}
+
TEST_F(HttpStreamPoolAttemptManagerTest, AltSvcFailOriginOk) {
const url::SchemeHostPort kOrigin(url::kHttpsScheme, "origin.example.org",
443);
diff --git a/net/http/http_stream_pool_job_controller.cc b/net/http/http_stream_pool_job_controller.cc
index e086b37..d827275 100644
--- a/net/http/http_stream_pool_job_controller.cc
+++ b/net/http/http_stream_pool_job_controller.cc
@@ -613,6 +613,21 @@
return false;
}
+ CHECK(alternative_->stream_key.alt_service().has_value());
+ if (!IsPortAllowedForScheme(
+ alternative_->stream_key.alt_service()->port,
+ alternative_->stream_key.destination().scheme())) {
+ net_log_.AddEvent(
+ NetLogEventType::
+ HTTP_STREAM_POOL_JOB_CONTROLLER_SKIPPED_ALTSVC_RESTRICTED_PORT,
+ [&] {
+ base::DictValue dict;
+ dict.Set("port", alternative_->stream_key.alt_service()->port);
+ return dict;
+ });
+ return false;
+ }
+
Group& alternative_group = pool_->GetOrCreateGroup(alternative_->stream_key);
// We never put streams that are negotiated to use HTTP/2 as idle streams.
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index 5d59f4a..f0b042b 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -1613,6 +1613,14 @@
// }
EVENT_TYPE(HTTP_STREAM_POOL_JOB_CONTROLLER_ALIVE)
+// Emitted when an HttpStreamPool::JobController skips an alternative service
+// because its port is not allowed for the scheme.
+// The event parameters are:
+// {
+// "port": <The port of the alternative service>,
+// }
+EVENT_TYPE(HTTP_STREAM_POOL_JOB_CONTROLLER_SKIPPED_ALTSVC_RESTRICTED_PORT)
+
// Emitted when an HttpStreamPool::JobController found an existing SPDY session.
EVENT_TYPE(HTTP_STREAM_POOL_JOB_CONTROLLER_FOUND_EXISTING_SPDY_SESSION)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/net/http/http_stream_pool_attempt_manager_unittest.cc b/net/http/http_stream_pool_attempt_manager_unittest.cc
index fb464d2a9..6029fba 100644
--- a/net/http/http_stream_pool_attempt_manager_unittest.cc
+++ b/net/http/http_stream_pool_attempt_manager_unittest.cc
@@ -562,6 +562,8 @@
return quic::ParsedQuicVersion::RFCv1();
}
+ RecordingNetLogObserver& net_log_observer() { return net_log_observer_; }
+
base::WeakPtr<SpdySession> CreateFakeSpdySession(
const HttpStreamKey& stream_key,
IPEndPoint peer_addr = IPEndPoint(IPAddress(192, 0, 2, 1), 443)) {
@@ -6846,6 +6848,48 @@
alternative_service, NetworkAnonymizationKey()));
}
+// Tests that if an alternative service destination uses a restricted port,
+// connection attempt to the alternative service is skipped and the request
+// falls back to the origin connection.
+// Regression test for crbug.com/501851312
+TEST_F(HttpStreamPoolAttemptManagerTest, AltSvcH2UnsafePort) {
+ const url::SchemeHostPort kOrigin(url::kHttpsScheme, "origin.example.org",
+ 443);
+ const HostPortPair kAlternative("alt.example.org", 10080);
+
+ const AlternativeService alternative_service(NextProto::kProtoHTTP2,
+ kAlternative);
+ const base::Time expiration = base::Time::Now() + base::Days(1);
+
+ StreamRequester requester;
+ requester.set_destination(kOrigin).set_alternative_service_info(
+ AlternativeServiceInfo::CreateHttp2AlternativeServiceInfo(
+ alternative_service, expiration));
+
+ resolver()
+ ->AddFakeRequest()
+ ->add_endpoint(ServiceEndpointBuilder().add_v4("192.0.2.1").endpoint())
+ .CompleteStartSynchronously(OK);
+
+ // For the origin. The connection is refused.
+ StaticSocketDataProvider origin_data;
+ origin_data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
+ socket_factory()->AddSocketDataProvider(&origin_data);
+
+ requester.RequestStream(pool());
+ requester.WaitForResult();
+
+ // The alternative job is not started because of the unsafe port,
+ // and the origin job fails with ERR_CONNECTION_REFUSED.
+ EXPECT_THAT(requester.result(), Optional(IsError(ERR_CONNECTION_REFUSED)));
+
+ auto entries = net_log_observer().GetEntriesWithType(
+ NetLogEventType::
+ HTTP_STREAM_POOL_JOB_CONTROLLER_SKIPPED_ALTSVC_RESTRICTED_PORT);
+ ASSERT_EQ(entries.size(), 1u);
+ EXPECT_THAT(entries[0].params.FindInt("port"), Optional(10080));
+}
+
TEST_F(HttpStreamPoolAttemptManagerTest, AltSvcFailOriginOk) {
const url::SchemeHostPort kOrigin(url::kHttpsScheme, "origin.example.org",
443);
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page