freepdfeditor/spike/E_sandbox/Sandbox.cpp

92 lines
3.0 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
//
// Sandbox.cpp — seccomp-bpf filter for the sandboxed document process.
// Allow-list: default-deny, permit only memory/thread-sync/exit and the
// already-open IPC socket fds. DENIED: socket, connect, open/openat, unlink,
// fork, exec.
#include "Sandbox.h"
#include <seccomp.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
namespace freepdfeditor::spike::e {
namespace {
// Add a syscall to the allow-list with the given argument restrictions (or
// none for a full allow). Returns 0 on success, negative on failure.
int allow_syscall(scmp_filter_ctx ctx, int nr)
{
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, nr, 0) < 0) return -1;
return 0;
}
} // namespace
int install_sandbox()
{
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL_PROCESS);
if (!ctx) {
std::fprintf(stderr, "seccomp_init failed\n");
return -ENOMEM;
}
// Memory management.
allow_syscall(ctx, SCMP_SYS(mmap));
allow_syscall(ctx, SCMP_SYS(munmap));
allow_syscall(ctx, SCMP_SYS(mprotect));
allow_syscall(ctx, SCMP_SYS(mremap));
allow_syscall(ctx, SCMP_SYS(brk));
allow_syscall(ctx, SCMP_SYS(madvise));
// Thread synchronisation and futex.
allow_syscall(ctx, SCMP_SYS(futex));
allow_syscall(ctx, SCMP_SYS(set_robust_list));
// The IPC channel: read/write/close on already-open fds. We do NOT allow
// open/openat/socket/connect — the document process works only with fds
// the UI process handed it.
allow_syscall(ctx, SCMP_SYS(read));
allow_syscall(ctx, SCMP_SYS(write));
allow_syscall(ctx, SCMP_SYS(close));
allow_syscall(ctx, SCMP_SYS(readv));
allow_syscall(ctx, SCMP_SYS(writev));
// poll/ppoll/epoll are needed by Cap'n Proto's event loop.
allow_syscall(ctx, SCMP_SYS(poll));
allow_syscall(ctx, SCMP_SYS(ppoll));
allow_syscall(ctx, SCMP_SYS(epoll_create1));
allow_syscall(ctx, SCMP_SYS(epoll_ctl));
allow_syscall(ctx, SCMP_SYS(epoll_wait));
allow_syscall(ctx, SCMP_SYS(eventfd2));
allow_syscall(ctx, SCMP_SYS(timerfd_create));
allow_syscall(ctx, SCMP_SYS(timerfd_settime));
allow_syscall(ctx, SCMP_SYS(dup)); // Cap'n Proto may dup its socket
// Exit / signal.
allow_syscall(ctx, SCMP_SYS(exit));
allow_syscall(ctx, SCMP_SYS(exit_group));
allow_syscall(ctx, SCMP_SYS(rt_sigprocmask));
allow_syscall(ctx, SCMP_SYS(rt_sigaction));
allow_syscall(ctx, SCMP_SYS(rt_sigreturn));
allow_syscall(ctx, SCMP_SYS(sigaltstack));
// Getters for things Cap'n Proto's event loop may query.
allow_syscall(ctx, SCMP_SYS(getpid));
allow_syscall(ctx, SCMP_SYS(gettid));
int rc = seccomp_load(ctx);
seccomp_release(ctx);
if (rc < 0) {
std::fprintf(stderr, "seccomp_load failed: %s\n", std::strerror(errno));
return -errno;
}
return 0;
}
} // namespace freepdfeditor::spike::e