15#include <sys/syscall.h>
19#if defined(__x86_64__)
20#define SYS_seccomp 317
21#elif defined(__aarch64__)
22#define SYS_seccomp 277
23#elif defined(__i386__)
24#define SYS_seccomp 354
26#define SYS_seccomp 383
29#ifndef SYS_landlock_create_ruleset
30#define SYS_landlock_create_ruleset 444
31#define SYS_landlock_add_rule 445
32#define SYS_landlock_restrict_self 446
35#if defined(__x86_64__)
36#define NATIVE_AUDIT_ARCH 0xc000003eU
37#elif defined(__aarch64__)
38#define NATIVE_AUDIT_ARCH 0xc00000b7U
39#elif defined(__i386__)
40#define NATIVE_AUDIT_ARCH 0x40000003U
42#define NATIVE_AUDIT_ARCH 0x40000028U
45#define PR_GET_SECCOMP 21
46#define PR_SET_NO_NEW_PRIVS 38
47#define PR_GET_NO_NEW_PRIVS 39
48#define SECCOMP_SET_MODE_FILTER 1
49#define SECCOMP_GET_ACTION_AVAIL 2
50#define SECCOMP_RET_KILL_PROCESS 0x80000000U
51#define SECCOMP_RET_ALLOW 0x7fff0000U
52#define SECCOMP_RET_ERRNO 0x00050000U
53#define LANDLOCK_CREATE_RULESET_VERSION 1
54#define LANDLOCK_RULE_PATH_BENEATH 1
55#define LANDLOCK_READ ((1ULL << 0) | (1ULL << 2) | (1ULL << 3))
56#define LANDLOCK_ABI3_ALL ((1ULL << 15) - 1)
58#define CHECK(expression) \
60 if (!(expression)) { \
61 fprintf(stderr, "SANDBOX-CONTRACT: FAIL line=%d %s errno=%d\n", __LINE__, #expression, \
67#define EXPECT(call, expected, error) \
70 long actualResult = (call); \
71 int actualError = errno; \
72 if (actualResult != (expected) || (actualResult == -1 && actualError != (error))) { \
73 fprintf(stderr, "SANDBOX-CONTRACT: FAIL line=%d %s result=%ld errno=%d expected=%ld/%d\n", \
74 __LINE__, #call, actualResult, actualError, (long)(expected), (error)); \
89 uint64_t handled_access_fs;
92 uint64_t allowed_access;
98 char inside[320], outside[320];
99 char allowed[384],
protected[384], created[384], renamed[384];
100 char inside_link[384], outside_link[384], outside_alias[384];
104 CHECK(strlen(root) <
sizeof(
fixture->root));
106 snprintf(
fixture->inside,
sizeof(
fixture->inside),
"%s/inside", root);
107 snprintf(
fixture->outside,
sizeof(
fixture->outside),
"%s/outside", root);
112 snprintf(
fixture->inside_link,
sizeof(
fixture->inside_link),
"%s/points-outside",
114 snprintf(
fixture->outside_link,
sizeof(
fixture->outside_link),
"%s/points-inside",
121 CHECK(mkdir(
fixture->inside, 0700) == 0);
122 CHECK(mkdir(
fixture->outside, 0700) == 0);
123 int fd = open(
fixture->protected, O_CREAT | O_EXCL | O_WRONLY, 0600);
125 EXPECT(write(fd,
"outside", 7), 7, 0);
126 EXPECT(close(fd), 0, 0);
127 fd = open(
fixture->allowed, O_CREAT | O_EXCL | O_WRONLY, 0600);
129 EXPECT(close(fd), 0, 0);
141 for (
size_t i = 0; i <
sizeof(files) /
sizeof(files[0]); ++i) {
142 if (unlink(files[i]) && errno != ENOENT) {
143 fprintf(stderr,
"SANDBOX-CONTRACT: cleanup %s errno=%d\n", files[i], errno);
148 for (
size_t i = 0; i <
sizeof(directories) /
sizeof(directories[0]); ++i) {
149 if (rmdir(directories[i]) && errno != ENOENT) {
150 fprintf(stderr,
"SANDBOX-CONTRACT: cleanup %s errno=%d\n", directories[i], errno);
157static int no_new_privileges(
void) {
158 EXPECT(syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 1UL, 0UL, 0UL, 0UL), -1, EINVAL);
159 EXPECT(syscall(SYS_prctl, PR_SET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL), -1, EINVAL);
160 EXPECT(syscall(SYS_prctl, PR_SET_NO_NEW_PRIVS, 2UL, 0UL, 0UL, 0UL), -1, EINVAL);
161 EXPECT(syscall(SYS_prctl, PR_SET_NO_NEW_PRIVS, 1UL, 1UL, 0UL, 0UL), -1, EINVAL);
162 EXPECT(syscall(SYS_prctl, PR_SET_NO_NEW_PRIVS, 1UL, 0UL, 0UL, 0UL), 0, 0);
163 EXPECT(syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL), 1, 0);
164 EXPECT(syscall(SYS_prctl, PR_SET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL), -1, EINVAL);
165 EXPECT(syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL), 1, 0);
169static int install_filter(
void) {
172 {0x15, 1, 0, NATIVE_AUDIT_ARCH},
173 {0x06, 0, 0, SECCOMP_RET_KILL_PROCESS},
175 {0x15, 0, 3, SYS_socket},
177 {0x15, 0, 1, AF_INET},
178 {0x06, 0, 0, SECCOMP_RET_ERRNO | EPERM},
179 {0x06, 0, 0, SECCOMP_RET_ALLOW},
181 const struct filter_program program = {
sizeof(instructions) /
sizeof(instructions[0]),
183 EXPECT(syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &program), 0, 0);
184 EXPECT(syscall(SYS_prctl, PR_GET_SECCOMP, 0UL, 0UL, 0UL, 0UL), 2, 0);
185 EXPECT(syscall(SYS_socket, AF_INET, SOCK_STREAM, 0), -1, EPERM);
189 EXPECT(syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &second), 0, 0);
190 EXPECT(syscall(SYS_socket, AF_INET, SOCK_STREAM, 0), -1, EPERM);
194static int filter_checks(
void) {
195 EXPECT(syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL), 1, 0);
196 EXPECT(syscall(SYS_prctl, PR_GET_SECCOMP, 0UL, 0UL, 0UL, 0UL), 2, 0);
197 EXPECT(syscall(SYS_socket, AF_INET, SOCK_STREAM, 0), -1, EPERM);
201static void* inherited_thread(
void* unused) {
203 return (
void*)(uintptr_t)filter_checks();
206static void* filtered_thread(
void* unused) {
208 int failed = no_new_privileges() || install_filter();
210 pthread_t descendant;
211 int error = pthread_create(&descendant, NULL, inherited_thread, NULL);
214 error = pthread_join(descendant, &result);
215 failed = result != NULL;
218 fprintf(stderr,
"SANDBOX-CONTRACT: descendant pthread error=%d\n", error);
222 return (
void*)(uintptr_t)failed;
225static int thread_isolation(
long privileges,
long mode) {
227 EXPECT(pthread_create(&
worker, NULL, filtered_thread, NULL), 0, 0);
229 EXPECT(pthread_join(
worker, &result), 0, 0);
230 CHECK(result == NULL);
231 EXPECT(syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL), privileges, 0);
232 EXPECT(syscall(SYS_prctl, PR_GET_SECCOMP, 0UL, 0UL, 0UL, 0UL), mode, 0);
233 int fd = syscall(SYS_socket, AF_INET, SOCK_STREAM, 0);
235 EXPECT(close(fd), 0, 0);
236 puts(
"SANDBOX-CONTRACT: pthread inheritance/isolation PASS");
240static int add_path(
int ruleset,
const char* path, uint64_t access) {
241 int directory = open(path, O_PATH | O_CLOEXEC);
242 CHECK(directory >= 0);
245 long result = syscall(SYS_landlock_add_rule, ruleset, LANDLOCK_RULE_PATH_BENEATH, &rule, 0);
249 fprintf(stderr,
"SANDBOX-CONTRACT: add-rule %s result=%ld errno=%d\n", path, result, saved);
257 long abi = syscall(SYS_landlock_create_ruleset, NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
260 int ruleset = syscall(SYS_landlock_create_ruleset, &attr,
sizeof(attr), 0);
262 int failed = add_path(ruleset,
"/", LANDLOCK_READ) ||
263 add_path(ruleset,
fixture->inside, LANDLOCK_ABI3_ALL);
266 long result = syscall(SYS_landlock_restrict_self, ruleset, 0);
268 fprintf(stderr,
"SANDBOX-CONTRACT: restrict-self result=%ld errno=%d\n", result, errno);
273 failed = add_path(ruleset,
"/", LANDLOCK_ABI3_ALL);
274 EXPECT(open(
fixture->protected, O_WRONLY), -1, EACCES);
275 EXPECT(syscall(SYS_landlock_restrict_self, ruleset, 0), 0, 0);
281static int confined_checks(
const struct fixture*
fixture,
int retained) {
282 CHECK(!filter_checks());
284 EXPECT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), 0, 0);
285 EXPECT(write(pair[0],
"s", 1), 1, 0);
287 EXPECT(read(pair[1], &
byte, 1), 1, 0);
289 EXPECT(close(pair[0]), 0, 0);
290 EXPECT(close(pair[1]), 0, 0);
292 EXPECT(open(
fixture->protected, O_WRONLY), -1, EACCES);
293 EXPECT(open(
fixture->protected, O_WRONLY | O_TRUNC), -1, EACCES);
294 EXPECT(open(
fixture->created, O_CREAT | O_EXCL | O_WRONLY, 0600), -1, EACCES);
295 EXPECT(truncate(
fixture->protected, 0), -1, EACCES);
297 EXPECT(open(
fixture->inside_link, O_WRONLY), -1, EACCES);
298 EXPECT(open(
fixture->outside_alias, O_WRONLY), -1, EACCES);
300 int fd = open(
fixture->protected, O_RDONLY);
303 EXPECT(read(fd, content,
sizeof(content)),
sizeof(content), 0);
304 CHECK(!memcmp(content,
"outside",
sizeof(content)));
305 EXPECT(close(fd), 0, 0);
306 fd = open(
fixture->allowed, O_CREAT | O_TRUNC | O_RDWR, 0600);
308 EXPECT(write(fd,
"allowed", 7), 7, 0);
309 EXPECT(ftruncate(fd, 4), 0, 0);
310 EXPECT(close(fd), 0, 0);
311 EXPECT(truncate(
fixture->allowed, 2), 0, 0);
312 fd = open(
fixture->outside_link, O_WRONLY);
314 EXPECT(write(fd,
"a", 1), 1, 0);
315 EXPECT(close(fd), 0, 0);
317 struct stat before, after;
318 EXPECT(fstat(retained, &before), 0, 0);
319 EXPECT(ftruncate(retained, before.st_size), 0, 0);
320 EXPECT(lseek(retained, 0, SEEK_END), before.st_size, 0);
321 EXPECT(write(retained,
"+", 1), 1, 0);
322 EXPECT(fstat(retained, &after), 0, 0);
323 CHECK(after.st_size == before.st_size + 1);
327static int reap(pid_t child) {
332 result = waitpid(child, &status, 0);
333 }
while (result < 0 && errno == EINTR);
334 CHECK(result == child);
335 if (!WIFEXITED(status) || WEXITSTATUS(status)) {
336 fprintf(stderr,
"SANDBOX-CONTRACT: child=%ld status=%d\n", (
long)child, status);
342static int restricted_child(
const struct fixture*
fixture,
const char* executable) {
344 int retained = open(
fixture->protected, O_RDWR);
345 CHECK(retained >= 0);
346 CHECK(!no_new_privileges());
347 CHECK(!install_filter());
348 CHECK(!install_domain(
fixture));
349 CHECK(!confined_checks(
fixture, retained));
350 pid_t child = fork();
353 if (confined_checks(
fixture, retained)) {
357 snprintf(descriptor,
sizeof(descriptor),
"%d", retained);
358 execl(executable, executable,
"--child",
fixture->root, descriptor, (
char*)NULL);
359 fprintf(stderr,
"SANDBOX-CONTRACT: exec %s errno=%d\n", executable, errno);
362 int failed = reap(child);
367static int parent_checks(
const struct fixture*
fixture,
long privileges,
long mode) {
368 EXPECT(syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL), privileges, 0);
369 EXPECT(syscall(SYS_prctl, PR_GET_SECCOMP, 0UL, 0UL, 0UL, 0UL), mode, 0);
370 int fd = syscall(SYS_socket, AF_INET, SOCK_STREAM, 0);
372 EXPECT(close(fd), 0, 0);
374 EXPECT(stat(
fixture->protected, &info), 0, 0);
375 CHECK(info.st_size == 10);
376 fd = open(
fixture->created, O_CREAT | O_EXCL | O_WRONLY, 0600);
378 EXPECT(write(fd,
"parent", 6), 6, 0);
379 EXPECT(close(fd), 0, 0);
380 EXPECT(truncate(
fixture->protected, 7), 0, 0);
386#define PROBE(name, call) \
389 long result = (call); \
390 printf("SANDBOX-PROBE: %s result=%ld errno=%d\n", name, result, errno); \
393static int probe(
void) {
394 PROBE(
"get-no-new-privs", syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL));
395 PROBE(
"get-seccomp", syscall(SYS_prctl, PR_GET_SECCOMP, 0UL, 0UL, 0UL, 0UL));
396 uint32_t action = SECCOMP_RET_ALLOW;
397 PROBE(
"seccomp-action-allow", syscall(SYS_seccomp, SECCOMP_GET_ACTION_AVAIL, 0, &action));
398 PROBE(
"landlock-abi",
399 syscall(SYS_landlock_create_ruleset, NULL, 0, LANDLOCK_CREATE_RULESET_VERSION));
400 PROBE(
"set-no-new-privs", syscall(SYS_prctl, PR_SET_NO_NEW_PRIVS, 1UL, 0UL, 0UL, 0UL));
403 PROBE(
"seccomp-install", syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &program));
406 int ruleset = syscall(SYS_landlock_create_ruleset, &attr,
sizeof(attr), 0);
407 printf(
"SANDBOX-PROBE: landlock-create result=%d errno=%d\n", ruleset, errno);
408 int root = open(
"/", O_PATH | O_CLOEXEC);
410 PROBE(
"landlock-add",
411 syscall(SYS_landlock_add_rule, ruleset, LANDLOCK_RULE_PATH_BENEATH, &rule, 0));
412 PROBE(
"landlock-restrict", syscall(SYS_landlock_restrict_self, ruleset, 0));
419 puts(
"SANDBOX-PROBE: DONE");
423static int command_denied(
long result,
int error,
const char* operation) {
424 if (result == -1 && (error == EACCES || error == EPERM || error == EROFS)) {
427 fprintf(stderr,
"CODEX-SANDBOX-COMMAND: FAIL %s result=%ld errno=%d\n", operation, result, error);
431static int command_check(
const char* directory,
const char*
protected,
int writable) {
432 EXPECT(syscall(SYS_socket, AF_INET, SOCK_STREAM, 0), -1, EPERM);
434 EXPECT(stat(
protected, &info), 0, 0);
435 CHECK(S_ISREG(info.st_mode));
436 int fd = open(
protected, O_RDONLY);
439 EXPECT(read(fd, &
byte, 1), 1, 0);
440 EXPECT(close(fd), 0, 0);
442 fd = open(
protected, O_WRONLY);
447 CHECK(!command_denied(fd, error,
"open protected file"));
449 long result = truncate(
protected, 0);
450 CHECK(!command_denied(result, errno,
"truncate protected file"));
454 snprintf(path,
sizeof(path),
"%s/codex-sandbox-command-%ld", directory, (
long)getpid());
455 CHECK(length > 0 && (
size_t)length <
sizeof(path));
457 fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0600);
459 CHECK(!command_denied(fd, errno,
"create file"));
460 puts(
"CODEX-SANDBOX-COMMAND: PASS");
465 result = write(fd,
"codex", 5);
467 int failed = result != 5;
469 fprintf(stderr,
"CODEX-SANDBOX-COMMAND: FAIL write result=%ld errno=%d\n", result, error);
471 failed |= close(fd) != 0;
472 failed |= unlink(path) != 0;
474 puts(
"CODEX-SANDBOX-COMMAND: PASS");
478int namespace_tests(
void);
479int namespace_network_test(
void);
481int main(
int argc,
char** argv) {
482 setvbuf(stdout, NULL, _IONBF, 0);
484 if (argc == 2 && !strcmp(argv[1],
"--network-test")) {
485 return namespace_network_test();
487 if (argc == 2 && !strcmp(argv[1],
"--namespaces-test")) {
488 return namespace_tests();
490 if (argc == 2 && !strcmp(argv[1],
"--probe")) {
493 if (argc == 4 && !strcmp(argv[1],
"--command-check")) {
494 return command_check(argv[2], argv[3], 1);
496 if (argc == 3 && !strcmp(argv[1],
"--read-only-check")) {
497 return command_check(
"/tmp", argv[2], 0);
500 if (argc == 4 && !strcmp(argv[1],
"--child")) {
503 long descriptor = strtol(argv[3], &end, 10);
504 CHECK(!*end && descriptor >= 0 && descriptor <= INT32_MAX);
505 return confined_checks(&
fixture, descriptor);
508 EXPECT(openat(AT_FDCWD,
"", O_RDONLY), -1, ENOENT);
509 char executable[4096];
510 if (!realpath(argv[0], executable)) {
511 ssize_t length = readlink(
"/proc/self/exe", executable,
sizeof(executable) - 1);
512 CHECK(length > 0 && (
size_t)length <
sizeof(executable) - 1);
513 executable[length] = 0;
515 long privileges = syscall(SYS_prctl, PR_GET_NO_NEW_PRIVS, 0UL, 0UL, 0UL, 0UL);
516 long mode = syscall(SYS_prctl, PR_GET_SECCOMP, 0UL, 0UL, 0UL, 0UL);
517 CHECK(privileges >= 0 && mode >= 0);
518 CHECK(!thread_isolation(privileges, mode));
520 snprintf(directory,
sizeof(directory),
"/tmp/sandbox-contract-%ld", (
long)getpid());
522 CHECK(mkdir(
fixture.root, 0700) == 0);
523 int failed = prepare(&
fixture);
525 pid_t child = fork();
527 _exit(restricted_child(&
fixture, executable));
529 failed = reap(child);
531 failed = parent_checks(&
fixture, privileges, mode);
535 puts(failed ?
"SANDBOX-CONTRACT: FAIL" :
"SANDBOX-CONTRACT: PASS");