Before you start#
Review processes and permissions, seccomp, Wayland buffers, and desktop portals. Chromium tag 129.0.6668.58 is pinned to commit 81a06fb873a9b386848719cf9f93e59579fb5d4b. The process IDs and messages below are synthetic; no browser or profile is opened.
Learning goals#
Distinguish browser, renderer and service roles; trace a pinned renderer launch and a syscall classification; reason about IPC, descriptor ownership and rendering; and design a falsifiable security-boundary investigation without executing web content.
Source walkthrough#
Read the text version
- Renderer: Processes page content under a policy → Broker or service: Validates requests and mediates access (requests services)
- Browser process: Coordinates privileged resources → Broker or service: Validates requests and mediates access (manages policy)
- Renderer: Processes page content under a policy → Display and GPU: Present composited output (submits rendering work)
A modern browser commonly separates the user interface and privileged coordination from processes that interpret web content. Chromium describes the browser process as managing UI and renderer processes, while renderer processes handle web content (Chromium multi-process architecture). Depending on browser version and configuration, renderer or content processes, a network service, GPU process and utility processes have distinct responsibilities. A renderer can parse and execute content controlled by a site, so a browser must treat messages from it as untrusted even if they follow a typed interface. Process assignment and site isolation are policy decisions; they are not a promise that every site always has exactly one dedicated process.
This exact excerpt is from Chromium 129 content/browser/renderer_host/render_process_host_impl.cc, in the renderer process initialization path. The file points to Chromium's BSD-style license. The surrounding delegate setup and launcher arguments are omitted:
child_process_launcher_ = std::make_unique<ChildProcessLauncher>(
std::move(sandbox_delegate), std::move(cmd_line), GetID(), this,
std::move(mojo_invitation_),
base::BindRepeating(&RenderProcessHostImpl::OnMojoError, id_),
std::move(file_data), metrics_memory_region_.Duplicate(),
std::move(tracing_config_memory_region));
channel_->Pause();
The constructor creates a ChildProcessLauncher, moves the sandbox delegate, command line and Mojo invitation into it, supplies an error callback and resource handles, and then pauses the channel. The immediately preceding source comments describe an asynchronous launch and a conditional zygote route. Moving those values transfers ownership to the launch machinery; an invitation or a queued message is not proof that the child started or a channel became usable. A launch failure or later renderer exit requires the host to resolve pending work and clean up its references. The exact behavior depends on platform and build flags.
The second excerpt is from Chromium 129 sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc, SyscallSets::IsDeniedFileSystemAccessViaFd, under the same BSD-style project license. Other architecture-specific cases and the default branch are omitted:
case __NR_fallocate:
case __NR_fchmod:
case __NR_fchown:
These cases belong to a classification function whose surrounding switch returns true for the listed syscall numbers. The comment says EPERM is a suitable errno for that set, but this function alone does not install a filter or prove the result of a particular process's syscall. Actual sandbox policy, architecture and caller determine how the classification is used. Treating a list of syscall names as a complete renderer security proof would ignore broker permissions, descriptors already granted, other IPC and kernel behavior.
Process roles and trust#
The browser process coordinates windows, profiles, navigation and permissions in a typical Chromium architecture. Renderers handle web documents, script execution and layout under a policy selected by the browser. A network service manages network operations through its interfaces; a GPU process and other utility processes can handle work that is isolated for robustness or security. The exact distribution of responsibilities changes between versions and can depend on flags and platform. Firefox has its own parent, content, GPU and utility-process architecture and uses IPDL for many typed IPC interfaces; those names and deployment choices must not be assumed identical to Chromium's Mojo implementation.
A zygote is a preinitialized process that can fork child processes under the browser's launch design. It is not a generic requirement that all Linux applications fork from a global system zygote. A child can inherit or receive a selected set of descriptors and other resources; avoiding unintended inheritance is part of the security and lifecycle design. The browser's launcher object tracks a process being started, while the actual child PID and its sandbox state become available through later operations. A successful allocation of a launcher object does not establish a running, fully sandboxed renderer.
An IPC interface can have typed messages, handles and validation rules. Type safety constrains representation but does not make the sender trustworthy or validate business authority. A compromised renderer might ask for a file it should not read, supply an invalid state transition or retain a handle after navigation. The receiving process must validate the request in the context of the origin, document, permission and object lifetime. A broker mediates selected privileged operations; its policy and argument validation are part of the boundary, not an automatic consequence of using IPC.
Linux sandbox mechanisms#
Linux browser sandboxes can combine user or other namespaces, seccomp filters, brokered filesystem operations, reduced credentials and host LSM policy. Which layers are enabled depends on the browser build, distribution, kernel configuration and environment. A seccomp filter constrains system calls according to its configured action; it does not by itself decide all web-origin permissions or revoke every file descriptor already received (Linux seccomp(2)). A user namespace changes the scope of identities and capabilities; a process can have UID 0 and capabilities inside it while remaining unprivileged outside (Linux user_namespaces(7)). A denied namespace setup or unavailable broker must be handled as a failure or a documented alternative, not silently described as equivalent protection.
In sandbox/linux/services/credentials.cc, Chromium 129, BSD-style license per the file header (the other functions are omitted):
bool Credentials::SetGidAndUidMaps(gid_t gid, uid_t uid) {
const char kGidMapFile[] = "/proc/self/gid_map";
const char kUidMapFile[] = "/proc/self/uid_map";
if (NamespaceUtils::KernelSupportsDenySetgroups() &&
!NamespaceUtils::DenySetgroups()) {
return false;
}
DCHECK(GetRESIds(NULL, NULL));
if (!NamespaceUtils::WriteToIdMapFile(kGidMapFile, gid) ||
!NamespaceUtils::WriteToIdMapFile(kUidMapFile, uid)) {
return false;
}
DCHECK(GetRESIds(NULL, NULL));
return true;
}
The function writes one GID entry and one UID entry into the process's /proc/self map files, and it returns false when its setgroups check fails before writing. The excerpt does not show which IDs the caller chooses or whether they match the host user, so it does not establish a safe mapping by itself.
A broker can open a permitted file on behalf of a less privileged process and pass an fd over a Unix socket; Linux AF_UNIX sockets support descriptor passing as ancillary data (Linux unix(7)). The receiver obtains a process-local descriptor reference to the underlying object. A descriptor can retain access after a path becomes invisible in a namespace, and the receiver should close it when finished. Passing an fd is an authority transfer, not merely sending a filename string. A browser permission decision, a portal grant, the broker's policy and host DAC/LSM checks are distinct. A file chooser granting one document does not automatically authorize every file in the user's home directory.
In the same credentials.cc, Chromium 129, BSD-style license per the file header (the surrounding functions are omitted):
bool Credentials::DropAllCapabilities(int proc_fd) {
if (!SetCapabilities(proc_fd, std::vector<Capability>())) {
return false;
}
CHECK(!HasAnyCapability());
return true;
}
This function sets an empty capability set through the proc_fd argument and then asserts with CHECK that no capability remains. It is a credential reduction, not a syscall filter and not a grant of any file. A failed CHECK aborts the process rather than returning an error to the caller.
The cited syscall set categorizes selected calls by number, including architecture-conditional cases. A specific installed filter may return an error, kill a process or use another action according to its policy. The source excerpt does not prove that the running browser uses a particular policy or that fchmod always returns the same result on every platform. A security counterexample is a renderer that cannot open an arbitrary path but has already been granted a writable fd by a more privileged component: path-open denial alone does not revoke that fd. The broker must grant only the intended resource and mode.
Synthetic IPC and descriptor timeline#
Assume a hypothetical browser process B has PID 100 and renderer R has PID 200. R asks B for access to a user-selected file using a typed request with document token D. B checks that D belongs to the active document and permission, obtains a read-only descriptor under its own authority and sends a duplicate reference through an IPC transport. R receives fd 7 in its process; B's descriptor might have a different integer. The values are synthetic, not captured browser messages. The IPC system and host kernel retain the object according to their own reference rules; both sides must close their own descriptors when finished.
The timeline below uses the same synthetic values. It is a teaching model of the request, not a captured Chromium session.
Synthetic teaching data, not a device capture.
- Renderer (PID 200)Browser (PID 100)
Request file access with document token D
- Browser (PID 100)Browser (PID 100)
Check D is active and permitted
- Browser (PID 100)File system
Open read-only under browser authority
- Browser (PID 100)Renderer (PID 200)
Return. Send fd over IPC; receiver gets its own number
- Browser (PID 100)Browser (PID 100)
If send fails: close local fd and fail request
Time runs downward. Dashed arrows show returns.
Read the text version
- Renderer (PID 200) → Browser (PID 100): Request file access with document token D
- Browser (PID 100) → Browser (PID 100): Check D is active and permitted
- Browser (PID 100) → File system: Open read-only under browser authority
- Browser (PID 100) → Renderer (PID 200) (return): Send fd over IPC; receiver gets its own number
- Browser (PID 100) → Browser (PID 100): If send fails: close local fd and fail request
Now suppose navigation destroys the document before the broker reply arrives. The browser must not attach a late response to an unrelated new document merely because a routing ID or PID was reused. The pending request needs an owner, cancellation or generation check, and a cleanup path for any fd produced after cancellation. If the broker opens the file but sending fails because R exited, B must close its local reference and resolve the pending request as failed. A process exit closes its descriptors but does not undo a write already performed through a granted writable fd.
Two competing hypotheses for a missing document result are: the permission was denied before open, or the renderer exited after the broker opened the file but before receiving the response. They predict different evidence at the broker and channel boundaries. A synthetic message ledger alone cannot choose between them. An authorized isolated test would use a self-authored temporary file, record request, permission outcome, fd transfer and child termination, then close all descriptors and delete only its own files. This chapter does not run a browser or access a real portal.
From a document to pixels#
A web engine constructs a document object model, applies styles, computes layout, paints or records drawing operations, rasterizes content and composes layers. Those steps have different inputs and can occur in different threads or processes. A GPU process may execute work via a graphics API and driver; software paths can also exist. A compositor integrates surfaces or buffers with the desktop display path. A Wayland buffer commit is not itself proof of visible scanout, and a browser frame can be delayed by a stalled renderer, unavailable GPU context, compositor scheduling or window visibility.
The stages below are conceptual. Where each one runs depends on the Chromium version, platform and flags, and nothing was rendered here.
- Style and layout
Computes boxes from the document inside the renderer.
- Paint and raster
Records drawing and rasterizes it, possibly on a GPU path.
- Compositor frame
Browser-side compositing combines surfaces from several sources.
- GPU and buffers
Shared buffers and synchronization handles carry work between processes.
- Wayland commit
A protocol request from the client, not an observation of scanout.
- Scanout
The compositor and driver decide; a commit does not prove it happened.
The exact handoff from a renderer to browser compositor, GPU service and Wayland client depends on Chromium version, platform and configuration. A DOM node is not a Wayland surface, and a GPU texture is not necessarily the final scanout buffer. Shared memory, dma-buf and synchronization handles can carry resources across boundaries, each with its own ownership and release conditions. A renderer that exits while a frame is pending requires cleanup of references and pending callbacks; a stale surface ID must not be confused with a newly created object. The previous Wayland and graphics chapters provide the protocol and driver background without asserting one universal browser pipeline.
Media and file portals can mediate access to selected devices or documents. Permission to capture a chosen source is scoped by the actual portal and user decision; it is not automatic permission to read every file, camera or microphone. A browser's site permission and a desktop portal permission can be separate checks. This lesson performs no screen, audio, camera, input or clipboard capture and does not start a browser.
Failure and evidence design#
A useful incident report records browser build, architecture, sandbox configuration, process role, request and response, relevant fd ownership and the first error. A renderer crash can result from malformed content, a failed IPC invariant, a graphics failure or an unrelated bug. A browser UI that remains alive does not prove the renderer survived; a renderer PID existing does not prove its document is responsive. Timestamps across process logs can be delayed or use different clocks, so ordering should be corroborated by protocol IDs and state transitions.
The source's asynchronous launcher and channel pause are useful starting points for a normal and failure path. If child launch fails before a channel is established, queued messages and moved invitation state need a cleanup path. If the child starts and later disconnects, the host must notify its dependents and release process and channel references. The precise Chromium cleanup sequence requires reading the matching branch and build conditions; this chapter does not assert that one excerpt exhaustively audits it. A source version is not proof of the installed browser binary's behavior.
A defensible test uses a prepared isolated environment and a self-authored static page or minimal IPC harness, with no personal profile, untrusted script or sandbox disabling. Compare a permitted temporary file with a denied sibling and observe both the broker decision and descriptor transfer. A negative control can attempt a request after the document owner is destroyed, expecting no access to be delivered to a new owner. Such a runtime test is a design only; the safe model below cannot establish browser security.
Separating service failure from site behavior#
Imagine two self-authored pages in an isolated test environment: one uses only static text, while the other requests access to a selected temporary file. If both fail before a renderer process starts, investigate launch and channel setup first. If the static page works but the file request is denied, the evidence instead points toward permission or broker behavior, subject to the test's actual logging and setup. A denial can be correct policy rather than a browser bug. Conversely, a permitted request delivered after its document is destroyed can indicate an ownership or authorization defect even when the IPC message is well formed. Neither result is measured here, and changing sandbox configuration to force a pass would invalidate the security comparison.
Practice#
This own-process model tracks a synthetic broker decision. It opens no file and starts no browser:
python3 - <<'PY'
allowed = {'document-D'}
for token in ('document-D', 'unselected-E'):
print(token, 'synthetic decision:', 'allow' if token in allowed else 'deny')
PY
The first token is allowed by this authored set and the second denied. This is not Chromium policy or a portal test. As a counterexample, adding the second token changes the result; it demonstrates that the model's authority depends on its inputs. If Python is unavailable, make the same two-row table by hand.
For a source-only alternative, use the B/R timeline and write the owner of the pending request and each fd before transfer, after transfer, and after renderer exit. Explain the late-navigation case and distinguish the syscall classification excerpt from an installed policy. Propose one observation that separates permission denial from channel failure. No profile, service, web page or device is accessed.
Knowledge check#
- Why does a typed IPC message still require authorization checks?
- What does the Chromium syscall-set excerpt establish, and what does it not establish?
- Why can a received fd retain authority when its pathname is unavailable?
- Why is a paused launcher channel not proof of a live renderer?
- Why does a committed browser buffer not by itself prove visible output?
Answers#
- A typed message can still request an unauthorized resource or refer to stale document state. The receiver must validate identity, lifetime and permission.
- It lists syscall cases in a classification function. It does not alone show that a particular sandbox installed a filter or how a specific call was handled.
- The fd is a reference to an already opened kernel object. Namespace path visibility does not automatically revoke it.
- Launch is asynchronous and can fail before the child and channel are usable. The launcher object and operating-system process have separate lifetimes.
- Compositing, GPU execution, compositor scheduling and display presentation are later or separate stages. A commit is not an observation of scanout.
Checkpoint#
Submit the process and fd ledger, two failure hypotheses and a falsifiable control. Pass when the IPC type is not confused with authorization, the sandbox classification is not called a complete policy, and the rendering stages remain separate. Revisit the cited launch and syscall paths if a PID is treated as a permanent document identity. This is not certification.