Medium watch CVSS 4.3 chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Network
DescriptionIncorrect authorization in Network
ComponentNetwork
Sub-componentNet
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker513719741
Fix commitd81fbc06ef3c (chromium/src) +27/-0
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-16 (per vendor advisory)
Fix landed2026-06-30 (public git)
Disclosed2026-08-25
Patch gap56 days — fix public in source before the advisory

Background

`IPAddress`
Chromium’s network address type that stores an address as a raw byte buffer (ip_address_), 4 bytes for IPv4 and 16 bytes for IPv6.
IPv4-mapped IPv6
an IPv6 address of the form ::ffff:a.b.c.d whose first 12 bytes are the ::ffff: prefix and whose embedded IPv4 lives in bytes 12–15.
`IsMulticast()`
a classifier returning whether an address is multicast, used by higher-level network policy that treats multicast addresses specially.
`IsIPv4MappedIPv6()`
a predicate identifying the ::ffff:a.b.c.d form so its embedded IPv4 can be inspected at offset 12.

Root Cause Analysis

IPAddress::IsMulticast() in net/base/ip_address.cc only had two branches: an IsIPv4() branch reading ip_address_[0], and an IsIPv6() branch checking ip_address_[0] == 0xff. For an IPv4-mapped IPv6 address such as ::ffff:224.0.0.1, IsIPv4() is false so it fell through to the IsIPv6() branch, where ip_address_[0] is 0x00 (the leading byte of the ::ffff: prefix), never 0xff — so the function returned false and misclassified a multicast address as non-multicast. This violated the invariant that an address’s multicast status must be independent of which textual/binary form (native IPv4, native IPv6, or IPv4-mapped) it is expressed in.

The fix inserts an IsIPv4MappedIPv6() branch before the IsIPv6() branch that reads the embedded IPv4 leading byte at ip_address_[12] and returns true when it is in 224–239, mirroring the existing handling in IPAddress::IsLinkLocal(). The added unit-test boundary cases (::ffff:224.0.0.1, ::ffff:239.255.255.255 true; ::ffff:223.255.255.255, ::ffff:240.0.0.0 false) confirm the corrected range check.

Key insight
The classifier inspected ip_address_[0] for all 16-byte addresses, ignoring that IPv4-mapped IPv6 stores its meaningful leading byte at offset 12; the fix adds a dedicated IsIPv4MappedIPv6() branch checking ip_address_[12], closing the classification gap.

Attack Path

  1. Express a multicast target in mapped form An attacker or a request path supplies a destination as an IPv4-mapped IPv6 literal such as ::ffff:224.0.0.1 instead of the native 224.0.0.1.
  2. Bypass the multicast check Pre-patch IsMulticast() returns false for the mapped form, so any authorization/filtering logic that special-cases multicast addresses (the advisory classifies this as incorrect authorization in Network) treats the address as ordinary unicast.
  3. Reach a restricted address The request is permitted to a multicast destination that the multicast-aware policy was meant to gate. (inferred, as the specific downstream policy consumer is not included in the diff)

Impact Assessment

An attacker gains an authorization bypass in the network stack by encoding a multicast address in IPv4-mapped IPv6 form so that IsMulticast()-gated checks no longer recognize it, in whatever process consumes IPAddress::IsMulticast() for policy decisions. The advisory classifies this as incorrect authorization in Network; no exploited-in-the-wild status is stated, and the concrete consequence at each call site of IsMulticast() is (inferred) since those consumers are not part of the diff.

Files Changed

  • net/base/ip_address_unittest.cc
  • net/base/ip_address.cc

Audit Directions

  • Mapped-form classification gaps
    Audit every IPAddress predicate (IsMulticast, IsLoopback, IsLinkLocal, IsReserved, etc.) for handling of IsIPv4MappedIPv6(); any that inspect ip_address_[0] for 16-byte addresses may miss the embedded IPv4 at offset 12.
  • Alternate address encodings in policy checks
    Look for security decisions keyed on address category where an equivalent address can be expressed in a form (mapped, compressed, or with different byte layout) the classifier does not normalize.
  • Divergent range logic
    Compare boundary constants (224–239, >= 240) across native and mapped code paths to ensure they stay in sync when either is updated.
From d81fbc06ef3c378671f1dd6d7668583456ae294c Mon Sep 17 00:00:00 2001
From: Kenichi Ishibashi <[email protected]>
Date: Mon, 29 Jun 2026 21:33:17 -0700
Subject: [PATCH] net: recognize IPv4-mapped IPv6 multicast in IsMulticast()

