High watch CVSS 8.3 chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in Mojo
DescriptionInteger overflow in Mojo
ComponentMojo
Sub-componentMojo
Bug ClassInteger Overflow
Action tierwatch — ub-class only — no demonstrated corruption path
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.
Tracker513138301
Fix commit1598e0f19160 (chromium/src) +25/-0
CWECWE-472
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-14 (per vendor advisory)
Fix landed2026-06-16 (public git)
Disclosed2026-06-25
Patch gap9 days — fix public in source before the advisory

Background

Mojo `Channel`
the per-connection object in mojo/core that reads raw bytes from a platform handle (socket/pipe) and reassembles them into Mojo messages for the receiving process.
`ReadBuffer`
the growable byte buffer a Channel uses to accumulate incoming data; Reserve ensures it has enough contiguous space for the next read and returns a pointer to the first free byte.
`GetReadBuffer` / `next_read_size`
before each read the channel asks for a buffer sized to the number of bytes the current partial message still expects, a value influenced by the peer-supplied message header.
`base::CheckAdd`
a checked-arithmetic helper that computes a sum and reports via IsValid() whether it overflowed the integer type.

Root Cause Analysis

Channel::ReadBuffer::Reserve(size_t num_bytes) grew the buffer using num_occupied_bytes_ + num_bytes and size_ * kGrowthFactor without checking whether the addition overflowed size_t. Because num_bytes is derived from the peer-controlled next-read size, a malformed message declaring an enormous expected size could make num_bytes + size_ wrap around, so the buffer could be grown to a too-small size and a subsequent read could write past it (inferred).

The fix adds const auto new_size = base::CheckAdd(num_bytes, size_); and returns nullptr when !new_size.IsValid(), before the growth computation runs; the three platform read loops (channel_fuchsia.cc, channel_posix.cc, channel_win.cc) then detect the null buffer and treat it as a read/validation error, breaking the connection instead of proceeding. This is a tightening: an overflow guard is added and propagated as an error up the call stack. The commit author states they are “fairly confident this isn’t exploitable” and made the change to be “more obviously correct.”

Key insight
The read-buffer size computation summed a peer-influenced byte count with the current size without an overflow check; the fix computes the sum with base::CheckAdd, returns nullptr on overflow, and makes every platform read loop bail on a null buffer.

Attack Path

  1. Send a malformed length The peer at the other end of a Mojo channel sends a message whose declared/next-read size is close to the maximum of size_t, becoming a very large num_bytes.
  2. Overflow the size computation In ReadBuffer::Reserve, num_occupied_bytes_ + num_bytes (or size_ * kGrowthFactor) overflows size_t and wraps to a small value, so the buffer is under-grown while the channel still intends to read num_bytes (inferred as the pre-patch failure mode).
  3. Out-of-bounds write on read The subsequent platform read writes the incoming bytes into the under-sized buffer, past its allocation (inferred — the diff shows only the added guard, not the sink, and the author doubts exploitability).
  4. Fix short-circuits Post-patch, Reserve returns nullptr on overflow and the read loop sets read_error/validation_error (or calls OnError(kDisconnected) on Windows) and tears down the channel before any read occurs.

Impact Assessment

The size computation is driven by data the channel peer controls, so the most security-relevant instance is a compromised renderer (the peer) sending a malformed message to the privileged browser process (the reader) across their Mojo channel; however Channel is generic, so the peer/reader roles depend on which connection carries the malformed message. The advisory rates this high severity with impact “Integer overflow in Mojo”, but the patch only adds a guard and its author states he is fairly confident the overflow is not exploitable, so any under-allocation or out-of-bounds write is (inferred) rather than demonstrated. The advisory does not state in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
mojo/core/channel.cc
modified
if
mojo/core/channel_fuchsia.cc
modified
if
mojo/core/channel_posix.cc
modified
if
mojo/core/channel_win.cc
modified

