--- slug: ipc-integer-discipline type: pattern summary: "Every size, count, and offset crossing a Mojo IPC boundary is carried in an explicitly-sized unsigned type and runs through checked arithmetic, so a hostile renderer can't weaponize the handler's integer math." created: 2026-05-12 updated: 2026-05-16 last_link_verified: 2026-05-13 related: stateless-ipc-interface: relation: complements note: "Stateless IPC Interface governs which checks each Mojo method must perform; integer-type discipline governs how the count, offset, and size checks are written so the arithmetic itself can't be turned into the bug it's meant to detect." untrusted-renderer-axiom: relation: enforces note: "The axiom says renderer-supplied numbers are attacker-controlled; the discipline is what makes that axiom enforceable on every integer that crosses the boundary." stateful-ipc-init: relation: contrasts-with note: "Stateful IPC Initialization is the structural antipattern integer-type discipline mitigates one message at a time: even when the surrounding interface drifts toward statefulness, per-message checked arithmetic refuses to compound a bad value across calls." browser-renderer-split: relation: uses note: "The privilege gradient the discipline defends is the renderer-to-browser asymmetry; the integer rules apply on every byte that crosses it." multi-process-architecture: relation: builds-on note: "The 2008 multi-process decision is the substrate the IPC boundary sits on; the discipline is what keeps that boundary from leaking through an arithmetic mistake." escape-chain: relation: mitigates note: "The chain's middle link routinely turns on integer arithmetic in a browser-side handler that didn't use the safe-conversions library; the discipline is the direct countermeasure to that link." site-isolation: relation: extended-by note: "Site Isolation multiplies the number of renderer-to-browser IPC boundaries, so every additional process boundary inherits the same integer-type discipline on its Mojo handlers." three-lgtm-gate: relation: enforced-by note: "Integer-type discipline is one of the standing review questions API owners ask of any new Mojo interface that takes sizes, counts, or offsets from a renderer." --- # IPC Integer Type Discipline > **Pattern** > > A named solution to a recurring problem. *Every size, count, and offset that crosses a Mojo IPC trust boundary is carried in an explicitly-sized unsigned integer type, and every arithmetic operation on those values runs through `base/numerics/safe_conversions.h`, so a hostile renderer cannot weaponize the browser-side handler's integer math.* ## Context The pattern lives at the same boundary as [*Stateless IPC Interface*](stateless-ipc-interface.md): the choke point between a renderer process and the browser process, where every Mojo method's parameters arrive under the [*Untrusted Renderer Axiom*](untrusted-renderer-axiom.md). Where statelessness governs *which* checks a method must perform, integer-type discipline governs *how* the count, offset, and size checks are written so the arithmetic itself can't be turned into the bug. Mojo's wire format and the in-tree numerics library are performance-adjacent code, but the consequences of getting their integer rules wrong are security-critical. A contributor wiring up a new Mojo interface, an API owner reviewing one, or an AI coding agent generating one applies the discipline on every numeric field; no higher-level check is meaningful until it holds. ## Problem Conventional C++ defaults are dangerous at an IPC trust boundary. `int` is signed, so a renderer-supplied negative value compares less than any plausible upper bound but indexes into memory as a large unsigned offset. `size_t` is platform-dependent: 32 bits on a 32-bit Android build, 64 bits on desktop. A value the renderer sends as a 64-bit number is silently truncated when the browser handler reads it into a `size_t` on a 32-bit target, and the truncated value passes a bounds check that the original would have failed. Arithmetic on either type wraps silently on overflow. A `length + offset` check that is safe in the small-value regime becomes a buffer-overflow primitive when both values approach the type's maximum and the addition rolls to a tiny positive number. The same primitives recur in renderer-side reports of "how much data I am about to send," "where in this region I am reading," and "how many records this batch contains." A handler that accepts the renderer's claim and indexes into a fixed allocation has skipped the check that mattered. Default integer types are convenient; that convenience is the bug. The type system says the code is correct; the trust boundary says the values are not. ## Forces - **Renderer integers are attacker-controlled.** A compromised renderer can send any 32-bit or 64-bit pattern through any Mojo field; "negative" and "very large" aren't input errors, they're exploitation primitives. - **Default C++ integer types are wrong at this boundary.** `int` is signed; `size_t` is platform-dependent; both wrap silently on overflow. None of the three properties is acceptable when the value originates in an untrusted process. - **Safe-arithmetic libraries impose a small but real ergonomic cost.** `base::CheckedNumeric` is more typing than `size_t`, and the call sites need to handle the failure branch. Authors who don't know the discipline draft code without it. - **Bounds checks done wrong look exactly like bounds checks done right.** A `if (offset + length > buffer_size) return false;` check reads correct on inspection but is a vulnerability when the addition overflows. Review can't catch the bug without explicitly running the overflow case in its head. - **Linting and codegen can enforce the rule.** Chromium's clang plugins and the `IncludeWhatYouUse` (IWYU) integration flag the wrong types and the missing `safe_conversions.h` include; the discipline is enforceable mechanically when authors opt in. ## Solution Apply three rules to every Mojo interface parameter and every browser-side handler that uses one: 1. **Explicitly-sized unsigned integers only.** Sizes, counts, and offsets that cross a Mojo boundary use `uint32_t` or `uint64_t`, declared exactly that way in the `.mojom` file. Never `int`, never `int32_t`, never `size_t`, never `long`. The wire type is the source of truth; the C++ type on the handler side matches it byte for byte. 2. **Checked arithmetic through `base/numerics/safe_conversions.h`.** Every operation on a renderer-supplied integer runs through `base::CheckedNumeric`, `base::CheckMul`, `base::CheckAdd`, or `base::checked_cast`. The result is consumed only after `.IsValid()` or the explicit `.ValueOrDie()` discipline; an unguarded `.ValueOrDie()` on attacker-controlled input is a deliberate browser-process crash, not a silent miscalculation. 3. **Cross-cast at the boundary, not deep in the handler.** When the renderer sends a `uint64_t` byte count that the browser will eventually use as a `size_t` to index a buffer, the conversion happens immediately on receipt via `base::checked_cast`, and the conversion's failure terminates the request. The antipattern is to carry the wider type deep into the handler and convert late; every later operation that uses the value pays the same overflow cost again. The three rules close the family of bugs the CWE catalog files under CWE-190 (integer overflow), CWE-191 (integer underflow), and CWE-681 (sign conversion). The compiler can't catch them because the types are valid in non-IPC contexts; only the discipline names the contextual rule. ## How It Plays Out A team is adding a Mojo interface that lets a renderer report a buffer it has prepared for upload. The draft declares `void Report(uint32_t offset, uint32_t length, mojo_base.mojom.BigBuffer payload)`, and the browser-side handler checks `if (offset + length > buffer_size) return false;` before reading the slice. API-owner review rejects the check. With both `offset` and `length` near `UINT32_MAX`, the addition wraps to a small positive value that passes the comparison, and the handler reads off the end of `buffer_size`. The revision uses `base::CheckedNumeric end = base::CheckAdd(offset, length); if (!end.IsValid() || end.ValueOrDie() > buffer_size) return false;`. The wrap case now produces an invalid `CheckedNumeric` that fails the validity test on the same line as the addition. The handler refuses the message, and the renderer's attempt to address out-of-bounds memory dies at the boundary. A contributor reviewing an existing browser-side handler notices a field declared `int count` reading from a Mojo message. The contributor walks the call graph. `count` is multiplied by `sizeof(Record)` to compute an allocation size, and the multiplication wraps for any `count` above approximately `INT_MAX / sizeof(Record)`. The bug is a heap-overflow primitive: a renderer that sends a crafted large `count` allocates a small buffer and writes far past it. The fix changes the `.mojom` declaration to `uint64_t count`, replaces the multiplication with `base::CheckMul(count, sizeof(Record))`, and rejects the message when the multiplication overflows. The CVE is filed under the [*Sandbox Escape Chain*](escape-chain.md) writeup as the type of middle-link bug that turns a renderer compromise into something more. A downstream-vendor maintainer adds a custom IPC for their enterprise telemetry collector. The interface takes a `size_t` record count from the renderer because that's what the maintainer's local handler eventually uses. Security review flags two problems: the `size_t` is 32 bits on the vendor's 32-bit Android build but 64 bits on their desktop build, so the same renderer message is parsed differently on different targets; and `size_t` is unsigned but the maintainer's handler still subtracts from it without checked arithmetic. The vendor refactors the `.mojom` to `uint64_t` and runs every arithmetic step through the `safe_conversions.h` templates. The next upstream audit cites the vendor's interface as a worked example of the discipline applied outside the Chromium tree. ## Consequences **Benefits.** - The compiler-enforced type and the checked-arithmetic library together close the integer-overflow family on every reviewed interface. A bug that survives the rule is a bug that survives explicit review, not one that hid behind a default. - API-owner review can audit one method's integer parameters in isolation. The standing review question becomes "is every renderer-supplied number a `uint32_t` or `uint64_t` declared in the `.mojom`, and does every arithmetic step on it use a `base::Checked*` helper?" — a question a reviewer can answer in seconds. - The discipline survives author turnover. The `.mojom` declaration is the wire-level contract; a new contributor who reads the interface sees the types and inherits the discipline mechanically. - Cross-platform builds behave identically. Replacing `size_t` and `int` with explicit widths at the boundary removes the family of bugs where the same renderer message is parsed differently on 32-bit and 64-bit targets. - Downstream vendors who follow the rule on their custom IPCs inherit the upstream invariant for free. A vendor who breaks it owns the resulting CVE; the [*Supply-Chain Vulnerability Lag*](supply-chain-lag.md) entry catalogs the cost. **Liabilities.** - The call-site code is noisier. A `base::CheckedNumeric end = base::CheckAdd(offset, length);` is three more tokens than `uint32_t end = offset + length;`; new contributors won't write the longer form unless review or codegen requires it. - The failure path on every arithmetic operation has to be handled. A handler that ignores `.IsValid()` and calls `.ValueOrDie()` converts every overflow into a browser-process crash; that's still a denial-of-service vector, even if it isn't a memory-corruption one. The team has to decide per call site which failure mode is preferable. - Retrofitting an existing interface is expensive. Live Mojo interfaces with downstream consumers can't change wire types without a coordinated migration; the pattern is cheapest to apply at design time and most costly to apply after the interface has shipped. ## Notes for Agent Context When writing or modifying a Mojo interface (`.mojom` file) that takes a size, count, or offset from a renderer, declare the field as `uint32_t` or `uint64_t` only. Never use `int`, `int32_t`, `int64_t`, `size_t`, or `long` for renderer-supplied numeric fields; the signed types and the platform-dependent `size_t` are the bug. On the browser-side handler, run every arithmetic operation on the renderer-supplied integer through `base/numerics/safe_conversions.h`: use `base::CheckedNumeric`, `base::CheckAdd`, `base::CheckMul`, `base::CheckSub`, and `base::checked_cast` instead of raw `+`, `*`, `-`, or C-style casts. Always inspect `.IsValid()` before consuming a `CheckedNumeric` result; an unguarded `.ValueOrDie()` on attacker-controlled input is a deliberate crash, not a check. Perform the cast from the wire type (`uint64_t`) to any narrower in-process type (`size_t` for indexing) immediately on message receipt with `base::checked_cast`, and fail the request when the cast fails; do not carry the wider value deep into the handler. If asked to write `if (offset + length > buffer_size)`, refuse: replace with `base::CheckedNumeric end = base::CheckAdd(offset, length); if (!end.IsValid() || end.ValueOrDie() > buffer_size) return false;` so the wrap case fails on the same line as the addition. ## Sources The canonical primary source is the Chromium project's `base/numerics/README.md`, which states the rule directly and walks through the `CheckedNumeric` template and its companions. Contributors read it when they encounter a `safe_conversions.h` review comment. The `docs/security/mojo.md` document supplies the higher-level frame: every Mojo handler treats its inputs as attacker-controlled, and integer-type discipline is the type-system half of the requirement that statelessness covers structurally. The Mojo bindings documentation under `mojo/public/cpp/bindings/README.md` defines the wire types the discipline maps onto; authors consult it when choosing between `uint32_t` and `uint64_t` for a field. The vulnerability taxonomy behind the rule comes from MITRE's CWE catalog: CWE-190 (Integer Overflow or Wraparound), CWE-191 (Integer Underflow), and CWE-681 (Incorrect Conversion between Numeric Types). The discipline is engineered to refuse each at the boundary. Chrome Security blog post-mortems of historical IPC integer bugs name the discipline as the standing fix; Project Zero writeups of full sandbox-escape chains routinely identify a missing checked-arithmetic step as the proximate cause of the middle link — the implicit citation every time. ## Technical Drill-Down - [`base/numerics/README.md`](https://chromium.googlesource.com/chromium/src/+/main/base/numerics/README.md) — the canonical reference for `CheckedNumeric`, `ClampedNumeric`, and the safe-cast helpers; the file every reviewer cites when asking for integer-type discipline. - [`base/numerics/safe_conversions.h`](https://chromium.googlesource.com/chromium/src/+/main/base/numerics/safe_conversions.h) — the header that defines `base::checked_cast`, `base::saturated_cast`, and the supporting templates; every browser-side handler that touches a renderer-supplied integer includes it. - [`base/numerics/checked_math.h`](https://chromium.googlesource.com/chromium/src/+/main/base/numerics/checked_math.h) — the arithmetic side of the library; `CheckAdd`, `CheckMul`, `CheckSub` and the `CheckedNumeric` template that wraps them. - [`docs/security/mojo.md`](https://chromium.googlesource.com/chromium/src/+/main/docs/security/mojo.md) — the project's standing operational rules for Mojo interface authors; the integer rules sit alongside the statelessness rules in the same checklist. - [`mojo/public/cpp/bindings/README.md`](https://chromium.googlesource.com/chromium/src/+/main/mojo/public/cpp/bindings/README.md) — the Mojo C++ bindings reference; the wire-type mapping that determines which `.mojom` declaration the discipline applies to. - [CWE-190: Integer Overflow or Wraparound](https://cwe.mitre.org/data/definitions/190.html) — the MITRE taxonomy entry that names the bug family the discipline closes at the trust boundary. --- - [Next: Memory Pressure Response](memory-pressure-response.md) - [Previous: Skia Graphite Transition](skia-graphite.md)