fix: canonicalize the writeable paths used in seatbelt policy (#275)

closes #207

I'd be lying if I said I was familiar with these particulars more than a
couple hours ago, but after investigating and testing locally, this does
fix the go issue, I prefer it over #272 which is a lot of code and a one
off fix
---- 

cc @bolinfest do you mind taking a look here?

1. Seatbelt compares the paths it gets from the kernal to its policies
1. Go is attempting to write to the os.tmpdir, which we have
allowlisted.
1. The kernel rewrites /var/… to /private/var/… before the sandbox
check.
1. The policy still said /var/…, so writes were denied.

Fix: canonicalise every writable root we feed into the policy
(realpathSync(...)).
We do not have to touch runtime file paths—the kernel already
canonicalises those.



### before
see that the command exited 1, and that the command was reported to be
prohibited, despite using the allowlisted tmpdir


https://github.com/user-attachments/assets/23911101-0ec0-4a59-a0a1-423be04063f0


### after
command exits 0


https://github.com/user-attachments/assets/6ab2bcd6-68bd-4f89-82bb-2c8612e39ac3
This commit is contained in:
Jon Church
2025-04-18 02:01:15 -04:00
committed by GitHub
parent 49991bb85a
commit 3356ac0aef

View File

@@ -3,6 +3,7 @@ import type { SpawnOptions } from "child_process";
import { exec } from "./raw-exec.js";
import { log } from "../log.js";
import { realpathSync } from "fs";
import { CONFIG_DIR } from "src/utils/config.js";
function getCommonRoots() {
@@ -29,7 +30,9 @@ export function execWithSeatbelt(
const { policies, params } = writableRoots
.map((root, index) => ({
policy: `(subpath (param "WRITABLE_ROOT_${index}"))`,
param: `-DWRITABLE_ROOT_${index}=${root}`,
// the kernel resolves symlinks before handing them to seatbelt for checking
// so store the canonicalized form in the policy to be compared against
param: `-DWRITABLE_ROOT_${index}=${realpathSync(root)}`,
}))
.reduce(
(