IPAddress::IsMulticast() classified ::ffff:224.0.0.0/4 as non-multicast
because for a 16-byte address it only inspects ip_address_[0], which is
0x00 for IPv4-mapped IPv6 addresses.

Add an IsIPv4MappedIPv6() branch that checks the embedded IPv4 leading
byte at offset 12, mirroring the existing handling in
IPAddress::IsLinkLocal(), and extend IPAddressTest.IsMulticast with
mapped-form boundary cases.

Bug: 513719741
Change-Id: I7a7e429a52d1d2aaf06e115f1cea578176f01037
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8018062
Commit-Queue: Kenichi Ishibashi <[email protected]>
Reviewed-by: Adam Rice <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1654567}
---
diff --git a/net/base/ip_address.cc b/net/base/ip_address.cc
index 1c6cb9e..8406d19f 100644
--- a/net/base/ip_address.cc
+++ b/net/base/ip_address.cc
@@ -231,11 +231,19 @@
 }
 
 bool IPAddress::IsMulticast() const {
+  // 224.0.0.0/4
   if (IsIPv4()) {
     uint8_t first_byte = ip_address_[0];
     return first_byte >= 224 && first_byte <= 239;
   }
 
+  // [::ffff:224.0.0.0]/100
+  if (IsIPv4MappedIPv6()) {
+    uint8_t first_byte = ip_address_[12];
+    return first_byte >= 224 && first_byte <= 239;
+  }
+
+  // [ff00::]/8
   if (IsIPv6()) {
     return ip_address_[0] == 0xff;
   }
diff --git a/net/base/ip_address_unittest.cc b/net/base/ip_address_unittest.cc
index c008dcd..76685fc 100644
--- a/net/base/ip_address_unittest.cc
+++ b/net/base/ip_address_unittest.cc
@@ -348,6 +348,25 @@
   ASSERT_TRUE(ipv6_non_multicast.AssignFromIPLiteral("fe80::1"));
   EXPECT_FALSE(ipv6_non_multicast.IsMulticast());
 
+  IPAddress ipv4_mapped_multicast;
+  ASSERT_TRUE(ipv4_mapped_multicast.AssignFromIPLiteral("::ffff:224.0.0.1"));
+  EXPECT_TRUE(ipv4_mapped_multicast.IsMulticast());
+
+  IPAddress ipv4_mapped_last_multicast;
+  ASSERT_TRUE(
+      ipv4_mapped_last_multicast.AssignFromIPLiteral("::ffff:239.255.255.255"));
+  EXPECT_TRUE(ipv4_mapped_last_multicast.IsMulticast());
+
+  IPAddress ipv4_mapped_non_multicast;
+  ASSERT_TRUE(
+      ipv4_mapped_non_multicast.AssignFromIPLiteral("::ffff:223.255.255.255"));
+  EXPECT_FALSE(ipv4_mapped_non_multicast.IsMulticast());
+
+  IPAddress ipv4_mapped_after_multicast;
+  ASSERT_TRUE(
+      ipv4_mapped_after_multicast.AssignFromIPLiteral("::ffff:240.0.0.0"));
+  EXPECT_FALSE(ipv4_mapped_after_multicast.IsMulticast());
+
   IPAddress invalid;
   EXPECT_FALSE(invalid.IsMulticast());
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/net/base/ip_address_unittest.cc b/net/base/ip_address_unittest.cc
index c008dcd..76685fc 100644
--- a/net/base/ip_address_unittest.cc
+++ b/net/base/ip_address_unittest.cc
@@ -348,6 +348,25 @@
   ASSERT_TRUE(ipv6_non_multicast.AssignFromIPLiteral("fe80::1"));
   EXPECT_FALSE(ipv6_non_multicast.IsMulticast());
 
+  IPAddress ipv4_mapped_multicast;
+  ASSERT_TRUE(ipv4_mapped_multicast.AssignFromIPLiteral("::ffff:224.0.0.1"));
+  EXPECT_TRUE(ipv4_mapped_multicast.IsMulticast());
+
+  IPAddress ipv4_mapped_last_multicast;
+  ASSERT_TRUE(
+      ipv4_mapped_last_multicast.AssignFromIPLiteral("::ffff:239.255.255.255"));
+  EXPECT_TRUE(ipv4_mapped_last_multicast.IsMulticast());
+
+  IPAddress ipv4_mapped_non_multicast;
+  ASSERT_TRUE(
+      ipv4_mapped_non_multicast.AssignFromIPLiteral("::ffff:223.255.255.255"));
+  EXPECT_FALSE(ipv4_mapped_non_multicast.IsMulticast());
+
+  IPAddress ipv4_mapped_after_multicast;
+  ASSERT_TRUE(
+      ipv4_mapped_after_multicast.AssignFromIPLiteral("::ffff:240.0.0.0"));
+  EXPECT_FALSE(ipv4_mapped_after_multicast.IsMulticast());
+
   IPAddress invalid;
   EXPECT_FALSE(invalid.IsMulticast());
 }
