Skip to content

Commit 32bcd7c

Browse files
committed
fix(release): bind privilege broker startup profile
1 parent 263eee6 commit 32bcd7c

3 files changed

Lines changed: 127 additions & 28 deletions

File tree

‎scripts/contained-image-authority.mjs‎

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -806,23 +806,81 @@ function requestedTmpfs(args, end) {
806806
.slice(separator + 1)
807807
.split(",")
808808
.sort();
809+
const exactOptions = {
810+
"/tmp": [
811+
"rw",
812+
"noexec",
813+
"nosuid",
814+
"nodev",
815+
"size=16m",
816+
"uid=65532",
817+
"gid=65532",
818+
"mode=0700",
819+
],
820+
"/work/jobs": [
821+
"rw",
822+
"noexec",
823+
"nosuid",
824+
"nodev",
825+
"size=64m",
826+
"uid=10001",
827+
"gid=10002",
828+
"mode=2710",
829+
],
830+
"/run/counterlab-codex": [
831+
"rw",
832+
"noexec",
833+
"nosuid",
834+
"nodev",
835+
"size=32m",
836+
"uid=0",
837+
"gid=10002",
838+
"mode=0710",
839+
],
840+
"/run/counterlab-privsep": [
841+
"rw",
842+
"noexec",
843+
"nosuid",
844+
"nodev",
845+
"size=4m",
846+
"uid=0",
847+
"gid=10001",
848+
"mode=0750",
849+
],
850+
};
851+
const expected = exactOptions[destination];
852+
const runtimeRootOptions =
853+
destination === "/counterlab-runtime" &&
854+
options.length === 8 &&
855+
options.includes("rw") &&
856+
options.includes("noexec") &&
857+
options.includes("nosuid") &&
858+
options.includes("nodev") &&
859+
options.some((entry) => /^size=(?:64|256)m$/.test(entry)) &&
860+
options.some((entry) => /^uid=\d{1,6}$/.test(entry)) &&
861+
options.some((entry) => /^gid=\d{1,6}$/.test(entry)) &&
862+
options.includes("mode=0700");
809863
if (
810-
!["/tmp", "/counterlab-runtime"].includes(destination) ||
811-
!options.includes("rw") ||
812-
!options.includes("noexec") ||
813-
!options.includes("nosuid") ||
814-
!options.includes("nodev") ||
815-
!options.some((entry) => /^size=(?:16|64|256)m$/.test(entry)) ||
816-
!options.some((entry) => /^uid=\d{1,6}$/.test(entry)) ||
817-
!options.some((entry) => /^gid=\d{1,6}$/.test(entry)) ||
818-
!options.includes("mode=0700")
864+
new Set(options).size !== options.length ||
865+
(!runtimeRootOptions &&
866+
(expected === undefined ||
867+
JSON.stringify(options) !== JSON.stringify([...expected].sort())))
819868
) {
820869
throw new Error("contained image authority tmpfs policy is invalid");
821870
}
822871
return { destination, options };
823872
});
824-
if (entries.length !== 1) {
825-
throw new Error("contained image authority requires one tmpfs");
873+
const destinations = entries.map((entry) => entry.destination).sort();
874+
const singleRoot =
875+
destinations.length === 1 &&
876+
["/tmp", "/counterlab-runtime"].includes(destinations[0]);
877+
const brokerRoots =
878+
JSON.stringify(destinations) ===
879+
JSON.stringify(
880+
["/work/jobs", "/run/counterlab-codex", "/run/counterlab-privsep"].sort(),
881+
);
882+
if (!singleRoot && !brokerRoots) {
883+
throw new Error("contained image authority tmpfs set is invalid");
826884
}
827885
return entries;
828886
}

‎scripts/contained-runtime.test.ts‎

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ function startupCommand(): string[] {
9999
"none",
100100
"--read-only",
101101
"--cap-drop=ALL",
102+
"--cap-add=CHOWN",
103+
"--cap-add=FOWNER",
104+
"--cap-add=SETGID",
105+
"--cap-add=SETUID",
102106
"--security-opt=no-new-privileges=true",
103107
"--ipc=private",
104108
"--pids-limit=32",
@@ -110,15 +114,13 @@ function startupCommand(): string[] {
110114
"--ulimit=fsize=1048576:1048576",
111115
"--ulimit=nofile=64:64",
112116
"--tmpfs",
113-
"/counterlab-runtime:rw,noexec,nosuid,nodev,size=64m,uid=10001,gid=10001,mode=0700",
114-
"-e",
115-
"TMPDIR=/counterlab-runtime",
117+
"/work/jobs:rw,noexec,nosuid,nodev,size=64m,uid=10001,gid=10002,mode=2710",
118+
"--tmpfs",
119+
"/run/counterlab-codex:rw,noexec,nosuid,nodev,size=32m,uid=0,gid=10002,mode=0710",
120+
"--tmpfs",
121+
"/run/counterlab-privsep:rw,noexec,nosuid,nodev,size=4m,uid=0,gid=10001,mode=0750",
116122
"-e",
117123
"COUNTERLAB_RUNNER_STARTUP_PROBE=1",
118-
"-e",
119-
"COUNTERLAB_RUNNER_WORK_ROOT=/counterlab-runtime/jobs",
120-
"-e",
121-
"COUNTERLAB_CODEX_HOME_ROOT=/counterlab-runtime/codex",
122124
image,
123125
];
124126
}
@@ -1574,6 +1576,37 @@ describe("contained runtime command policy", () => {
15741576
expect(validate(...boundedAdapterCommand()).status).toBe(0);
15751577
});
15761578

