CVE-2026-12311
Overview
Background
- `SandboxBroker`
- a trusted parent-process broker that services filesystem requests from sandboxed Linux child processes over a socket, applying a per-child access policy.
- `Operation`
- the enum tag on a broker request identifying the syscall being brokered (e.g.
SANDBOX_FILE_LINK,SANDBOX_FILE_RENAME), some of which involve two paths and most only one. - `FORCE_DENY` / `CRASH_INSTEAD`
- negative permission bits in the policy that cause a request to be refused (or the child to be crashed) rather than granting any access.
- `mPolicy->Lookup`
- the call that maps a resolved path string to its permission bitmask under the child’s sandbox policy.
Root Cause Analysis
In SandboxBroker::ThreadMain, a request carrying a non-empty second path had that path’s permissions computed via mPolicy->Lookup and combined with the first path’s permissions using a plain intersection, perms &= perms2. That intersection is correct for positive access bits (both paths must be permitted) but wrong for the negative bits FORCE_DENY and CRASH_INSTEAD, because AND-ing clears a deny bit whenever it is set on only one of the two paths, effectively cancelling a denial that should have stood. Additionally, the code processed a second path for any operation, even single-path operations that legitimately supply only one path, so a malicious child could attach an unexpected second path to influence the permission result.
The fix rejects extra paths for operations whose OperationPaths(req.mOp) is less than 2 by logging, shutting down the read side, and breaking, and it changes the combination to perms = (perms & perms2) | ((perms | perms2) & kNegPerms) so deny bits are unioned (kept if present on either path) while positive bits are still intersected. This restores the invariant that a denial on either path denies the whole operation and that only two-path operations may carry two paths.
kNegPerms) separately and refuses second paths on single-path operations.Attack Path
- Craft a two-path request
A compromised or malicious content process sends a brokered file request (e.g. rename/link, or a single-path op with an injected extra path) to the parent
SandboxBroker. - Pair a denied path with an allowed path
One path resolves to a policy entry carrying
FORCE_DENY/CRASH_INSTEADwhile the other resolves to an allowed entry. - Cancel the denial via intersection
The old
perms &= perms2AND-s the two masks, clearing the deny bit because it is set on only one path, yielding a net-permissive result. - Perform the brokered operation The broker executes the filesystem action on the path that policy intended to deny, disclosing or manipulating files outside the sandbox’s allowed set.
Impact Assessment
FORCE_DENY, which amounts to information disclosure and a sandbox escape from the child’s confinement. The action runs in the trusted parent broker context on behalf of the child, so it reaches files outside the child’s permitted set. The precondition is prior compromise (or malicious control) of a sandboxed process capable of issuing broker requests.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsecurity/sandbox/linux/broker/SandboxBroker.cpp |
modified | |
switchsecurity/sandbox/linux/broker/SandboxBrokerCommon.h |
modified |
Files Changed
security/sandbox/linux/broker/SandboxBroker.cppsecurity/sandbox/linux/broker/SandboxBrokerCommon.h
Audit Directions
- Negative permission bits under set-combinationAudit every place that merges permission masks (AND/OR) and confirm deny/crash bits are unioned, not intersected, so a denial on any input is never silently cleared.
- Per-operation input arityVerify broker request handlers validate the number and shape of attacker-controlled fields (extra paths, extra descriptors) against what each
Operationlegitimately expects, rejecting surplus data. - Multi-path privilege decisionsReview link/rename and other multi-argument brokered operations to ensure the access decision reflects the least-privileged of all involved paths.
Patch
diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp
index 886a815b507..d9843e3c9ea 100644
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -820,13 +820,21 @@ void SandboxBroker::ThreadMain(void) {
// Same for the second path.
pathLen2 = strnlen(pathBuf2, kMaxPathLen);
if (pathLen2 > 0) {
+ if (OperationPaths(req.mOp) < 2) {
+ SANDBOX_LOG("extra path for op %s from pid %d",
+ OperationDescription(req.mOp), mChildPid);
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
// Force 0 termination.
pathBuf2[pathLen2] = '\0';
pathLen2 = ConvertRelativePath(pathBuf2, sizeof(pathBuf2), pathLen2);
int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2));
- // Take the intersection of the permissions for both paths.
- perms &= perms2;
+ // Take the intersection of the permissions for both paths
+ // (the bits which cause denials need to be handled specially).
+ constexpr int kNegPerms = FORCE_DENY | CRASH_INSTEAD;
+ perms = (perms & perms2) | ((perms | perms2) & kNegPerms);
}
} else {
// Failed to receive intelligible paths.
diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.h b/security/sandbox/linux/broker/SandboxBrokerCommon.h
index 1bdae7a3c38..f9ab26a2578 100644
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.h
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -54,6 +54,16 @@ class SandboxBrokerCommon {
static_cast<unsigned>(SANDBOX_OP_MAX_VALUE);
}
+ static int OperationPaths(Operation aOp) {
+ switch (aOp) {
+ case SANDBOX_FILE_LINK:
+ case SANDBOX_FILE_RENAME:
+ return 2;
+ default:
+ return 1;
+ }
+ }
+
static unsigned OperationToInt(Operation);
static const char* OperationDescription(Operation);