--- slug: stateful-ipc-init type: antipattern summary: "A Mojo interface requires sequential calls, later operations presuming state from an earlier Init; a compromised renderer reorders the sequence and the browser-side handler runs against state the renderer chose." created: 2026-05-12 updated: 2026-05-16 last_link_verified: 2026-05-13 related: stateless-ipc-interface: relation: prevented-by note: "Stateless IPC Interface is the direct corrective pattern: every Mojo method validates and authorizes on its own message, with no prior call's state load-bearing on a browser-side check." untrusted-renderer-axiom: relation: violates note: "The antipattern violates the axiom directly: it trusts a renderer claim from one call (that an earlier Init() ran with validated arguments) inside a later handler that the renderer can re-order or omit." browser-renderer-split: relation: leaks-across note: "The browser-renderer privilege split is the trust asymmetry the antipattern erodes when stateful interfaces let renderer-influenced state flow into a privileged handler without per-message validation." multi-process-architecture: relation: violates note: "The 2008 multi-process decision created the boundary the antipattern leaks across; the boundary is structural, the rule that defends it is the axiom, and a stateful IPC handler is what fails to honor the rule." ipc-integer-discipline: relation: complements note: "IPC integer-type discipline is the type-level companion fix at the same boundary: statelessness governs which checks are required per message, integer-type discipline governs how the count, offset, and size checks are written. A stateful interface that also skips integer-type discipline produces compounding bugs." three-lgtm-gate: relation: prevented-by note: "The three-LGTM API-owner gate is the governance mechanism that refuses new Mojo interfaces failing the stateless rule. The antipattern's most reliable mitigation is upstream review." escape-chain: relation: enables note: "The chain's middle link (escalation from a renderer-process compromise into a browser-process memory-corruption primitive) is almost always a browser-side handler that trusted a prior renderer call. The antipattern is the recurring source of that middle link." exploit-chain: relation: enables note: "Where Sandbox Escape Chain describes the project's threat model in the abstract, Exploit Chain Anatomy enumerates the concrete chain structure. Stateful IPC initialization is one of the named recurring forms the middle link takes." --- # Stateful IPC Initialization > **Antipattern** > > A recurring trap that causes harm — learn to recognize and escape it. *A Mojo interface requires sequential method calls. State established on one call (typically an `Init()`) is presumed by later operational calls. A compromised renderer reorders the sequence and the browser-side handler runs against state the renderer chose.* ## Symptoms - A Mojo interface implementation class on the browser side carries member fields populated by an `Init`, `Begin`, or `Open` method and consulted by subsequent operational methods. - The interface's documentation says "call `Init` before any other method," and the browser-side code reads as if that ordering were guaranteed. - A handler reads a `url::Origin`, `GURL`, profile identifier, or buffer size from an instance field rather than from the message it is currently processing. - API-owner review comments on the interface ask "what happens if `Op()` is called before `Init()`?" or "what happens if `Init()` is called twice with different arguments?", and the answers are not in the design document. - A `mojom` file shows a `Begin`/`Continue`/`Commit` triplet, a `SetX` / `UseX` split, or an iterator-shaped surface where one method produces a handle the next method dereferences. - A handler comment reads "the renderer has already validated this" or "`Init` checked the origin, so we can trust it here." - The post-mortem on a High- or Critical-severity browser-process bug attributes the root cause to "method called out of order" or "uninitialized member field dereference under hostile call ordering." ## Why It Happens Stateful object design is the C++ default. Conventional C++ teaches that an object is constructed once, populates its invariants, and then exposes operations that depend on those invariants. A Mojo interface implementation is *also* an object on the browser side, and the gravity of the language idiom pulls every author toward distributing responsibility across a constructor-shaped `Init` and operation-shaped methods. The pattern feels natural; the prohibition is Chromium-specific. The cost of doing the right thing is real. A stateless protocol sends more bytes per call: the identity, origin, and size fields ride on every operational message rather than being shared via a one-time `Init`. The bindings code is slightly more verbose. Some interfaces feel awkward when the natural shape of the operation is multi-step (streaming uploads, long-running media decodes, iterator-shaped result sets). Authors who weigh the cost without weighing the security gain reach for statefulness. The bindings don't enforce ordering. Mojo guarantees in-order delivery on a single message pipe, but it doesn't require any specific sequence of methods to be called. A compromised renderer can issue the interface's methods in any sequence the operating system can deliver, including sequences the author didn't consider. The author's mental model (Init runs first; subsequent calls inherit its validation) holds only as long as the renderer cooperates, and the [*Untrusted Renderer Axiom*](untrusted-renderer-axiom.md) says the renderer does not cooperate. Per-channel state is invisible to per-message review. API-owner review reads one method at a time. A method that looks safe in isolation because it consults a member field can only be audited as unsafe by reading the whole interface's call graph. Review at Chromium's scale can't afford to read call graphs interface by interface; a stateful design hides the bug from the gate that should have caught it. Refactor cost compounds. Once an interface ships with downstream consumers (a feature in Stable, a downstream Chromium-based product, a developer-facing API), changing it requires coordinated migration. The longer the antipattern lives, the more expensive its removal becomes. Projects end up with stateful surfaces everyone agrees should be refactored but nobody has the budget to retire. ## The Harm A compromised renderer can call operational methods *before* the `Init`. Member fields are zero-initialized or uninitialized; the handler dereferences them, indexes into them, or treats them as authenticated identifiers. The outcomes range from a null-pointer crash (a denial-of-service bug, Low severity) to a use-of-uninitialized-memory primitive (a High-severity browser-process memory-corruption bug whose exploitation primitive is the middle link of a sandbox-escape chain). A compromised renderer can call `Init` with attacker-chosen arguments, run the operational method, then call `Init` again with different arguments. The browser-side handler, processing the second `Init`, may free or replace state the first `Init` allocated while a callback or async continuation from the first operational call still holds references. A use-after-free in the browser process is the canonical primitive for the chain's middle link. A compromised renderer can omit a check the `Init` was responsible for performing. Suppose `Init(origin, settings)` validated that the renderer was authorized to use the interface with that origin and stored the result on the channel, and `Op(payload)` consulted only the stored result. The renderer can construct a sequence where `Init` ran for an allowed origin earlier in the channel's life and `Op` is invoked under a different security context. The handler cannot detect the shift; the browser process loses sight of which origin the operation actually belongs to. The antipattern is the recurring middle link of the [*Sandbox Escape Chain*](escape-chain.md). The chain has three structural links: a renderer-process compromise (a V8 type confusion, a Blink object-lifetime bug, a parser memory-corruption), a privilege boundary crossing (an IPC handler that mishandles a renderer-controlled call), and a browser-process exploitation primitive. Project Zero's writeups, the Chrome Security blog's post-mortems, and the project's own `docs/security/mojo.md` all single out stateful initialization as the most common shape of the second link. A renderer compromise that finds a stateless interface dies at the boundary; one that finds a stateful interface walks through. Downstream Chromium-based products inherit the antipattern's surface area. A vendor that ships a Chromium fork, an Electron application with a custom IPC handler, or a WebView2 integration that exposes its own Mojo interfaces inherits the boundary along with the responsibility for defending it. A stateful handler the vendor wrote becomes a vendor-owned CVE. The 2025 enterprise-browser-vendor disclosures cited in [*Supply-Chain Vulnerability Lag*](supply-chain-lag.md) include cases where the vendor-introduced Mojo interface failed exactly this rule. ## The Way Out [*Stateless IPC Interface*](stateless-ipc-interface.md) is the direct corrective pattern. Every Mojo method between renderer and browser process carries, in the single message it sends, all data required to authorize and execute the call. The browser-side handler validates each message in isolation. No prior call's state is load-bearing on a security check. Three concrete refactoring moves convert a stateful interface to a stateless one. **Fold the `Init` arguments into the operational call.** Replace `Init(profile_id, origin)` followed by `LookUp(image_url)` with `LookUp(profile_id, origin, image_url)`. The browser-side handler reads the authority parameters from the request itself and cross-checks them against the renderer's `SiteInstance` identity on every call. The wire bytes per operation go up; the security gap closes. **Replace `Begin`/`Continue`/`Commit` triplets with a single self-contained method.** Most multi-call protocols collapse to one method when the author asks "does this operation logically need to be split across messages, or is the split a convenience?" Often the split is convenience. If the operation logically needs to stream data (uploading a large blob, decoding a media file, iterating over a long result set), pass the bytes through a side channel the browser already trusts: `mojo::DataPipe`, `mojom::BigBuffer`, or `base::ReadOnlySharedMemoryRegion`. Keep the *control* methods stateless. The trusted channel carries the data; the control surface stays self-validating. **Move multi-call state into a sandboxed utility process.** When the protocol genuinely cannot be flattened (a long-running compiler in V8, a media decoder, a font shaper), the project's standing answer is to host the multi-call state in a separate sandboxed utility process whose Mojo surface to the *browser* is itself stateless. The Rule of 2 (`docs/security/rule-of-2.md`) names this move as the standing response for any interface that would need to parse complex input in the browser process at C++ scale. The utility process holds the per-channel state internally; the browser-process interface to the utility process exchanges fully-formed control messages with no cross-call dependence. A refactor of an existing stateful interface follows a four-step sequence. Identify every member field on the implementation class whose value is populated by one method and consumed by another. Add the equivalent parameter to each consuming method's `mojom` definition. Rewrite the browser-side handler to read from the message rather than the member field. Delete the member field and the `Init`-shaped method. The Gerrit reviewer reads each `before`/`after` method pair against the [*Untrusted Renderer Axiom*](untrusted-renderer-axiom.md) and confirms the new version validates as if no prior call ran. ## How It Plays Out A team is adding a Mojo interface that lets a renderer request a server-side image proxy lookup. The first draft has `Init(profile_id)` followed by `LookUp(image_url)`; the browser-side handler stores `profile_id` on the implementation class. API-owner review rejects the draft against the antipattern. A compromised renderer can call `LookUp` without `Init` (member is null, handler crashes), or can call `Init(profile_id_a)` once and `LookUp` many times expecting the same profile to apply, when in fact the renderer can call `Init(profile_id_b)` between any two `LookUp` calls. The revised interface drops `Init` and changes `LookUp` to `LookUp(profile_id, image_url)`; the handler reads the profile from the message, cross-checks it against the renderer's `SiteInstance` on every call, and runs the lookup. The bug is closed at design time. The original draft's wire-cost objection was real; the security cost of paying it with statefulness was larger. A contributor reviewing an existing `WebTransport` Mojo interface notices that `SetReceiveBuffer(size)` and `ReceiveMessage()` share a buffer field on the channel. The contributor draws the call graph: a renderer can call `ReceiveMessage` immediately after a small `SetReceiveBuffer`, before the buffer is committed, and a race in the handler indexes into the prior-call buffer with the new size. Triage classifies the bug as High-severity. The fix folds the size into the receive call so the browser allocates a one-message buffer at the moment of read and the channel carries no buffer state across calls. The interface ships in the next milestone with the antipattern removed; the bug becomes the canonical exhibit cited in subsequent design reviews. The Chrome Security blog publishes the post-mortem with the antipattern named explicitly. A downstream enterprise-browser vendor's security audit finds a custom IPC the vendor added for telemetry: `BeginSession(user_id)`, `Report(event)`, `EndSession()`. The vendor's IPC followed the upstream Mojo conventions for surface shape but not for statelessness. An internal red-team exercise produced a renderer-compromise scenario in which the renderer called `Report` after `EndSession` was supposed to have torn down the session. The browser-side handler logged the renderer's event under the previous user's identity. The vendor refactors. `Report(user_id, event)` carries the session identity on every call, the handler verifies `user_id` against the renderer's authenticated frame state on each report, and `BeginSession`/`EndSession` are removed entirely. The session lifecycle is reconstructed on the browser side from the stream of reports. The vendor's audit write-up is shared with the upstream `docs/security/` team as a worked example of a downstream-vendor refactor away from stateful initialization. ## Notes for Agent Context Do not propose a Mojo interface whose `mojom` definition includes an `Init`, `Begin`, `Open`, or `Connect` method followed by operational methods that depend on the `Init` arguments. Refuse the shape and refactor into self-contained methods. Never store renderer-supplied data on the per-channel implementation object's member fields and consult it on a later call; every check runs on the current message's fields, and every authority parameter (origin, profile identifier, session identifier) is re-verified against the renderer's `SiteInstance` identity in the handler. When a logical operation appears to need multi-call structure (a streaming upload, a long-running compile, an iterator-shaped result set), route the data through `mojo::DataPipe`, `mojom::BigBuffer`, or `base::ReadOnlySharedMemoryRegion` and keep the control surface stateless; do not introduce a `SetX`/`UseX` split on the control interface. When refactoring an existing stateful interface, fold every member field whose value crosses calls into a parameter on each consuming `mojom` method, then delete the member field; a remaining cross-call member is a remaining bug. ## Sources The Chromium project's `docs/security/mojo.md` names statefulness as the standing failure mode of Mojo interface design and prescribes the stateless rule directly. The `docs/security/rule-of-2.md` document supplies the architectural form of the larger response: when an interface would require complex parsing or multi-step state in the browser process, push it into a sandboxed utility process whose interface back to the browser is itself stateless. The Chrome Security blog's running coverage of post-mortems names stateful initialization repeatedly as the proximate cause of browser-process memory-corruption bugs traced to renderer compromise. Project Zero's analyses of historical Chromium sandbox escapes (Ned Williamson's IPC-bug writeups and the V8-escape-chain documentation) single out the antipattern as the recurring middle link of the chain. The Mojo bindings reference under `mojo/public/cpp/bindings/README.md` is the operational source for what the bindings do and do not enforce; it documents that the system does not guarantee any particular method-call ordering, which is the substrate on which the antipattern's harm rests. ## Technical Drill-Down - [`docs/security/mojo.md`](https://chromium.googlesource.com/chromium/src/+/main/docs/security/mojo.md) — the project's canonical operational rule for Mojo interface authors; the stateless requirement appears in the opening section and is referenced throughout. - [`docs/security/rule-of-2.md`](https://chromium.googlesource.com/chromium/src/+/main/docs/security/rule-of-2.md) — the standing heuristic that pushes complex parsing into sandboxed utility processes; the structural answer when a stateful surface cannot be flattened. - [`mojo/public/cpp/bindings/README.md`](https://chromium.googlesource.com/chromium/src/+/main/mojo/public/cpp/bindings/README.md) — the Mojo C++ bindings reference; documents the call-ordering and lifetime guarantees the bindings do and do not provide. - [`base/numerics/safe_conversions.h`](https://chromium.googlesource.com/chromium/src/+/main/base/numerics/safe_conversions.h) — the checked-arithmetic library used in stateless handlers to re-validate every count, offset, and size on the current message rather than trusting a stored value. - [`content/browser/`](https://chromium.googlesource.com/chromium/src/+/main/content/browser/) — the directory that hosts browser-side Mojo interface implementations; the in-tree examples of refactored stateless interfaces live under its subdirectories. - [Chrome Security blog](https://security.googleblog.com/) — the public-facing series in which post-mortems of stateful-IPC bugs and the underlying review rules are explained for an outside audience. --- - [Next: Security Response and Vulnerability Classes](security-response.md) - [Previous: Untrusted Renderer Axiom](untrusted-renderer-axiom.md)