/** * bookmarkfs/src/sandbox.c * ---- * * Copyright (C) 2024 CismonX * * This file is part of BookmarkFS. * * BookmarkFS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * BookmarkFS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with BookmarkFS. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "sandbox.h" #include #include #include #include #if !defined(BOOKMARKFS_SANDBOX) #elif defined(__linux__) # ifdef BOOKMARKFS_SANDBOX_LANDLOCK # include # include # endif # include # include # include # define SANDBOX_IMPL_SECCOMP_LANDLOCK #elif defined(__FreeBSD__) # include # include # define SANDBOX_IMPL_CAPSICUM #else # error "sandbox not implemented on this platform" #endif #include "macros.h" #include "xstd.h" #ifdef SANDBOX_IMPL_SECCOMP_LANDLOCK #define ARG_CMP_(...) (struct scmp_arg_cmp []) { __VA_ARGS__ } #define SCMP_RULE(sys, prio, ...) \ { \ .syscall_num = SCMP_SYS(sys), \ .priority = prio, \ .argc = sizeof(ARG_CMP_(__VA_ARGS__)) / sizeof(struct scmp_arg_cmp), \ .argv = ARG_CMP_(__VA_ARGS__), \ } #define SCMP_RULE_NOARG(sys, prio) \ { \ .syscall_num = SCMP_SYS(sys), \ .priority = prio, \ } #define SCMP_RULES(rules) rules, sizeof(rules) / sizeof(struct scmp_rule_def) struct scmp_rule_def { int syscall_num; int argc; struct scmp_arg_cmp const *argv; uint8_t priority; }; // Forward declaration start static int add_scmp_rules (scmp_filter_ctx, struct scmp_rule_def const *, size_t); #ifdef BOOKMARKFS_SANDBOX_LANDLOCK static int landlock_create_ruleset (struct landlock_ruleset_attr const *, size_t, uint32_t); static int landlock_add_rule (int, enum landlock_rule_type, void const *, uint32_t); static int landlock_restrict_self (int, uint32_t); #endif /* defined(BOOKMARKFS_SANDBOX_LANDLOCK) */ // Forward declaration end static int add_scmp_rules ( scmp_filter_ctx sfctx, struct scmp_rule_def const *rules, size_t rules_cnt ) { for (size_t idx = 0; idx < rules_cnt; ++idx) { struct scmp_rule_def const *rule = rules + idx; int status = seccomp_rule_add_array(sfctx, SCMP_ACT_ALLOW, rule->syscall_num, rule->argc, rule->argv); if (status < 0) { log_printf("seccomp_rule_add(): %s", xstrerror(-status)); return status; } status = seccomp_syscall_priority(sfctx, rule->syscall_num, rule->priority); if (status < 0) { log_printf("seccomp_syscall_priority(): %s", xstrerror(-status)); return status; } } return 0; } #ifdef BOOKMARKFS_SANDBOX_LANDLOCK static int landlock_create_ruleset ( struct landlock_ruleset_attr const *attr, size_t attr_size, uint32_t flags ) { return syscall(SYS_landlock_create_ruleset, attr, attr_size, flags); } static int landlock_add_rule ( int ruleset_fd, enum landlock_rule_type rule_type, void const *rule_attr, uint32_t flags ) { return syscall(SYS_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags); } static int landlock_restrict_self ( int ruleset_fd, uint32_t flags ) { return syscall(SYS_landlock_restrict_self, ruleset_fd, flags); } #endif /* defined(BOOKMARKFS_SANDBOX_LANDLOCK) */ int sandbox_enter ( int dirfd, uint32_t flags ) { if (flags & SANDBOX_NOOP) { return 0; } if (unlikely(0 != prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))) { log_printf("prctl(): %s", xstrerror(errno)); return -1; } scmp_filter_ctx sfctx = seccomp_init(SCMP_ACT_ERRNO(EPERM)); xassert(sfctx != NULL); int status = seccomp_attr_set(sfctx, SCMP_FLTATR_CTL_NNP, 0); if (unlikely(status < 0)) { log_printf("seccomp_attr_set(): %s", xstrerror(-status)); goto free_sfctx; } status = -1; pid_t pid = getpid(); struct scmp_rule_def const rules_common[] = { // exit SCMP_RULE_NOARG(exit, 0), SCMP_RULE_NOARG(exit_group, 0), // signals SCMP_RULE_NOARG(sigaction, 10), SCMP_RULE_NOARG(sigprocmask, 10), SCMP_RULE_NOARG(sigreturn, 10), SCMP_RULE_NOARG(restart_syscall, 10), SCMP_RULE_NOARG(rt_sigaction, 10), SCMP_RULE_NOARG(rt_sigprocmask, 10), SCMP_RULE_NOARG(rt_sigreturn, 10), SCMP_RULE(kill, 0, SCMP_A0_32(SCMP_CMP_EQ, pid)), SCMP_RULE(tgkill, 0, SCMP_A0_32(SCMP_CMP_EQ, pid)), // read/write SCMP_RULE_NOARG(lseek, 190), SCMP_RULE_NOARG(pread64, 190), SCMP_RULE_NOARG(pwrite64, 190), SCMP_RULE_NOARG(read, 200), SCMP_RULE_NOARG(write, 200), SCMP_RULE_NOARG(writev, 200), // other file operations SCMP_RULE_NOARG(close, 20), SCMP_RULE_NOARG(fallocate, 30), SCMP_RULE_NOARG(fcntl, 100), SCMP_RULE_NOARG(fdatasync, 30), SCMP_RULE_NOARG(flock, 20), SCMP_RULE_NOARG(fstat, 100), SCMP_RULE_NOARG(fstat64, 100), SCMP_RULE_NOARG(fsync, 30), SCMP_RULE_NOARG(ftruncate, 30), SCMP_RULE_NOARG(getdents64, 100), SCMP_RULE_NOARG(ioctl, 30), // memory management SCMP_RULE_NOARG(brk, 20), SCMP_RULE_NOARG(madvise, 30), SCMP_RULE(mmap, 30, SCMP_A2_32(SCMP_CMP_MASKED_EQ, PROT_EXEC, 0)), SCMP_RULE(mmap2, 30, SCMP_A2_32(SCMP_CMP_MASKED_EQ, PROT_EXEC, 0)), SCMP_RULE(mprotect, 30, SCMP_A2_32(SCMP_CMP_MASKED_EQ, PROT_EXEC, 0)), SCMP_RULE_NOARG(mremap, 30), SCMP_RULE_NOARG(msync, 30), SCMP_RULE_NOARG(munmap, 30), // current process info SCMP_RULE_NOARG(geteuid, 10), SCMP_RULE_NOARG(geteuid32, 10), SCMP_RULE_NOARG(getegid, 10), SCMP_RULE_NOARG(getegid32, 10), SCMP_RULE_NOARG(getpid, 10), SCMP_RULE_NOARG(gettid, 10), // system info SCMP_RULE_NOARG(clock_gettime, 50), SCMP_RULE_NOARG(clock_gettime64, 50), // other utils SCMP_RULE_NOARG(futex, 40), SCMP_RULE_NOARG(getrandom, 40), SCMP_RULE_NOARG(poll, 40), SCMP_RULE_NOARG(pselect6, 40), SCMP_RULE_NOARG(pselect6_time64, 40), }; if (unlikely(0 != add_scmp_rules(sfctx, SCMP_RULES(rules_common)))) { goto free_sfctx; } if (dirfd < 0) { goto apply_seccomp; } struct scmp_rule_def const rules_dir[] = { SCMP_RULE(fanotify_mark, 40, SCMP_A3_32(SCMP_CMP_NE, AT_FDCWD)), SCMP_RULE(fstatat64, 100, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD)), SCMP_RULE(newfstatat, 100, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD)), SCMP_RULE(openat, 20, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD)), }; if (unlikely(0 != add_scmp_rules(sfctx, SCMP_RULES(rules_dir)))) { goto free_sfctx; } if (flags & SANDBOX_READONLY) { goto do_landlock; } struct scmp_rule_def const rules_dir_extra[] = { SCMP_RULE(renameat, 20, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD), SCMP_A2_32(SCMP_CMP_NE, AT_FDCWD)), }; if (unlikely(0 != add_scmp_rules(sfctx, SCMP_RULES(rules_dir_extra)))) { goto free_sfctx; } do_landlock: if (flags & SANDBOX_NO_LANDLOCK) { goto apply_seccomp; } status = -1; #ifdef BOOKMARKFS_SANDBOX_LANDLOCK int ruleset_version = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); if (unlikely(ruleset_version < 0)) { log_printf("landlock_create_ruleset(): %s", xstrerror(errno)); goto free_sfctx; } #define LANDLOCK_FS_RIGHT_NAME_(name) LANDLOCK_ACCESS_FS_##name #define LANDLOCK_FS_RIGHT(...) \ BITWISE_OR(LANDLOCK_FS_RIGHT_NAME_, __VA_ARGS__) uint64_t handled_access = LANDLOCK_FS_RIGHT(EXECUTE) | LANDLOCK_FS_RIGHT(WRITE_FILE, READ_FILE, READ_DIR, REMOVE_DIR) | LANDLOCK_FS_RIGHT(REMOVE_FILE, MAKE_CHAR, MAKE_DIR, MAKE_REG) | LANDLOCK_FS_RIGHT(MAKE_SOCK, MAKE_FIFO, MAKE_BLOCK, MAKE_SYM); switch (ruleset_version) { default: case 3: handled_access |= LANDLOCK_FS_RIGHT(TRUNCATE); // fallthrough case 2: handled_access |= LANDLOCK_FS_RIGHT(REFER); // fallthrough case 1: break; } struct landlock_ruleset_attr const ruleset_attr = { .handled_access_fs = handled_access, }; int lrfd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); if (unlikely(lrfd < 0)) { log_printf("landlock_create_ruleset(): %s", xstrerror(errno)); goto free_sfctx; } uint64_t allowed_access = LANDLOCK_FS_RIGHT(READ_FILE, READ_DIR); if (!(flags & SANDBOX_READONLY)) { allowed_access |= LANDLOCK_FS_RIGHT(WRITE_FILE, REMOVE_FILE, MAKE_REG); if (ruleset_version >= 3) { allowed_access |= LANDLOCK_FS_RIGHT(TRUNCATE); } } enum landlock_rule_type rule_type = LANDLOCK_RULE_PATH_BENEATH; struct landlock_path_beneath_attr const rule_attr = { .allowed_access = allowed_access, .parent_fd = dirfd, }; if (unlikely(0 != landlock_add_rule(lrfd, rule_type, &rule_attr, 0))) { log_printf("landlock_add_rule(): %s", xstrerror(errno)); goto free_ruleset; } if (unlikely(0 != landlock_restrict_self(lrfd, 0))) { log_printf("landlock_restrict_self(): %s", xstrerror(errno)); goto free_ruleset; } status = 0; free_ruleset: close(lrfd); #else log_printf("landlock is not supported on this build"); #endif /* defined(BOOKMARKFS_SANDBOX_LANDLOCK) */ if (status < 0) { goto free_sfctx; } apply_seccomp: status = seccomp_load(sfctx); if (unlikely(status != 0)) { log_printf("seccomp_load(): %s", xstrerror(-status)); } free_sfctx: seccomp_release(sfctx); return status; } #endif /* defined(SANDBOX_IMPL_SECCOMP_LANDLOCK) */ #if defined(SANDBOX_IMPL_CAPSICUM) int sandbox_enter ( int dirfd, uint32_t flags ) { if (flags & SANDBOX_NOOP) { return 0; } int val = PROC_NO_NEW_PRIVS_ENABLE; if (unlikely(0 != procctl(P_PID, getpid(), PROC_NO_NEW_PRIVS_CTL, &val))) { log_printf("procctl(): %s", xstrerror(errno)); return -1; } if (unlikely(0 != cap_enter())) { log_printf("cap_enter(): %s", xstrerror(errno)); return -1; } if (dirfd >= 0) { cap_rights_t rights; cap_rights_init(&rights, CAP_LOOKUP, CAP_READ, CAP_FSTAT, CAP_FLOCK, CAP_EVENT, CAP_IOCTL, CAP_MMAP_R); if (!(flags & SANDBOX_READONLY)) { cap_rights_set(&rights, CAP_CREATE, CAP_WRITE, CAP_FSYNC, CAP_FTRUNCATE, CAP_RENAMEAT_SOURCE, CAP_RENAMEAT_TARGET, CAP_UNLINKAT); } if (unlikely(0 != cap_rights_limit(dirfd, &rights))) { log_printf("cap_rights_limit(): %s", xstrerror(errno)); return -1; } } return 0; } #endif /* defined(SANDBOX_IMPL_CAPSICUM) */ #ifndef BOOKMARKFS_SANDBOX int sandbox_enter ( int UNUSED_VAR(fusefd), int UNUSED_VAR(dirfd), uint32_t flags ) { if (flags & SANDBOX_NOOP) { return 0; } log_puts("sandbox not implemented"); return -1; } #endif /* !defined(BOOKMARKFS_SANDBOX) */