1579+
it("rejects startup probes with altered broker capabilities or writable roots", () => {
1580+
const missingCapability = startupCommand().filter(
1581+
(argument) => argument !== "--cap-add=SETUID",
1582+
);
1583+
const extraCapability = startupCommand();
1584+
extraCapability.splice(
1585+
extraCapability.indexOf(image),
1586+
0,
1587+
"--cap-add=SYS_ADMIN",
1588+
);
1589+
const alteredWorkspace = startupCommand().map((argument) =>
1590+
argument.startsWith("/work/jobs:")
1591+
? argument.replace("gid=10002", "gid=10001")
1592+
: argument,
1593+
);
1594+
const legacyRuntimeRoot = startupCommand().map((argument) =>
1595+
argument.startsWith("/work/jobs:")
1596+
? "/counterlab-runtime:rw,noexec,nosuid,nodev,size=64m,uid=10001,gid=10001,mode=0700"
1597+
: argument,
1598+
);
1599+
1600+
for (const command of [
1601+
missingCapability,
1602+
extraCapability,
1603+
alteredWorkspace,
1604+
legacyRuntimeRoot,
1605+
]) {
1606+
expect(validate(...command).status).not.toBe(0);
1607+
}
1608+
});
1609+
15771610
it("binds every runner profile to the hosted image address-space limit", () => {
15781611
const sessionRoot = resolve(root, ".rt/rt-validator-image-role-limits");
15791612
const installRoot = resolve(

‎scripts/validate-contained-runtime-command.mjs‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ function validateRun(runArgs) {
227227
"--read-only",
228228
"--pull=never",
229229
"--cap-drop=ALL",
230+
"--cap-add=CHOWN",
231+
"--cap-add=FOWNER",
232+
"--cap-add=SETGID",
233+
"--cap-add=SETUID",
230234
"--ipc=private",
231235
]);
232236

@@ -271,7 +275,9 @@ function validateRun(runArgs) {
271275
if (option === "-e" || option === "--env") environment.push(value);
272276
if (
273277
option === "--tmpfs" &&
274-
!/^\/(?:tmp|counterlab-runtime):/.test(value)
278+
!/^\/(?:tmp|counterlab-runtime|work\/jobs|run\/counterlab-(?:codex|privsep)):/.test(
279+
value,
280+
)
275281
) {
276282
fail("tmpfs destination is not approved");
277283
}
@@ -316,19 +322,17 @@ function validateRun(runArgs) {
316322
"--memory-swap": 1,
317323
"--cpus": 1,
318324
"--ulimit": 4,
319-
"--tmpfs": 1,
320-
"--env": 4,
325+
"--tmpfs": 3,
326+
"--env": 1,
321327
});
322328
if (
323329
command.length !== 0 ||
324-
!sameValues(environment, [
325-
"TMPDIR=/counterlab-runtime",
326-
"COUNTERLAB_RUNNER_STARTUP_PROBE=1",
327-
"COUNTERLAB_RUNNER_WORK_ROOT=/counterlab-runtime/jobs",
328-
"COUNTERLAB_CODEX_HOME_ROOT=/counterlab-runtime/codex",
330+
!sameValues(environment, ["COUNTERLAB_RUNNER_STARTUP_PROBE=1"]) ||
331+
!sameValues(options.get("--tmpfs") ?? [], [
332+
"/work/jobs:rw,noexec,nosuid,nodev,size=64m,uid=10001,gid=10002,mode=2710",
333+
"/run/counterlab-codex:rw,noexec,nosuid,nodev,size=32m,uid=0,gid=10002,mode=0710",
334+
"/run/counterlab-privsep:rw,noexec,nosuid,nodev,size=4m,uid=0,gid=10001,mode=0750",
329335
]) ||
330-
options.get("--tmpfs")?.[0] !==
331-
"/counterlab-runtime:rw,noexec,nosuid,nodev,size=64m,uid=10001,gid=10001,mode=0700" ||
332336
options.get("--security-opt")?.[0] !== "no-new-privileges=true" ||
333337
options.get("--pids-limit")?.[0] !== "32" ||
334338
options.get("--memory")?.[0] !== "4096m" ||
@@ -345,6 +349,10 @@ function validateRun(runArgs) {
345349
"--read-only",
346350
"--pull=never",
347351
"--cap-drop=ALL",
352+
"--cap-add=CHOWN",
353+
"--cap-add=FOWNER",
354+
"--cap-add=SETGID",
355+
"--cap-add=SETUID",
348356
"--ipc=private",
349357
])
350358
) {

0 commit comments

Comments
 (0)