Files Changed

  • mojo/core/channel.cc
  • mojo/core/channel_fuchsia.cc
  • mojo/core/channel_posix.cc
  • mojo/core/channel_win.cc

Audit Directions

  • Unchecked size arithmetic on peer input
    Audit buffer growth/allocation code where a size derived from an incoming message is added to or multiplied with an existing size without base::CheckAdd/CheckMul; these are prime integer-overflow candidates.
  • Nullable allocator return propagation
    When a helper is changed to return nullptr on failure, verify every caller checks it before dereferencing or passing it to a read/write sink, across all platform variants.
  • Platform-specific read/write loops
    Cross-check the Fuchsia/POSIX/Windows Channel implementations against each other, since a guard added to one platform’s read loop must be mirrored in the others to be effective.
From 1598e0f1916030a6a9bd2ac6b878b48a696b35e5 Mon Sep 17 00:00:00 2001
From: Elly <[email protected]>
Date: Tue, 16 Jun 2026 08:04:55 -0700
Subject: [PATCH] mojo: don't overflow computation of channel read buffer size

I am fairly confident this isn't exploitable (see rationale on the bug)
but to make the code more obviously correct, add a check for overflow
and error handling up the call stack.

Fixed: 513138301
Change-Id: Ie2093dc08c2761951e4988ef283e21662de3bfd8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7946296
Commit-Queue: Elly <[email protected]>
Reviewed-by: Fred Shih <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1647564}
---

diff --git a/mojo/core/channel.cc b/mojo/core/channel.cc
index 32a35844..1131bb4a 100644
--- a/mojo/core/channel.cc
+++ b/mojo/core/channel.cc
@@ -939,7 +939,12 @@
 
   // Ensures the ReadBuffer has enough contiguous space allocated to hold
   // |num_bytes| more bytes; returns the address of the first available byte.
