53e38bbfa7c649f774fa753a19e13a1e66e9d160 [M144-LTS] mojo: don't overflow computation of channel read buffer size
The bug at a glance
The patch adds a CheckAdd overflow guard to the Mojo channel read-buffer size computation; the author states on the bug they are fairly confident it is not exploitable, and the change is defensive hardening on an IPC-reachable size calculation rather than a demonstrated corruption.
A compromised or malicious endpoint controlling the advertised next-read size could drive num_bytes + size_ to wrap a size_t, so a defender cares whether the wrapped (small) size then leads to an undersized buffer and a subsequent OOB write when the channel copies message bytes in.
Root cause
ReadBuffer::Reserve(num_bytes) grew the buffer via size_ = max(size_ * kGrowthFactor, num_occupied_bytes_ + num_bytes) without checking that num_bytes + size_ fits in size_t. A sufficiently large attacker-influenced num_bytes could overflow the addition, yielding a bogus (wrapped) size and an under-allocated buffer. The fix computes base::CheckAdd(num_bytes, size_) and returns nullptr on overflow; each platform read loop (fuchsia/posix/win) now treats a null buffer as a malformed/overflowed message and breaks the connection with a validation error instead of proceeding.
Key code
Reject read-buffer growth when the new-size addition overflows size_t
+ const auto new_size = base::CheckAdd(num_bytes, size_);
+ if (!new_size.IsValid()) {
+ return nullptr;
+ }
if (num_occupied_bytes_ + num_bytes > size_) {
Patch walkthrough
mojo/core/channel.cc— Adds the CheckAdd overflow guard in ReadBuffer::Reserve and documents that it returns nullptr on overflow, preventing a wrapped size from producing an undersized allocation.mojo/core/channel_posix.cc— Checks the GetReadBuffer() return for null (overflow) and, if so, sets read_error/validation_error and breaks, turning a potential OOB condition into a clean connection teardown.mojo/core/channel_fuchsia.cc— Same null-buffer handling as posix in the Fuchsia read loop.mojo/core/channel_win.cc— On a null buffer, calls OnError(kDisconnected) and returns, aborting the read rather than using a bad buffer.
Background
Mojo channel — The low-level transport carrying serialized IPC messages between Chromium processes.
ReadBuffer::Reserve — Ensures contiguous space for incoming bytes and returns the first writable address, growing the buffer as needed.
base::CheckAdd — Checked arithmetic helper that flags overflow instead of silently wrapping.
next_read_size / GetReadBuffer — The requested read capacity passed down per read iteration, influenced by prior message framing.
Vulnerability window
- Reachability — A peer process writes to a Mojo channel, driving the read loop to request additional buffer space (inferred).
- Trigger — num_bytes + size_ overflows size_t in Reserve, producing a wrapped size.
- Primitive — Pre-fix, an undersized buffer could be returned, enabling an out-of-bounds write of subsequent bytes (inferred, and per author likely not reachable).
- Escalation — Any heap corruption would need shaping into a useful primitive within the receiving process (inferred).
Proof of concept
Conceptually, a peer would advertise/stream read sizes engineered so the running size plus the next request wraps size_t; the overflow path is now rejected, and the author assesses the pre-fix path as likely unreachable, so no working trigger is provided.
Exploitation
- Control read size — Attacker-controlled endpoint influences num_bytes across read iterations toward the size_t boundary (inferred).
- Force overflow — Make num_bytes + size_ wrap so an undersized buffer is produced (inferred).
- Overwrite — Subsequent byte copy writes past the undersized allocation, corrupting adjacent heap (inferred, contested by author).
Detection & hunting
For defenders and detection engineers:
- weggli/grep for size growth patterns like ‘size_ = std::max(… * kGrowthFactor, x + num_bytes)’ without a checked add — Audit buffer-grow routines that add attacker-influenced lengths to an existing size without CheckAdd/CheckMul.
- grep for GetReadBuffer( callers that ignore a possible null return — Ensure every transport read loop handles the newly-possible null (overflow) return.
Audit directions
- mojo/core and other IPC transports (NodeChannel, data pipe buffers) — Look for other size/offset arithmetic on externally-supplied lengths lacking checked-arithmetic guards.
- Callers of GetReadBuffer across platforms — Confirm no remaining platform or variant proceeds with a null buffer after this change.