CVE-2026-74987
Overview
Background
- `SandboxBroker`
- A trusted broker process that mediates filesystem syscalls on behalf of sandboxed content processes on Linux.
- `realpath`/`SymlinkPath`
- The broker’s path-canonicalization routine that resolves
.,.., and symlinks so a requested path can be checked against the sandbox policy. - `readlink`
- A syscall that reads a symlink’s target string, which the broker calls during path resolution before enforcing permissions.
- `PROC_SUPER_MAGIC`
- The
statfsfilesystem-type identifier forprocfs, whose “magic” symlinks resolve to arbitrary kernel-managed targets.
Root Cause Analysis
During policy enforcement, SandboxBroker::SymlinkPath called readlink on a caller-controlled path to canonicalize it before deciding whether access was permitted. If that path pointed into /proc, the resolved entry could be a procfs “magic” symlink (for example /proc/self/fd/N or /proc/self/root), which the kernel resolves dynamically to a target the sandbox policy never intended to expose, letting the broker’s canonicalization be steered toward out-of-policy paths. The invariant violated is that path resolution inside the trusted broker must operate on ordinary filesystem symlinks whose textual target is stable and policy-checkable, not on kernel-synthesized procfs links.
The fix introduces SafeReadlink, which rejects any path beginning with /proc/, then opens the node with O_PATH | O_NOFOLLOW and calls fstatfs to confirm the object is not on a filesystem whose f_type equals PROC_SUPER_MAGIC before reading the link via readlinkat on the file descriptor itself. Operating on an O_PATH descriptor and re-reading through readlinkat(fd, "", ...) closes the resolution against a fixed object rather than re-walking the attacker-controlled path string. This works because both a fast string prefix check and an authoritative filesystem-type check must pass before any link target is trusted, denying procfs-backed symlinks entirely.
readlink on an attacker-supplied path without excluding procfs, whose magic symlinks resolve to arbitrary kernel targets; the fix gates resolution behind an explicit /proc/ prefix rejection plus an fstatfs/PROC_SUPER_MAGIC filesystem-type check on a stable O_PATH descriptor.Attack Path
- Compromise a content process An attacker gains code execution inside a sandboxed content/renderer process that can send filesystem broker requests.
- Craft a procfs-backed path
The attacker submits a path that resolves through a
procfs“magic” symlink such as/proc/self/fd/Nor/proc/self/root. - Subvert canonicalization
The broker’s
SymlinkPathcallsreadlinkon that entry, and the kernel returns a dynamically resolved target outside the intended policy scope. - Bypass policy checks Because the canonicalized path no longer reflects the caller’s textual request, the broker evaluates permissions against the wrong or attacker-influenced target.
- Reach out-of-policy resources The attacker leverages the mis-resolved path to access files the sandbox policy was meant to deny.
Impact Assessment
procfs magic symlinks to escape the intended filesystem policy, reaching files outside the sandbox’s allowed set. The precondition is prior code execution in a content process capable of issuing broker filesystem requests on Linux. The metadata classifies this as a high-severity memory-corruption-class defect with presumed exploitability, so the practical gain is a sandbox filesystem-policy escape from the constrained process context.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsecurity/sandbox/linux/broker/SandboxBrokerRealpath.cpp |
modified |
Files Changed
security/sandbox/linux/broker/SandboxBrokerRealpath.cpp
Audit Directions
- procfs magic symlinksAudit every trusted-process path resolver for
readlink/realpathon caller-controlled paths that could traverse/proc, and require both a prefix check and anfstatfsf_typecheck before trusting a link target. - Path re-resolution TOCTOUPrefer resolving against a stable
O_PATHdescriptor withreadlinkat(fd, "", ...)over re-walking a path string, so the object checked is the object read. - Broker policy boundariesReview other
SandboxBrokeroperations that canonicalize or stat paths to confirm they enforce filesystem-type restrictions consistently, not just this one call site.
Patch
diff --git a/security/sandbox/linux/broker/SandboxBrokerRealpath.cpp b/security/sandbox/linux/broker/SandboxBrokerRealpath.cpp
index b910264a70f..27656540844 100644
--- a/security/sandbox/linux/broker/SandboxBrokerRealpath.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerRealpath.cpp
@@ -36,15 +36,19 @@
static char sccsid[] = "@(#)realpath.c 8.1 (Berkeley) 2/16/94";
#endif /* LIBC_SCCS and not lint */
#include <errno.h>
+#include <fcntl.h>
+#include <linux/magic.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <sys/vfs.h>
#include <unistd.h>
#include "SandboxBroker.h"
#include "SandboxLogging.h"
#include "base/strings/string_util.h"
+#include "mozilla/UniquePtrExtensions.h"
// Original copy in, but not usable from here:
// toolkit/crashreporter/google-breakpad/src/common/linux/linux_libc_support.cc
@@ -60,6 +64,34 @@ static size_t my_strlcat(char* s1, const char* s2, size_t len) {
namespace mozilla {
+static ssize_t SafeReadlink(const char* __restrict path, char* __restrict buf,
+ size_t bufsiz) {
+ // In theory this is subsumed by the f_type check, but just in case
+ if (strncmp(path, "/proc/", 6) == 0) {
+ errno = EPERM;
+ return -1;
+ }
+
+ // Linux extension: open any filesystem node, including a symlink
+ UniqueFileHandle fd{open(path, O_PATH | O_NOFOLLOW)};
+ if (!fd) {
+ return -1;
+ }
+
+ // Check the filesystem info for the symlink (not its target)
+ struct statfs sf;
+ if (fstatfs(fd.get(), &sf) != 0) {
+ return -1;
+ }
+ if (sf.f_type == PROC_SUPER_MAGIC) {
+ errno = EPERM;
+ return -1;
+ }
+
+ // Linux extension: readlink the object referenced by the fd itself
+ return readlinkat(fd.get(), "", buf, bufsiz);
+}
+
/*
* Original: realpath
* Find the real name of path, by removing all ".", ".." and symlink
@@ -223,7 +255,7 @@ char* SandboxBroker::SymlinkPath(const Policy* policy,
*perms |= link_path_perms;
}
/* Original symlink lookup code */
- slen = readlink(resolved, symlink, sizeof(symlink) - 1);
+ slen = SafeReadlink(resolved, symlink, sizeof(symlink) - 1);
if (slen < 0) {
if (m) free(resolved);
return (nullptr);