Loading diff…

Original Bug Report

reported by [email protected]

Bypass of Direct Sockets Local Network permissions via IPv4-mapped IPv6 literals

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: Chromium’s net::IPAddress::IsMulticast() incorrectly returns false for IPv4-mapped IPv6 multicast addresses, such as ::ffff:239.255.255.250. In the Direct Sockets API, this flaw allows these addresses to bypass the LOCAL_NETWORK permission prompt and the direct-sockets-private Permissions Policy. A potential attacker could exploit this to silently discover and interact with devices on the user’s local network via multicast protocols.

Affected files:

  • net/base/ip_address.cc
  • content/browser/direct_sockets/direct_sockets_service_impl.cc

Estimated timestamp from git blame: 2025-09-03

Potential Vulnerability: Direct Sockets Permission Bypass

A potential vulnerability has been identified in how Chromium classifies IPv4-mapped IPv6 multicast addresses, leading to a security check bypass in the Direct Sockets API.

Root Cause

The primary issue resides in net::IPAddress::IsMulticast() within net/base/ip_address.cc. The current implementation for IPv6 addresses only checks if the first byte is 0xff. However, for IPv4-mapped IPv6 addresses (which follow the format ::ffff:a.b.c.d), the first byte is 0x00 regardless of whether the mapped IPv4 address is a multicast address.

// net/base/ip_address.cc
bool IPAddress::IsMulticast() const {
  if (IsIPv4()) {
    uint8_t first_byte = ip_address_[0];
    return first_byte >= 224 && first_byte <= 239;
  }
  if (IsIPv6()) {
    return ip_address_[0] == 0xff; // Returns false for IPv4-mapped multicast
  }
  return false;
}

This classification is critical for the Direct Sockets API in content/browser/direct_sockets/direct_sockets_service_impl.cc. The function RequiresPrivateNetworkAccess relies on IsMulticast() to identify endpoints that require high-privilege permissions:

// content/browser/direct_sockets/direct_sockets_service_impl.cc
bool RequiresPrivateNetworkAccess(const net::AddressList& addresses) {
  return std::ranges::any_of(
      addresses.endpoints(), [](const net::IPEndPoint& ip_endpoint) {
        return network::IPAddressToIPAddressSpace(ip_endpoint.address()) ==
                   network::mojom::IPAddressSpace::kLocal ||
               ip_endpoint.address().IsMulticast();
      });
}

Because IsMulticast() returns false for mapped addresses, and network::IPAddressToIPAddressSpace() treats these multicast ranges as kPublic (since they are not in the standard private unicast ranges), RequiresPrivateNetworkAccess returns false. This causes the Browser process to skip the LOCAL_NETWORK permission prompt and the direct-sockets-private Permissions Policy check.

Potential Trigger Path

  1. A potential attacker creates an Isolated Web App (IWA) with the direct-sockets permission enabled.
  2. The IWA executes JavaScript to open a UDP socket to an IPv4-mapped IPv6 multicast address literal: new UDPSocket({ remoteAddress: '::ffff:239.255.255.250', remotePort: 1900 }).
  3. The Browser process resolves the literal and calls GetRequiredPermissions, which returns an empty list due to the IsMulticast() flaw.
  4. The Browser skips user permission prompts and policy enforcement, granting the socket access.
  5. On dual-stack systems (Linux, macOS, Windows), the underlying OS networking stack will correctly emit IPv4 multicast packets for these mapped addresses.

Impact

An attacker could silently perform local network reconnaissance (e.g., via SSDP or mDNS) and interact with local network devices without the user’s knowledge or consent, bypassing the intended security boundaries of the Direct Sockets API.

Suggested Fix

Update net::IPAddress::IsMulticast() in net/base/ip_address.cc to explicitly handle IPv4-mapped IPv6 addresses by checking the mapped IPv4 portion:

bool IPAddress::IsMulticast() const {
  if (IsIPv4()) {
    uint8_t first_byte = ip_address_[0];
    return first_byte >= 224 && first_byte <= 239;
  }
  if (IsIPv4MappedIPv6()) {
    return ConvertIPv4MappedIPv6ToIPv4(*this).IsMulticast();
  }
  if (IsIPv6()) {
    return ip_address_[0] == 0xff;
  }
  return false;
}

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


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.

View on issue tracker