e8736f9d1a1d77dc60fabfd4e0a50f46e4e834fd [mojo] Guard VM buffer address access in ChannelMac
The bug at a glance
A shutdown race nulls the ChannelMac VM buffer address (via receive_buffer_.reset()/send_buffer_.reset()), and a pending Mach-port read/write callback then dereferences address 0 or passes a null header to mach_msg, producing a reproducible crash (SIGSEGV) but no attacker-controlled memory corruption.
An attacker who can influence the timing of a mojo channel teardown on macOS could race a pending I/O callback against buffer deallocation to crash the process; a defender cares because it is a renderer/broker-process liveness and stability issue rather than a corruption primitive.
Root cause
During ChannelMac shutdown, receive_buffer_.reset() and send_buffer_.reset() deallocate the backing VM page and set the buffer address to 0. If an in-flight Mach-port read event (ReceiveMessages path) or a queued write callback (SendPendingMessagesLocked / SendMessageLocked) runs during or after this teardown, the code builds a base::BufferIterator over reinterpret_cast<char*>(address()==0) with the old size, so it dereferences null or hands a null header to mach_msg. The fix adds early-return guards checking send_buffer_.address() and receive_buffer_.address() before any buffer access, mirroring ChannelLinux’s defensive validation so callbacks exit gracefully.
Key code
Guard VM buffer address before building BufferIterator / calling mach_msg
if (!send_buffer_.address()) {
return false;
}
...
if (!receive_buffer_.address()) {
return;
}
Patch walkthrough
mojo/core/channel_mac.cc— Adds address-validity guards at the start of SendPendingMessagesLocked, SendMessageLocked, and the receive handler so that a callback firing during/after reset() returns early instead of dereferencing a freed (address-0) VM buffer or passing a null header to mach_msg.
Background
mojo ChannelMac — The macOS-specific mojo IPC channel implementation that shuttles messages between processes using Mach ports and VM-page-backed send/receive buffers.
base::mac VM buffer (ScopedMachVM) — A buffer backed by an allocated Mach VM page whose reset() deallocates the page and zeroes the reported address().
Vulnerability window
- Reachability — Reachable on macOS whenever a mojo channel is torn down (e.g. process/connection shutdown) with I/O callbacks still pending; ordinary operation, no special flag or gesture needed.
- Trigger — A pending Mach-port read event or queued write callback executes after receive_buffer_.reset()/send_buffer_.reset() has nulled the buffer address during shutdown (inferred ordering).
- Primitive — Null-pointer dereference / null mach_msg header producing EXC_BAD_ACCESS / SIGSEGV, i.e. a crash only.
- Escalation — No escalation path; the access is to address 0 with no attacker-controlled offset, so it is a denial-of-service rather than a corruption primitive (inferred).
Proof of concept
Conceptually, drive a mojo connection over the macOS channel while rapidly initiating teardown so that an outstanding send or Mach-port receive callback is delivered after the buffers have been reset to address 0; the subsequent buffer access crashes. No exploit code provided.
Exploitation
- Timing — Induce channel shutdown while I/O callbacks remain queued so the reset() and the callback interleave (inferred).
- Impact — Process crash from null dereference; not weaponizable into code execution from the diff alone (inferred).
Detection & hunting
For defenders and detection engineers:
- weggli: buffer address cast reinterpret_cast<char>(->address()) without a preceding if(!->address()) guard* — Hunt for ScopedMachVM/VM-buffer address() consumers that build BufferIterator or call mach_msg without first validating the address is non-zero.
- Crash telemetry EXC_BAD_ACCESS at address 0 in mojo::core::channel_mac during shutdown — Correlate SIGSEGV reports whose stacks pass through SendMessageLocked/SendPendingMessages/ReceiveMessages near teardown.
Audit directions
- Other platform Channel implementations (channel_fuchsia, channel_win, channel_posix) — Check each platform’s send/receive buffer accessors for the same reset()-then-callback race and confirm they guard against a zeroed/freed buffer during shutdown.