+  // If computing the new size overflows, returns nullptr.
   char* Reserve(size_t num_bytes) {
+    const auto new_size = base::CheckAdd(num_bytes, size_);
+    if (!new_size.IsValid()) {
+      return nullptr;
+    }
     if (num_occupied_bytes_ + num_bytes > size_) {
       size_ = std::max(static_cast<size_t>(size_ * kGrowthFactor),
                        num_occupied_bytes_ + num_bytes);
diff --git a/mojo/core/channel_fuchsia.cc b/mojo/core/channel_fuchsia.cc
index b7a75b5..adedc2b4 100644
--- a/mojo/core/channel_fuchsia.cc
+++ b/mojo/core/channel_fuchsia.cc
@@ -310,6 +310,13 @@
     do {
       buffer_capacity = next_read_size;
       char* buffer = GetReadBuffer(&buffer_capacity);
+      // A null buffer means the size computation for the new buffer overflowed
+      // so mark the connection as broken and bail.
+      if (!buffer) {
+        read_error = true;
+        validation_error = true;
+        break;
+      }
       DCHECK_GT(buffer_capacity, 0u);
 
       uint32_t bytes_read = 0;
diff --git a/mojo/core/channel_posix.cc b/mojo/core/channel_posix.cc
index 40aafd68..bdc9a5a 100644
--- a/mojo/core/channel_posix.cc
+++ b/mojo/core/channel_posix.cc
@@ -291,6 +291,13 @@
   do {
     buffer_capacity = next_read_size;
     char* buffer = GetReadBuffer(&buffer_capacity);
+    // A null buffer means that computing the read size overflowed, which means
+    // we received a malformed message; bail.
+    if (!buffer) {
+      read_error = true;
+      validation_error = true;
+      break;
+    }
     DCHECK_GT(buffer_capacity, 0u);
 
     std::vector<base::ScopedFD> incoming_fds;
diff --git a/mojo/core/channel_win.cc b/mojo/core/channel_win.cc
index 8c87829..f4f3e83 100644
--- a/mojo/core/channel_win.cc
+++ b/mojo/core/channel_win.cc
@@ -306,6 +306,12 @@
 
     size_t buffer_capacity = next_read_size_hint;
     char* buffer = GetReadBuffer(&buffer_capacity);
+    // A null buffer means the size computation for the buffer overflowed;
+    // break the connection.
+    if (!buffer) {
+      OnError(Error::kDisconnected);
+      return;
+    }
     DCHECK_GT(buffer_capacity, 0u);
 
     BOOL ok =
Loading diff…

Original Bug Report

reported by [email protected]

Potential 32-bit Integer Overflow in Mojo Channel::ReadBuffer::Reserve leading to Heap OOB Write

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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential 32-bit integer overflow exists in Mojo’s ReadBuffer::Reserve function, allowing an attacker to bypass buffer reallocation logic. This can result in an out-of-bounds heap write in the browser process when processing crafted IPC messages from a compromised renderer. The issue specifically affects 32-bit platforms where size_t is 32 bits.

Affected files:

  • mojo/core/channel.cc
  • mojo/core/channel_posix.cc
  • mojo/core/channel_win.cc
  • mojo/core/channel.h

Estimated timestamp from git blame: 2022-08-15

Summary

A potential integer overflow vulnerability has been identified in mojo/core/channel.cc. On 32-bit architectures, the Channel::ReadBuffer::Reserve method can be induced to return a pointer that is out-of-bounds of its backing heap allocation. This occurs because the calculation used to determine if the buffer should grow can overflow, leading the code to incorrectly conclude that sufficient space is already available.

Root Cause Analysis

In mojo/core/channel.cc, the Channel::ReadBuffer::Reserve(size_t num_bytes) method is responsible for ensuring the read buffer has enough contiguous space for incoming data:

char* Reserve(size_t num_bytes) {
  if (num_occupied_bytes_ + num_bytes > size_) {
    // ... reallocation logic ...
  }
  return data_.subspan(num_occupied_bytes_).data();
}

On 32-bit systems, size_t is 32 bits. An attacker can control num_bytes (via a Mojo message header parsed in TryDispatchMessage) by providing a value such that num_occupied_bytes_ + num_bytes overflows and wraps around. For example, if the current buffer is full (num_occupied_bytes_ == size_ == 4096), and num_bytes is 0xFFFFFFFF, the sum wraps to 4095. Since 4095 > 4096 is false, the reallocation is skipped.

Reserve then returns data_.subspan(4096).data(), which is a pointer to the first byte immediately following the 4096-byte allocation.

Exploitation Potential

The transport layer (e.g., ChannelPosix or ChannelWin) uses the pointer returned by Reserve to read data from the IPC socket. In ChannelPosix::OnFdReadable:

char* buffer = GetReadBuffer(&buffer_capacity);
// ...
ssize_t read_result = SocketRecvmsg(socket_.get(), buffer, buffer_capacity, ...);

Here, buffer will be the out-of-bounds pointer, and buffer_capacity will be the original large num_bytes value. This allows a compromised renderer to perform a linear heap overflow in the unsandboxed browser process, which can be leveraged for a sandbox escape.

Suggested Fix

Use base::CheckedNumeric to perform the addition and comparison in ReadBuffer::Reserve safely to detect and handle potential overflows.

char* Reserve(size_t num_bytes) {
  base::CheckedNumeric<size_t> required_size = num_occupied_bytes_;
  required_size += num_bytes;
  if (!required_size.IsValid() || required_size.ValueOrDie() > size_) {
    // ... reallocation logic ...
  }
  return data_.subspan(num_occupied_bytes_).data();
}

Suggested Reproduction Steps

Note: These steps are theoretical as they have not been validated with a running PoC.

  1. Target a 32-bit Chrome/WebView build (e.g., 32-bit Android or Windows x86).
  2. From a compromised renderer, send a Mojo message where the header specifies a very large size (e.g., 0xFFFFFFFF).
  3. Ensure the browser-side Mojo Channel has some data already in its ReadBuffer such that the addition overflows.
  4. Send the overflow payload over the IPC channel. The browser should write this data past the end of the ReadBuffer allocation.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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