bookmarkfs/src/sandbox.c
CismonX 35d4a93a41
backend_firefox: fix sandbox
- Allow fdatasync(), since it is used by SQLite when commiting.
- Move `PRAGMA quick_check` to backend_create(), since it sometimes
  calls stat() and cannot be sandboxed.
2025-01-25 21:15:57 +08:00

417 lines
12 KiB
C

/**
* bookmarkfs/src/sandbox.c
* ----
*
* Copyright (C) 2024 CismonX <admin@cismon.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sandbox.h"
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#if !defined(BOOKMARKFS_SANDBOX)
#elif defined(__linux__)
# ifdef BOOKMARKFS_SANDBOX_LANDLOCK
# include <linux/landlock.h>
# include <sys/syscall.h>
# endif
# include <sys/mman.h>
# include <sys/prctl.h>
# include <seccomp.h>
# define SANDBOX_IMPL_SECCOMP_LANDLOCK
#elif defined(__FreeBSD__)
# include <sys/capsicum.h>
# include <sys/procctl.h>
# 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) */