// 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 #include #include #include #include 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 // ioctl(FIONBIO) is used by KJ's wrapSocketFd to set non-blocking mode on // the socketpair end. Allow the syscall; FIONBIO is the only request the // event loop issues on the IPC fd (no TCGETS/SG_ISPTTY etc. on a socket). allow_syscall(ctx, SCMP_SYS(ioctl)); // 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