CVE-2026-79050
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
fornet/base/port_util_unittest.cc |
modified | |
TESTnet/base/port_util_unittest.cc |
modified | |
fornet/socket/tcp_client_socket_unittest.cc |
modified | |
TestSocketPerformanceWatchernet/socket/tcp_client_socket_unittest.cc |
modified |
Files Changed
net/base/port_util.ccnet/base/port_util_unittest.ccnet/socket/tcp_client_socket_unittest.cc
Patch
From 085f44318aea6217b5130ad12e8332e1be10599e Mon Sep 17 00:00:00 2001 From: Adam Rice <[email protected]> Date: Wed, 08 Jul 2026 08:38:42 -0700 Subject: [PATCH] Restrict abuse ports on 0.0.0.0 and :: as they are localhost aliases. On POSIX-like OSs, 0.0.0.0 and :: can be used as aliases for localhost, but the implementation of the kRestrictAbusePortsOnLocalhost feature didn't check for that. This CL modifies IsPortAllowedForIpEndpoint to also restrict ports on zero addresses. It also updates tests in net/socket/udp_socket_unittest.cc, net/socket/tcp_client_socket_unittest.cc, and net/base/port_util_unittest.cc to verify that the ports are blocked for all of 127.0.0.0/8, ::1, 0.0.0.0, and ::. Implemented by Gemini. Bug: 517364411 Change-Id: I1064c920a7cb98c262c77ae9b03febafa5193829 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7967099 Reviewed-by: Yoichi Osato <[email protected]> Commit-Queue: Adam Rice <[email protected]> Cr-Commit-Position: refs/heads/main@{#1658792} --- diff --git a/net/base/port_util.cc b/net/base/port_util.cc index 49d0f744..41cbfcaa 100644 --- a/net/base/port_util.cc +++ b/net/base/port_util.cc @@ -19,6 +19,7 @@ #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "net/base/features.h" +#include "net/base/ip_address.h" #include "net/base/ip_endpoint.h" #include "net/base/parse_number.h" #include "url/url_constants.h" @@ -154,6 +155,17 @@ constinit bool g_need_to_reset_restrict_localhost_ports = false; +// Returns true if connecting to `address` may reach a service on the local +// host. In addition to the loopback ranges this includes the unspecified +// address, which many operating systems route to loopback, and IPv4-mapped +// IPv6 forms of those addresses. +bool MayConnectToLocalhost(const IPAddress& address) { + const IPAddress unmapped = address.IsIPv4MappedIPv6() + ? ConvertIPv4MappedIPv6ToIPv4(address) + : address; + return unmapped.IsLoopback() || unmapped.IsZero(); +} + } // namespace bool IsPortValid(int port) { @@ -206,7 +218,10 @@ } // This function currently restricts only on localhost. - if (!endpoint.address().IsLoopback()) { + // 0.0.0.0 and :: are treated as localhost aliases on some platforms. + // On other platforms, connections to those addresses fail, so applying + // the same restriction is harmless. + if (!MayConnectToLocalhost(endpoint.address())) { return true; } diff --git a/net/base/port_util_unittest.cc b/net/base/port_util_unittest.cc index 0523bd9..2a04b07 100644 --- a/net/base/port_util_unittest.cc +++ b/net/base/port_util_unittest.cc @@ -57,22 +57,40 @@ ReloadLocalhostRestrictedPortsForTesting(); IPAddress public_address(8, 8, 8, 8); EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 12345))); + EXPECT_TRUE(IsPortAllowedForIpEndpoint( + IPEndPoint(ConvertIPv4ToIPv4MappedIPv6(public_address), 12345))); EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 443))); EXPECT_TRUE( IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv4Localhost(), 443))); EXPECT_TRUE( IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv6Localhost(), 443))); histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 0); + + const IPAddress localhost_addresses[] = { + IPAddress::IPv4Localhost(), + IPAddress(127, 0, 0, 2), + IPAddress::IPv4AllZeros(), + IPAddress::IPv6Localhost(), + IPAddress::IPv6AllZeros(), + ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4Localhost()), + ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4AllZeros()), + }; + + int expected_count = 0; for (int port : {12345, 23456, 34567}) { - EXPECT_FALSE(IsPortAllowedForIpEndpoint( - IPEndPoint(IPAddress::IPv4Localhost(), port))); - EXPECT_FALSE(IsPortAllowedForIpEndpoint( - IPEndPoint(IPAddress::IPv6Localhost(), port))); + for (const auto& address : localhost_addresses) { + EXPECT_FALSE(IsPortAllowedForIpEndpoint(IPEndPoint(address, port))); + expected_count++; + } } - histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 6); - histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 12345, 2); - histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 23456, 2); - histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 34567, 2); + histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", + expected_count); + histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 12345, + std::size(localhost_addresses)); + histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 23456, + std::size(localhost_addresses)); + histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 34567, + std::size(localhost_addresses)); } TEST(NetUtilTest, RestrictedAbusePortsLocalhostTestNoParamSet) { @@ -82,17 +100,29 @@ ReloadLocalhostRestrictedPortsForTesting(); IPAddress public_address(8, 8, 8, 8); EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 12345))); + EXPECT_TRUE(IsPortAllowedForIpEndpoint( + IPEndPoint(ConvertIPv4ToIPv4MappedIPv6(public_address), 12345))); EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 443))); EXPECT_TRUE( IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv4Localhost(), 443))); EXPECT_TRUE( IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv6Localhost(), 443))); histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 0); + + const IPAddress localhost_addresses[] = { + IPAddress::IPv4Localhost(), + IPAddress(127, 0, 0, 2), + IPAddress::IPv4AllZeros(), + IPAddress::IPv6Localhost(), + IPAddress::IPv6AllZeros(), + ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4Localhost()), + ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4AllZeros()), + }; + for (int port : {12345, 23456, 34567}) { - EXPECT_TRUE(IsPortAllowedForIpEndpoint( - IPEndPoint(IPAddress::IPv4Localhost(), port))); - EXPECT_TRUE(IsPortAllowedForIpEndpoint( - IPEndPoint(IPAddress::IPv6Localhost(), port))); + for (const auto& address : localhost_addresses) { + EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(address, port))); + } } histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 0); } diff --git a/net/socket/tcp_client_socket_unittest.cc b/net/socket/tcp_client_socket_unittest.cc index 7ce8d38..493a666c 100644 --- a/net/socket/tcp_client_socket_unittest.cc +++ b/net/socket/tcp_client_socket_unittest.cc @@ -339,16 +339,33 @@ {{"localhost_restrict_ports", base::NumberToString(server_address.port())}}); ReloadLocalhostRestrictedPortsForTesting(); - TCPClientSocket socket(AddressList(server_address), nullptr, nullptr, nullptr, - NetLogSource(), handles::kInvalidNetworkHandle); + const IPAddress addresses_to_test[] = { + IPAddress::IPv4Localhost(), + IPAddress(127, 0, 0, 2), + IPAddress::IPv4AllZeros(), + IPAddress::IPv6Localhost(), + IPAddress::IPv6AllZeros(), + ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4Localhost()), + ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4AllZeros()), + }; - TestCompletionCallback connect_callback; - int connect_result = socket.Connect(connect_callback.callback()); - EXPECT_THAT(connect_callback.GetResult(connect_result), - IsError(ERR_UNSAFE_PORT)); - histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 1); + int expected_count = 0; + for (const auto& address : addresses_to_test) { + TCPClientSocket socket( + AddressList(IPEndPoint(address, server_address.port())), nullptr, + nullptr, nullptr, NetLogSource(), handles::kInvalidNetworkHandle); + + TestCompletionCallback connect_callback; + int connect_result = socket.Connect(connect_callback.callback()); + EXPECT_THAT(connect_callback.GetResult(connect_result), + IsError(ERR_UNSAFE_PORT)); + expected_count++; + } + + histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", + expected_count); histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", - server_address.port(), 1); + server_address.port(), expected_count); } class TestSocketPerformanceWatcher : public SocketPerformanceWatcher {
Regression Test / PoC
diff --git a/net/base/port_util_unittest.cc b/net/base/port_util_unittest.cc
index 0523bd9..2a04b07 100644
--- a/net/base/port_util_unittest.cc
+++ b/net/base/port_util_unittest.cc
@@ -57,22 +57,40 @@
ReloadLocalhostRestrictedPortsForTesting();
IPAddress public_address(8, 8, 8, 8);
EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 12345)));
+ EXPECT_TRUE(IsPortAllowedForIpEndpoint(
+ IPEndPoint(ConvertIPv4ToIPv4MappedIPv6(public_address), 12345)));
EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 443)));
EXPECT_TRUE(
IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv4Localhost(), 443)));
EXPECT_TRUE(
IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv6Localhost(), 443)));
histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 0);
+
+ const IPAddress localhost_addresses[] = {
+ IPAddress::IPv4Localhost(),
+ IPAddress(127, 0, 0, 2),
+ IPAddress::IPv4AllZeros(),
+ IPAddress::IPv6Localhost(),
+ IPAddress::IPv6AllZeros(),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4Localhost()),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4AllZeros()),
+ };
+
+ int expected_count = 0;
for (int port : {12345, 23456, 34567}) {
- EXPECT_FALSE(IsPortAllowedForIpEndpoint(
- IPEndPoint(IPAddress::IPv4Localhost(), port)));
- EXPECT_FALSE(IsPortAllowedForIpEndpoint(
- IPEndPoint(IPAddress::IPv6Localhost(), port)));
+ for (const auto& address : localhost_addresses) {
+ EXPECT_FALSE(IsPortAllowedForIpEndpoint(IPEndPoint(address, port)));
+ expected_count++;
+ }
}
- histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 6);
- histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 12345, 2);
- histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 23456, 2);
- histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 34567, 2);
+ histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts",
+ expected_count);
+ histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 12345,
+ std::size(localhost_addresses));
+ histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 23456,
+ std::size(localhost_addresses));
+ histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts", 34567,
+ std::size(localhost_addresses));
}
TEST(NetUtilTest, RestrictedAbusePortsLocalhostTestNoParamSet) {
@@ -82,17 +100,29 @@
ReloadLocalhostRestrictedPortsForTesting();
IPAddress public_address(8, 8, 8, 8);
EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 12345)));
+ EXPECT_TRUE(IsPortAllowedForIpEndpoint(
+ IPEndPoint(ConvertIPv4ToIPv4MappedIPv6(public_address), 12345)));
EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(public_address, 443)));
EXPECT_TRUE(
IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv4Localhost(), 443)));
EXPECT_TRUE(
IsPortAllowedForIpEndpoint(IPEndPoint(IPAddress::IPv6Localhost(), 443)));
histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 0);
+
+ const IPAddress localhost_addresses[] = {
+ IPAddress::IPv4Localhost(),
+ IPAddress(127, 0, 0, 2),
+ IPAddress::IPv4AllZeros(),
+ IPAddress::IPv6Localhost(),
+ IPAddress::IPv6AllZeros(),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4Localhost()),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4AllZeros()),
+ };
+
for (int port : {12345, 23456, 34567}) {
- EXPECT_TRUE(IsPortAllowedForIpEndpoint(
- IPEndPoint(IPAddress::IPv4Localhost(), port)));
- EXPECT_TRUE(IsPortAllowedForIpEndpoint(
- IPEndPoint(IPAddress::IPv6Localhost(), port)));
+ for (const auto& address : localhost_addresses) {
+ EXPECT_TRUE(IsPortAllowedForIpEndpoint(IPEndPoint(address, port)));
+ }
}
histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 0);
}
diff --git a/net/socket/tcp_client_socket_unittest.cc b/net/socket/tcp_client_socket_unittest.cc
index 7ce8d38..493a666c 100644
--- a/net/socket/tcp_client_socket_unittest.cc
+++ b/net/socket/tcp_client_socket_unittest.cc
@@ -339,16 +339,33 @@
{{"localhost_restrict_ports",
base::NumberToString(server_address.port())}});
ReloadLocalhostRestrictedPortsForTesting();
- TCPClientSocket socket(AddressList(server_address), nullptr, nullptr, nullptr,
- NetLogSource(), handles::kInvalidNetworkHandle);
+ const IPAddress addresses_to_test[] = {
+ IPAddress::IPv4Localhost(),
+ IPAddress(127, 0, 0, 2),
+ IPAddress::IPv4AllZeros(),
+ IPAddress::IPv6Localhost(),
+ IPAddress::IPv6AllZeros(),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4Localhost()),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4AllZeros()),
+ };
- TestCompletionCallback connect_callback;
- int connect_result = socket.Connect(connect_callback.callback());
- EXPECT_THAT(connect_callback.GetResult(connect_result),
- IsError(ERR_UNSAFE_PORT));
- histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 1);
+ int expected_count = 0;
+ for (const auto& address : addresses_to_test) {
+ TCPClientSocket socket(
+ AddressList(IPEndPoint(address, server_address.port())), nullptr,
+ nullptr, nullptr, NetLogSource(), handles::kInvalidNetworkHandle);
+
+ TestCompletionCallback connect_callback;
+ int connect_result = socket.Connect(connect_callback.callback());
+ EXPECT_THAT(connect_callback.GetResult(connect_result),
+ IsError(ERR_UNSAFE_PORT));
+ expected_count++;
+ }
+
+ histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts",
+ expected_count);
histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts",
- server_address.port(), 1);
+ server_address.port(), expected_count);
}
class TestSocketPerformanceWatcher : public SocketPerformanceWatcher {
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index b4d177c2..be44ded 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -400,14 +400,30 @@
{{"localhost_restrict_ports",
base::NumberToString(server_address.port())}});
ReloadLocalhostRestrictedPortsForTesting();
- // Setup the client.
- auto client = std::make_unique<UDPClientSocket>(
- DatagramSocket::DEFAULT_BIND, NetLog::Get(), NetLogSource(),
- handles::kInvalidNetworkHandle);
- EXPECT_THAT(client->Connect(server_address), IsError(ERR_UNSAFE_PORT));
- histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts", 1);
+ const IPAddress addresses_to_test[] = {
+ IPAddress::IPv4Localhost(),
+ IPAddress(127, 0, 0, 2),
+ IPAddress::IPv4AllZeros(),
+ IPAddress::IPv6Localhost(),
+ IPAddress::IPv6AllZeros(),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4Localhost()),
+ ConvertIPv4ToIPv4MappedIPv6(IPAddress::IPv4AllZeros()),
+ };
+
+ int expected_count = 0;
+ for (const auto& address : addresses_to_test) {
+ auto client = std::make_unique<UDPClientSocket>(
+ DatagramSocket::DEFAULT_BIND, NetLog::Get(), NetLogSource(),
+ handles::kInvalidNetworkHandle);
+ EXPECT_THAT(client->Connect(IPEndPoint(address, server_address.port())),
+ IsError(ERR_UNSAFE_PORT));
+ expected_count++;
+ }
+
+ histogram_tester.ExpectTotalCount("Net.RestrictedLocalhostPorts",
+ expected_count);
histogram_tester.ExpectBucketCount("Net.RestrictedLocalhostPorts",
- server_address.port(), 1);
+ server_address.port(), expected_count);
}
TEST_F(UDPSocketTest, ConnectUsingNetworkRestrictedPort) {
Original Bug Report
Bypass of localhost port restrictions via unspecified addresses (0.0.0.0 and ::)
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The localhost port restriction mechanism designed to block connections to unsafe local services on POSIX platforms can be bypassed using unspecified IP addresses (0.0.0.0 and ::). Because Chromium’s port authorization checks rely on IPAddress::IsLoopback(), which does not recognize unspecified IP addresses, the blocklist is not applied. On POSIX operating systems, the kernel routes connections to unspecified addresses directly to the loopback interface, allowing access to restricted localhost ports.
Affected files:
net/base/port_util.ccnet/base/ip_address.cc
Estimated timestamp from git blame: 2025-05-09
Summary
The kRestrictAbusePortsOnLocalhost feature is a security mitigation designed to prevent network requests to restricted ports on localhost. However, this mitigation can potentially be bypassed on POSIX platforms (such as Linux, Android, and macOS) by using unspecified IP addresses like 0.0.0.0 or ::.
Root Cause Analysis
The bypass occurs in IsPortAllowedForIpEndpoint() (net/base/port_util.cc), which checks if the endpoint address is a loopback address:
bool IsPortAllowedForIpEndpoint(const IPEndPoint& endpoint) {
if (!base::FeatureList::IsEnabled(features::kRestrictAbusePortsOnLocalhost)) {
return true;
}
// This function currently restricts only on localhost.
if (!endpoint.address().IsLoopback()) {
return true;
}
...
However, IPAddress::IsLoopback() (net/base/ip_address.cc) only matches IPv4 loopback (127.0.0.0/8) and IPv6 loopback (::1):
bool IPAddress::IsLoopback() const {
// 127.0.0.1/8
if (IsIPv4())
return ip_address_[0] == 127;
// ::1
if (IsIPv6()) {
for (size_t i = 0; i + 1 < ip_address_.size(); ++i) {
if (ip_address_[i] != 0)
return false;
}
return ip_address_.back() == 1;
}
return false;
}
Because of this, 0.0.0.0 and :: are not recognized as loopback. On POSIX systems (Linux, Android, macOS), calling connect(2) or sendto(2) on a socket with 0.0.0.0 or :: as the destination address is rewritten by the kernel route lookup to loopback. Consequently, packets are delivered to services bound to loopback, but Chromium’s port restriction mechanism treats the endpoint as a non-loopback address and does not apply the port restrictions.
Potential Trigger Path
Note: These are potential steps to trigger the issue, as we have not verified them dynamically using functional exploit code.
- The
RestrictAbusePortsOnLocalhostfeature is active (e.g., on Android, restricting a port like30987). - A page served from a loopback origin (e.g.,
127.0.0.1or WebView content classified askLoopback) makes a request tohttp://0.0.0.0:30987/or targets0.0.0.0:30987via WebRTC (P2PSocketUdp::DoSend). - Local Network Access permits the request because
0.0.0.0is mapped toIPAddressSpace::kLoopback. TCPClientSocket::DoConnectorP2PSocketUdp::DoSendcallsIsPortAllowedForIpEndpoint()on{0.0.0.0, 30987}.- Since
0.0.0.0is not recognized as a loopback IP byIsLoopback(), the function returnstrue, allowing the connection. - The host kernel routes the connection to
127.0.0.1:30987, successfully reaching the restricted local service.
Suggested Fix
Ensure that IsPortAllowedForIpEndpoint() also treats unspecified/zero IP addresses (0.0.0.0 and ::) as restricted loopback equivalents. This can be done by checking endpoint.address().IsZero() in IsPortAllowedForIpEndpoint:
bool IsPortAllowedForIpEndpoint(const IPEndPoint& endpoint) {
if (!base::FeatureList::IsEnabled(features::kRestrictAbusePortsOnLocalhost)) {
return true;
}
// This function currently restricts only on localhost.
if (!endpoint.address().IsLoopback() && !endpoint.address().IsZero()) {
return true;
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.