// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "sandboxed_api/sandbox2/policybuilder.h" #include // For TCGETS #include // For the fcntl flags #include #include #include // For SYS_CONNECT #include // For GRND_NONBLOCK #include // For mmap arguments #include #include #include #include #include #include #include #include #include #include #include #include #include #include "absl/log/log.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" #include "sandboxed_api/config.h" #include "sandboxed_api/sandbox2/allow_all_syscalls.h" #include "sandboxed_api/sandbox2/allow_unrestricted_networking.h" #include "sandboxed_api/sandbox2/namespace.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/util/bpf_helper.h" #include "sandboxed_api/sandbox2/violation.pb.h" #include "sandboxed_api/util/path.h" #include "sandboxed_api/util/status_macros.h" #if defined(SAPI_X86_64) #include #elif defined(SAPI_PPC64_LE) #include // On PPC, TCGETS macro needs termios #endif #ifndef PR_SET_VMA #define PR_SET_VMA 0x53564d41 #endif #ifndef PR_SET_VMA_ANON_NAME #define PR_SET_VMA_ANON_NAME 0 #endif namespace sandbox2 { namespace { namespace file = ::sapi::file; constexpr std::array kMmapSyscalls = { #ifdef __NR_mmap2 __NR_mmap2, #endif #ifdef __NR_mmap __NR_mmap, #endif }; bool CheckBpfBounds(const sock_filter& filter, size_t max_jmp) { if (BPF_CLASS(filter.code) == BPF_JMP) { if (BPF_OP(filter.code) == BPF_JA) { return filter.k <= max_jmp; } return filter.jt <= max_jmp && filter.jf <= max_jmp; } return true; } bool IsOnReadOnlyDev(const std::string& path) { struct statvfs vfs; if (TEMP_FAILURE_RETRY(statvfs(path.c_str(), &vfs)) == -1) { PLOG(ERROR) << "Could not statvfs: " << path.c_str(); return false; } return vfs.f_flag & ST_RDONLY; } } // namespace PolicyBuilder& PolicyBuilder::Allow(UnrestrictedNetworking tag) { EnableNamespaces(); // NOLINT(clang-diagnostic-deprecated-declarations) allow_unrestricted_networking_ = true; return *this; } PolicyBuilder& PolicyBuilder::AllowSyscall(uint32_t num) { if (handled_syscalls_.insert(num).second) { user_policy_.insert(user_policy_.end(), {SYSCALL(num, ALLOW)}); } return *this; } PolicyBuilder& PolicyBuilder::AllowSyscalls(absl::Span nums) { for (auto num : nums) { AllowSyscall(num); } return *this; } PolicyBuilder& PolicyBuilder::BlockSyscallsWithErrno( absl::Span nums, int error) { for (auto num : nums) { BlockSyscallWithErrno(num, error); } return *this; } PolicyBuilder& PolicyBuilder::BlockSyscallWithErrno(uint32_t num, int error) { if (handled_syscalls_.insert(num).second) { user_policy_.insert(user_policy_.end(), {SYSCALL(num, ERRNO(error))}); if (num == __NR_bpf) { user_policy_handles_bpf_ = true; } if (num == __NR_ptrace) { user_policy_handles_ptrace_ = true; } } return *this; } PolicyBuilder& PolicyBuilder::OverridableBlockSyscallWithErrno(uint32_t num, int error) { overridable_policy_.insert(overridable_policy_.end(), {SYSCALL(num, ERRNO(error))}); return *this; } PolicyBuilder& PolicyBuilder::AllowEpollWait() { return AllowSyscalls({ #ifdef __NR_epoll_wait __NR_epoll_wait, #endif #ifdef __NR_epoll_pwait __NR_epoll_pwait, #endif #ifdef __NR_epoll_pwait2 __NR_epoll_pwait2, #endif }); } PolicyBuilder& PolicyBuilder::AllowEpoll() { AllowSyscalls({ #ifdef __NR_epoll_create __NR_epoll_create, #endif #ifdef __NR_epoll_create1 __NR_epoll_create1, #endif #ifdef __NR_epoll_ctl __NR_epoll_ctl, #endif }); return AllowEpollWait(); } PolicyBuilder& PolicyBuilder::AllowInotifyInit() { return AllowSyscalls({ #ifdef __NR_inotify_init __NR_inotify_init, #endif #ifdef __NR_inotify_init1 __NR_inotify_init1, #endif }); } PolicyBuilder& PolicyBuilder::AllowSelect() { return AllowSyscalls({ #ifdef __NR_select __NR_select, #endif #ifdef __NR_pselect6 __NR_pselect6, #endif }); } PolicyBuilder& PolicyBuilder::AllowExit() { return AllowSyscalls({__NR_exit, __NR_exit_group}); } PolicyBuilder& PolicyBuilder::AllowScudoMalloc() { AllowTime(); AllowSyscalls({__NR_munmap, __NR_nanosleep}); AllowFutexOp(FUTEX_WAKE); AllowLimitedMadvise(); AllowGetRandom(); AllowWipeOnFork(); return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { return { ARG_32(2), // prot JEQ32(PROT_NONE, JUMP(&labels, prot_none)), JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), // PROT_READ | PROT_WRITE ARG_32(3), // flags BPF_STMT(BPF_ALU | BPF_AND | BPF_K, ~uint32_t{MAP_FIXED | MAP_NORESERVE}), JEQ32(MAP_PRIVATE | MAP_ANONYMOUS, ALLOW), JUMP(&labels, mmap_end), // PROT_NONE LABEL(&labels, prot_none), ARG_32(3), // flags JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, ALLOW), LABEL(&labels, mmap_end), }; }); } PolicyBuilder& PolicyBuilder::AllowTcMalloc() { AllowTime(); AllowRestartableSequences(kRequireFastFences); AllowSyscalls( {__NR_munmap, __NR_nanosleep, __NR_brk, __NR_mincore, __NR_membarrier}); AllowLimitedMadvise(); AllowPrctlSetVma(); AllowPoll(); AddPolicyOnSyscall(__NR_mprotect, { ARG_32(2), JEQ32(PROT_READ | PROT_WRITE, ALLOW), JEQ32(PROT_NONE, ALLOW), }); return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { return { ARG_32(2), // prot JEQ32(PROT_NONE, JUMP(&labels, prot_none)), JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), // PROT_READ | PROT_WRITE ARG_32(3), // flags JNE32(MAP_ANONYMOUS | MAP_PRIVATE, JUMP(&labels, mmap_end)), ALLOW, // PROT_NONE LABEL(&labels, prot_none), ARG_32(3), // flags JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, ALLOW), JEQ32(MAP_ANONYMOUS | MAP_PRIVATE, ALLOW), LABEL(&labels, mmap_end), }; }); } PolicyBuilder& PolicyBuilder::AllowSystemMalloc() { AllowSyscalls({__NR_munmap, __NR_brk}); AllowFutexOp(FUTEX_WAKE); AddPolicyOnSyscall(__NR_mremap, { ARG_32(3), JEQ32(MREMAP_MAYMOVE, ALLOW), }); return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { return { ARG_32(2), // prot JEQ32(PROT_NONE, JUMP(&labels, prot_none)), JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), // PROT_READ | PROT_WRITE ARG_32(3), // flags JEQ32(MAP_ANONYMOUS | MAP_PRIVATE, ALLOW), // PROT_NONE LABEL(&labels, prot_none), ARG_32(3), // flags JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, ALLOW), LABEL(&labels, mmap_end), }; }); return *this; } PolicyBuilder& PolicyBuilder::AllowLlvmSanitizers() { if constexpr (sapi::sanitizers::IsAny()) { // *san use a custom allocator that runs mmap/unmap under the hood. For // example: // https://github.com/llvm/llvm-project/blob/596d534ac3524052df210be8d3c01a33b2260a42/compiler-rt/lib/asan/asan_allocator.cpp#L980 // https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/compiler-rt/lib/sanitizer_common/sanitizer_allocator_secondary.h#L98 AllowMmap(); AllowSyscall(__NR_munmap); // https://github.com/llvm/llvm-project/blob/4bbc3290a25c0dc26007912a96e0f77b2092ee56/compiler-rt/lib/sanitizer_common/sanitizer_stack_store.cpp#L293 AddPolicyOnSyscall(__NR_mprotect, { ARG_32(2), BPF_STMT(BPF_AND | BPF_ALU | BPF_K, ~uint32_t{PROT_READ | PROT_WRITE}), JEQ32(PROT_NONE, ALLOW), }); AddPolicyOnSyscall(__NR_madvise, { ARG_32(2), JEQ32(MADV_DONTDUMP, ALLOW), JEQ32(MADV_NOHUGEPAGE, ALLOW), }); // Sanitizers read from /proc. For example: // https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L1155 AddDirectoryIfNamespaced("/proc"); AllowOpen(); // Sanitizers need pid for reports. For example: // https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L740 AllowGetPIDs(); // Sanitizers may try color output. For example: // https://github.com/llvm/llvm-project/blob/87dd3d350c4ce0115b2cdf91d85ddd05ae2661aa/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp#L157 OverridableBlockSyscallWithErrno(__NR_ioctl, EPERM); // https://github.com/llvm/llvm-project/blob/9aa39481d9eb718e872993791547053a3c1f16d5/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp#L150 // https://sourceware.org/git/?p=glibc.git;a=blob;f=nptl/pthread_getattr_np.c;h=de7edfa0928224eb8375e2fe894d6677570fbb3b;hb=HEAD#l188 OverridableBlockSyscallWithErrno(__NR_sched_getaffinity, EPERM); // https://github.com/llvm/llvm-project/blob/02c2b472b510ff55679844c087b66e7837e13dc2/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L434 #ifdef __NR_readlink OverridableBlockSyscallWithErrno(__NR_readlink, ENOENT); #endif OverridableBlockSyscallWithErrno(__NR_readlinkat, ENOENT); } if constexpr (sapi::sanitizers::IsASan()) { AllowSyscall(__NR_sigaltstack); } return *this; } PolicyBuilder& PolicyBuilder::AllowLimitedMadvise() { return AddPolicyOnSyscall(__NR_madvise, { ARG_32(2), JEQ32(MADV_DONTNEED, ALLOW), JEQ32(MADV_REMOVE, ALLOW), JEQ32(MADV_HUGEPAGE, ALLOW), JEQ32(MADV_NOHUGEPAGE, ALLOW), }); } PolicyBuilder& PolicyBuilder::AllowMmap() { return AllowSyscalls(kMmapSyscalls); } PolicyBuilder& PolicyBuilder::AllowOpen() { #ifdef __NR_creat AllowSyscall(__NR_creat); #endif #ifdef __NR_open AllowSyscall(__NR_open); #endif #ifdef __NR_openat AllowSyscall(__NR_openat); #endif return *this; } PolicyBuilder& PolicyBuilder::AllowStat() { #ifdef __NR_fstat AllowSyscall(__NR_fstat); #endif #ifdef __NR_fstat64 AllowSyscall(__NR_fstat64); #endif #ifdef __NR_fstatat AllowSyscall(__NR_fstatat); #endif #ifdef __NR_fstatat64 AllowSyscall(__NR_fstatat64); #endif #ifdef __NR_fstatfs AllowSyscall(__NR_fstatfs); #endif #ifdef __NR_fstatfs64 AllowSyscall(__NR_fstatfs64); #endif #ifdef __NR_lstat AllowSyscall(__NR_lstat); #endif #ifdef __NR_lstat64 AllowSyscall(__NR_lstat64); #endif #ifdef __NR_newfstatat AllowSyscall(__NR_newfstatat); #endif #ifdef __NR_oldfstat AllowSyscall(__NR_oldfstat); #endif #ifdef __NR_oldlstat AllowSyscall(__NR_oldlstat); #endif #ifdef __NR_oldstat AllowSyscall(__NR_oldstat); #endif #ifdef __NR_stat AllowSyscall(__NR_stat); #endif #ifdef __NR_stat64 AllowSyscall(__NR_stat64); #endif #ifdef __NR_statfs AllowSyscall(__NR_statfs); #endif #ifdef __NR_statfs64 AllowSyscall(__NR_statfs64); #endif return *this; } PolicyBuilder& PolicyBuilder::AllowAccess() { #ifdef __NR_access AllowSyscall(__NR_access); #endif #ifdef __NR_faccessat AllowSyscall(__NR_faccessat); #endif #ifdef __NR_faccessat2 AllowSyscall(__NR_faccessat2); #endif return *this; } PolicyBuilder& PolicyBuilder::AllowDup() { AllowSyscall(__NR_dup); #ifdef __NR_dup2 AllowSyscall(__NR_dup2); #endif AllowSyscall(__NR_dup3); return *this; } PolicyBuilder& PolicyBuilder::AllowPipe() { #ifdef __NR_pipe AllowSyscall(__NR_pipe); #endif AllowSyscall(__NR_pipe2); return *this; } PolicyBuilder& PolicyBuilder::AllowChmod() { #ifdef __NR_chmod AllowSyscall(__NR_chmod); #endif AllowSyscall(__NR_fchmod); AllowSyscall(__NR_fchmodat); return *this; } PolicyBuilder& PolicyBuilder::AllowChown() { #ifdef __NR_chown AllowSyscall(__NR_chown); #endif #ifdef __NR_lchown AllowSyscall(__NR_lchown); #endif AllowSyscall(__NR_fchown); AllowSyscall(__NR_fchownat); return *this; } PolicyBuilder& PolicyBuilder::AllowRead() { return AllowSyscalls({ __NR_read, __NR_readv, __NR_preadv, __NR_pread64, }); } PolicyBuilder& PolicyBuilder::AllowWrite() { return AllowSyscalls({ __NR_write, __NR_writev, __NR_pwritev, __NR_pwrite64, }); } PolicyBuilder& PolicyBuilder::AllowReaddir() { return AllowSyscalls({ #ifdef __NR_getdents __NR_getdents, #endif #ifdef __NR_getdents64 __NR_getdents64, #endif }); } PolicyBuilder& PolicyBuilder::AllowReadlink() { return AllowSyscalls({ #ifdef __NR_readlink __NR_readlink, #endif #ifdef __NR_readlinkat __NR_readlinkat, #endif }); } PolicyBuilder& PolicyBuilder::AllowLink() { return AllowSyscalls({ #ifdef __NR_link __NR_link, #endif #ifdef __NR_linkat __NR_linkat, #endif }); } PolicyBuilder& PolicyBuilder::AllowSymlink() { return AllowSyscalls({ #ifdef __NR_symlink __NR_symlink, #endif #ifdef __NR_symlinkat __NR_symlinkat, #endif }); } PolicyBuilder& PolicyBuilder::AllowMkdir() { return AllowSyscalls({ #ifdef __NR_mkdir __NR_mkdir, #endif #ifdef __NR_mkdirat __NR_mkdirat, #endif }); } PolicyBuilder& PolicyBuilder::AllowUtime() { return AllowSyscalls({ #ifdef __NR_futimens __NR_futimens, #endif #ifdef __NR_utime __NR_utime, #endif #ifdef __NR_utimes __NR_utimes, #endif #ifdef __NR_utimensat __NR_utimensat, #endif }); } PolicyBuilder& PolicyBuilder::AllowSafeFcntl() { return AddPolicyOnSyscalls({__NR_fcntl, #ifdef __NR_fcntl64 __NR_fcntl64 #endif }, { ARG_32(1), JEQ32(F_GETFD, ALLOW), JEQ32(F_SETFD, ALLOW), JEQ32(F_GETFL, ALLOW), JEQ32(F_SETFL, ALLOW), JEQ32(F_GETLK, ALLOW), JEQ32(F_SETLK, ALLOW), JEQ32(F_SETLKW, ALLOW), JEQ32(F_DUPFD, ALLOW), JEQ32(F_DUPFD_CLOEXEC, ALLOW), }); } PolicyBuilder& PolicyBuilder::AllowFork() { return AllowSyscalls({ #ifdef __NR_fork __NR_fork, #endif #ifdef __NR_vfork __NR_vfork, #endif __NR_clone}); } PolicyBuilder& PolicyBuilder::AllowWait() { return AllowSyscalls({ #ifdef __NR_waitpid __NR_waitpid, #endif __NR_wait4}); } PolicyBuilder& PolicyBuilder::AllowAlarm() { return AllowSyscalls({ #ifdef __NR_alarm __NR_alarm, #endif __NR_setitimer}); } PolicyBuilder& PolicyBuilder::AllowHandleSignals() { return AllowSyscalls({ __NR_rt_sigaction, __NR_rt_sigreturn, __NR_rt_sigprocmask, #ifdef __NR_signal __NR_signal, #endif #ifdef __NR_sigaction __NR_sigaction, #endif #ifdef __NR_sigreturn __NR_sigreturn, #endif #ifdef __NR_sigprocmask __NR_sigprocmask, #endif #ifdef __NR_sigaltstack __NR_sigaltstack, #endif }); } PolicyBuilder& PolicyBuilder::AllowTCGETS() { return AddPolicyOnSyscall(__NR_ioctl, { ARG_32(1), JEQ32(TCGETS, ALLOW), }); } PolicyBuilder& PolicyBuilder::AllowTime() { return AllowSyscalls({ #ifdef __NR_time __NR_time, #endif __NR_gettimeofday, __NR_clock_gettime}); } PolicyBuilder& PolicyBuilder::AllowSleep() { return AllowSyscalls({ __NR_clock_nanosleep, __NR_nanosleep, }); } PolicyBuilder& PolicyBuilder::AllowGetIDs() { return AllowSyscalls({ __NR_getuid, __NR_geteuid, __NR_getresuid, __NR_getgid, __NR_getegid, __NR_getresgid, #ifdef __NR_getuid32 __NR_getuid32, __NR_geteuid32, __NR_getresuid32, __NR_getgid32, __NR_getegid32, __NR_getresgid32, #endif __NR_getgroups, }); } PolicyBuilder& PolicyBuilder::AllowRestartableSequences( CpuFenceMode cpu_fence_mode) { #ifdef __NR_rseq AllowSyscall(__NR_rseq); #endif AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { return { ARG_32(2), // prot JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), ARG_32(3), // flags JNE32(MAP_PRIVATE | MAP_ANONYMOUS, JUMP(&labels, mmap_end)), ALLOW, LABEL(&labels, mmap_end), }; }); AllowSyscall(__NR_getcpu); AllowSyscall(__NR_membarrier); AllowFutexOp(FUTEX_WAIT); AllowFutexOp(FUTEX_WAKE); AddPolicyOnSyscall(__NR_rt_sigprocmask, { ARG_32(0), JEQ32(SIG_SETMASK, ALLOW), }); if (cpu_fence_mode == kAllowSlowFences) { AllowSyscall(__NR_sched_getaffinity); AllowSyscall(__NR_sched_setaffinity); } AddFileIfNamespaced("/proc/cpuinfo"); AddFileIfNamespaced("/proc/stat"); AddDirectoryIfNamespaced("/sys/devices/system/cpu"); if (cpu_fence_mode == kAllowSlowFences) { AddFileIfNamespaced("/proc/self/cpuset"); } return *this; } PolicyBuilder& PolicyBuilder::AllowGetPIDs() { return AllowSyscalls({ __NR_getpid, __NR_getppid, __NR_gettid, }); } PolicyBuilder& PolicyBuilder::AllowGetPGIDs() { return AllowSyscalls({ __NR_getpgid, #ifdef __NR_getpgrp __NR_getpgrp, #endif }); } PolicyBuilder& PolicyBuilder::AllowGetRlimit() { return AllowSyscalls({ #ifdef __NR_getrlimit __NR_getrlimit, #endif #ifdef __NR_ugetrlimit __NR_ugetrlimit, #endif }); } PolicyBuilder& PolicyBuilder::AllowSetRlimit() { return AllowSyscalls({ #ifdef __NR_setrlimit __NR_setrlimit, #endif #ifdef __NR_usetrlimit __NR_usetrlimit, #endif }); } PolicyBuilder& PolicyBuilder::AllowGetRandom() { return AddPolicyOnSyscall(__NR_getrandom, { ARG_32(2), JEQ32(0, ALLOW), JEQ32(GRND_NONBLOCK, ALLOW), }); } PolicyBuilder& PolicyBuilder::AllowWipeOnFork() { // System headers may not be recent enough to include MADV_WIPEONFORK. static constexpr uint32_t kMadv_WipeOnFork = 18; // The -1 value is used by code to probe that the kernel returns -EINVAL for // unknown values because some environments, like qemu, ignore madvise // completely, but code needs to know whether WIPEONFORK took effect. return AddPolicyOnSyscall(__NR_madvise, { ARG_32(2), JEQ32(kMadv_WipeOnFork, ALLOW), JEQ32(static_cast(-1), ALLOW), }); } PolicyBuilder& PolicyBuilder::AllowLogForwarding() { AllowWrite(); AllowSystemMalloc(); AllowTcMalloc(); // From comms AllowGetPIDs(); AllowSyscalls({// from logging code __NR_clock_gettime, // From comms __NR_gettid, __NR_close}); // For generating stacktraces in logging (e.g. `LOG(FATAL)`) AddPolicyOnSyscall(__NR_rt_sigprocmask, { ARG_32(0), JEQ32(SIG_BLOCK, ALLOW), }); AllowSyscall(__NR_prlimit64); // For LOG(FATAL) return AddPolicyOnSyscall(__NR_kill, [](bpf_labels& labels) -> std::vector { return { ARG_32(0), JNE32(0, JUMP(&labels, pid_not_null)), ARG_32(1), JEQ32(SIGABRT, ALLOW), LABEL(&labels, pid_not_null), }; }); } PolicyBuilder& PolicyBuilder::AllowUnlink() { AllowSyscalls({ #ifdef __NR_rmdir __NR_rmdir, #endif #ifdef __NR_unlink __NR_unlink, #endif __NR_unlinkat, }); return *this; } PolicyBuilder& PolicyBuilder::AllowPoll() { AllowSyscalls({ #ifdef __NR_poll __NR_poll, #endif __NR_ppoll, }); return *this; } PolicyBuilder& PolicyBuilder::AllowRename() { AllowSyscalls({ #ifdef __NR_rename __NR_rename, #endif __NR_renameat, #ifdef __NR_renameat2 __NR_renameat2, #endif }); return *this; } PolicyBuilder& PolicyBuilder::AllowEventFd() { AllowSyscalls({ #ifdef __NR_eventfd __NR_eventfd, #endif __NR_eventfd2, }); return *this; } PolicyBuilder& PolicyBuilder::AllowPrctlSetName() { AddPolicyOnSyscall(__NR_prctl, {ARG_32(0), JEQ32(PR_SET_NAME, ALLOW)}); return *this; } PolicyBuilder& PolicyBuilder::AllowPrctlSetVma() { AddPolicyOnSyscall(__NR_prctl, [](bpf_labels& labels) -> std::vector { return { ARG_32(0), JNE32(PR_SET_VMA, JUMP(&labels, prctlsetvma_end)), ARG_32(1), JEQ32(PR_SET_VMA_ANON_NAME, ALLOW), LABEL(&labels, prctlsetvma_end), }; }); return *this; } PolicyBuilder& PolicyBuilder::AllowFutexOp(int op) { return AddPolicyOnSyscall( __NR_futex, { ARG_32(1), // a <- a & FUTEX_CMD_MASK BPF_STMT(BPF_ALU + BPF_AND + BPF_K, static_cast(FUTEX_CMD_MASK)), JEQ32(static_cast(op) & FUTEX_CMD_MASK, ALLOW), }); } PolicyBuilder& PolicyBuilder::AllowStaticStartup() { AllowGetRlimit(); AllowSyscalls({ // These syscalls take a pointer, so no restriction. __NR_uname, __NR_brk, __NR_set_tid_address, #if defined(__ARM_NR_set_tls) // libc sets the TLS during startup __ARM_NR_set_tls, #endif // This syscall takes a pointer and a length. // We could restrict length, but it might change, so not worth it. __NR_set_robust_list, }); AllowFutexOp(FUTEX_WAIT_BITSET); AddPolicyOnSyscall(__NR_rt_sigaction, { ARG_32(0), // This is real-time signals used internally by libc. JEQ32(__SIGRTMIN + 0, ALLOW), JEQ32(__SIGRTMIN + 1, ALLOW), }); AllowSyscall(__NR_rt_sigprocmask); #ifdef SAPI_X86_64 // The second argument is a pointer. AddPolicyOnSyscall(__NR_arch_prctl, { ARG_32(0), JEQ32(ARCH_SET_FS, ALLOW), }); #endif if constexpr (sapi::host_cpu::IsArm64()) { OverridableBlockSyscallWithErrno(__NR_readlinkat, ENOENT); } #ifdef __NR_readlink OverridableBlockSyscallWithErrno(__NR_readlink, ENOENT); #endif AddPolicyOnSyscall(__NR_mprotect, { ARG_32(2), JEQ32(PROT_READ, ALLOW), }); return *this; } PolicyBuilder& PolicyBuilder::AllowDynamicStartup() { #ifdef __ANDROID__ AllowAccess(); AllowSafeFcntl(); AllowGetIDs(); AllowGetPIDs(); AllowGetRandom(); AllowOpen(); AllowSyscalls({ #ifdef __NR_fstatfs __NR_fstatfs, #endif #ifdef __NR_fstatfs64 __NR_fstatfs64, #endif __NR_readlinkat, __NR_sched_getaffinity, __NR_sched_getscheduler, }); AllowHandleSignals(); AllowFutexOp(FUTEX_WAKE_PRIVATE); AddPolicyOnSyscall(__NR_prctl, [](bpf_labels& labels) -> std::vector { return { ARG_32(0), // option JEQ32(PR_GET_DUMPABLE, ALLOW), JNE32(PR_SET_VMA, JUMP(&labels, prctl_end)), ARG_32(1), // arg2 JEQ32(PR_SET_VMA_ANON_NAME, ALLOW), LABEL(&labels, prctl_end), }; }); AddPolicyOnSyscall(__NR_mremap, { ARG_32(3), JEQ32(MREMAP_MAYMOVE | MREMAP_FIXED, ALLOW), }); AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { return { ARG_32(2), // prot JEQ32(PROT_NONE, JUMP(&labels, prot_none)), JEQ32(PROT_READ, JUMP(&labels, prot_read)), JEQ32(PROT_READ | PROT_WRITE, JUMP(&labels, prot_RW_or_RX)), JEQ32(PROT_READ | PROT_EXEC, JUMP(&labels, prot_RW_or_RX)), // PROT_NONE LABEL(&labels, prot_none), ARG_32(3), // flags JEQ32(MAP_PRIVATE | MAP_ANONYMOUS, ALLOW), JEQ32(MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, ALLOW), JUMP(&labels, mmap_end), // PROT_READ LABEL(&labels, prot_read), ARG_32(3), // flags JEQ32(MAP_SHARED, ALLOW), JEQ32(MAP_PRIVATE, ALLOW), JEQ32(MAP_PRIVATE | MAP_FIXED, ALLOW), JUMP(&labels, mmap_end), // PROT_READ | PROT_WRITE // PROT_READ | PROT_EXEC LABEL(&labels, prot_RW_or_RX), ARG_32(3), // flags JEQ32(MAP_PRIVATE | MAP_FIXED, ALLOW), LABEL(&labels, mmap_end), }; }); #endif AllowRead(); AllowStat(); AllowSyscalls({__NR_lseek, #ifdef __NR__llseek __NR__llseek, // Newer glibc on PPC #endif __NR_close, __NR_munmap}); AddPolicyOnSyscall(__NR_mprotect, { ARG_32(2), JEQ32(PROT_READ, ALLOW), JEQ32(PROT_NONE, ALLOW), JEQ32(PROT_READ | PROT_WRITE, ALLOW), JEQ32(PROT_READ | PROT_EXEC, ALLOW), }); AllowStaticStartup(); return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { return { ARG_32(2), // prot JEQ32(PROT_READ | PROT_EXEC, JUMP(&labels, prot_exec)), JEQ32(PROT_READ | PROT_WRITE, JUMP(&labels, prot_read_write)), JNE32(PROT_READ, JUMP(&labels, mmap_end)), // PROT_READ ARG_32(3), // flags JEQ32(MAP_PRIVATE, ALLOW), JUMP(&labels, mmap_end), // PROT_READ | PROT_WRITE LABEL(&labels, prot_read_write), ARG_32(3), // flags JEQ32(MAP_FILE | MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE, ALLOW), JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, ALLOW), JEQ32(MAP_ANONYMOUS | MAP_PRIVATE, ALLOW), JUMP(&labels, mmap_end), // PROT_READ | PROT_EXEC LABEL(&labels, prot_exec), ARG_32(3), // flags JEQ32(MAP_FILE | MAP_PRIVATE | MAP_DENYWRITE, ALLOW), LABEL(&labels, mmap_end), }; }); } PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall( uint32_t num, absl::Span policy) { return AddPolicyOnSyscalls({num}, policy); } PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall(uint32_t num, BpfFunc f) { return AddPolicyOnSyscalls({num}, f); } PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls( absl::Span nums, absl::Span policy) { if (nums.empty()) { SetError(absl::InvalidArgumentError( "Cannot add a policy for empty list of syscalls")); return *this; } std::deque out; // Insert and verify the policy. out.insert(out.end(), policy.begin(), policy.end()); for (size_t i = 0; i < out.size(); ++i) { sock_filter& filter = out[i]; const size_t max_jump = out.size() - i - 1; if (!CheckBpfBounds(filter, max_jump)) { SetError(absl::InvalidArgumentError("bpf jump out of bounds")); return *this; } // Syscall arch is expected as TRACE value if (filter.code == (BPF_RET | BPF_K) && (filter.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE && (filter.k & SECCOMP_RET_DATA) != Syscall::GetHostArch()) { LOG(WARNING) << "SANDBOX2_TRACE should be used in policy instead of " "TRACE(value)"; filter = SANDBOX2_TRACE; } } // Pre-/Postcondition: Syscall number loaded into A register out.push_back(LOAD_SYSCALL_NR); if (out.size() > std::numeric_limits::max()) { SetError(absl::InvalidArgumentError("syscall policy is too long")); return *this; } // Create jumps for each syscall. size_t do_policy_loc = out.size(); // Iterate in reverse order and prepend instruction, so that jumps can be // calculated easily. constexpr size_t kMaxShortJump = 255; bool last = true; for (auto it = std::rbegin(nums); it != std::rend(nums); ++it) { if (*it == __NR_bpf || *it == __NR_ptrace) { SetError(absl::InvalidArgumentError( "cannot add policy for bpf/ptrace syscall")); return *this; } // If syscall is not matched try with the next one. uint8_t jf = 0; // If last syscall on the list does not match skip the policy by jumping // over it. if (last) { if (out.size() > kMaxShortJump) { out.push_front( BPF_STMT(BPF_JMP + BPF_JA, static_cast(out.size()))); } else { jf = out.size(); } last = false; } // Add a helper absolute jump if needed - the policy/last helper jump is // out of reach of a short jump. if ((out.size() - do_policy_loc) > kMaxShortJump) { out.push_front(BPF_STMT( BPF_JMP + BPF_JA, static_cast(out.size() - policy.size()))); do_policy_loc = out.size(); ++jf; } uint8_t jt = out.size() - do_policy_loc; out.push_front(BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, *it, jt, jf)); } user_policy_.insert(user_policy_.end(), out.begin(), out.end()); return *this; } PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls( absl::Span nums, BpfFunc f) { return AddPolicyOnSyscalls(nums, ResolveBpfFunc(f)); } PolicyBuilder& PolicyBuilder::AddPolicyOnMmap( absl::Span policy) { return AddPolicyOnSyscalls(kMmapSyscalls, policy); } PolicyBuilder& PolicyBuilder::AddPolicyOnMmap(BpfFunc f) { return AddPolicyOnSyscalls(kMmapSyscalls, f); } PolicyBuilder& PolicyBuilder::DangerDefaultAllowAll() { return DefaultAction(AllowAllSyscalls()); } PolicyBuilder& PolicyBuilder::DefaultAction(AllowAllSyscalls) { default_action_ = ALLOW; return *this; } absl::StatusOr PolicyBuilder::ValidateAbsolutePath( absl::string_view path) { if (!file::IsAbsolutePath(path)) { return absl::InvalidArgumentError( absl::StrCat("Path is not absolute: '", path, "'")); } return ValidatePath(path); } absl::StatusOr PolicyBuilder::ValidatePath( absl::string_view path) { std::string fixed_path = file::CleanPath(path); if (fixed_path != path) { return absl::InvalidArgumentError(absl::StrCat( "Path was not normalized. '", path, "' != '", fixed_path, "'")); } return fixed_path; } std::vector PolicyBuilder::ResolveBpfFunc(BpfFunc f) { bpf_labels l = {0}; std::vector policy = f(l); if (bpf_resolve_jumps(&l, policy.data(), policy.size()) != 0) { SetError(absl::InternalError("Cannot resolve bpf jumps")); } return policy; } absl::StatusOr> PolicyBuilder::TryBuild() { if (!last_status_.ok()) { return last_status_; } if (user_policy_.size() > kMaxUserPolicyLength) { return absl::FailedPreconditionError( absl::StrCat("User syscall policy is to long (", user_policy_.size(), " > ", kMaxUserPolicyLength, ").")); } // Using `new` to access a non-public constructor. auto output = absl::WrapUnique(new Policy()); if (already_built_) { return absl::FailedPreconditionError("Can only build policy once."); } if (use_namespaces_) { if (allow_unrestricted_networking_ && hostname_ != kDefaultHostname) { return absl::FailedPreconditionError( "Cannot set hostname without network namespaces."); } output->namespace_ = Namespace(allow_unrestricted_networking_, std::move(mounts_), hostname_, allow_mount_propagation_); } output->collect_stacktrace_on_signal_ = collect_stacktrace_on_signal_; output->collect_stacktrace_on_violation_ = collect_stacktrace_on_violation_; output->collect_stacktrace_on_timeout_ = collect_stacktrace_on_timeout_; output->collect_stacktrace_on_kill_ = collect_stacktrace_on_kill_; output->collect_stacktrace_on_exit_ = collect_stacktrace_on_exit_; output->user_policy_ = std::move(user_policy_); if (default_action_) { output->user_policy_.push_back(*default_action_); } output->user_policy_.insert(output->user_policy_.end(), overridable_policy_.begin(), overridable_policy_.end()); output->user_policy_handles_bpf_ = user_policy_handles_bpf_; output->user_policy_handles_ptrace_ = user_policy_handles_ptrace_; PolicyBuilderDescription pb_description; StoreDescription(&pb_description); output->policy_builder_description_ = pb_description; output->allowed_hosts_ = std::move(allowed_hosts_); already_built_ = true; return std::move(output); } PolicyBuilder& PolicyBuilder::AddFile(absl::string_view path, bool is_ro) { return AddFileAt(path, path, is_ro); } PolicyBuilder& PolicyBuilder::AddFileAt(absl::string_view outside, absl::string_view inside, bool is_ro) { EnableNamespaces(); // NOLINT(clang-diagnostic-deprecated-declarations) return AddFileAtIfNamespaced(outside, inside, is_ro); } PolicyBuilder& PolicyBuilder::AddFileIfNamespaced(absl::string_view path, bool is_ro) { return AddFileAtIfNamespaced(path, path, is_ro); } PolicyBuilder& PolicyBuilder::AddFileAtIfNamespaced(absl::string_view outside, absl::string_view inside, bool is_ro) { auto valid_outside = ValidateAbsolutePath(outside); if (!valid_outside.ok()) { SetError(valid_outside.status()); return *this; } if (absl::StartsWith(*valid_outside, "/proc/self") && *valid_outside != "/proc/self/cpuset") { SetError(absl::InvalidArgumentError( absl::StrCat("Cannot add /proc/self mounts, you need to mount the " "whole /proc instead. You tried to mount ", outside))); return *this; } if (!is_ro && IsOnReadOnlyDev(*valid_outside)) { SetError(absl::FailedPreconditionError( absl::StrCat("Cannot add ", outside, " as read-write as it's on a read-only device"))); return *this; } if (auto status = mounts_.AddFileAt(*valid_outside, inside, is_ro); !status.ok()) { SetError( absl::InternalError(absl::StrCat("Could not add file ", outside, " => ", inside, ": ", status.message()))); } return *this; } PolicyBuilder& PolicyBuilder::AddLibrariesForBinary( absl::string_view path, absl::string_view ld_library_path) { EnableNamespaces(); // NOLINT(clang-diagnostic-deprecated-declarations) auto valid_path = ValidatePath(path); if (!valid_path.ok()) { SetError(valid_path.status()); return *this; } if (auto status = mounts_.AddMappingsForBinary(*valid_path, ld_library_path); !status.ok()) { SetError(absl::InternalError(absl::StrCat( "Could not add libraries for ", *valid_path, ": ", status.message()))); } return *this; } PolicyBuilder& PolicyBuilder::AddLibrariesForBinary( int fd, absl::string_view ld_library_path) { return AddLibrariesForBinary(absl::StrCat("/proc/self/fd/", fd), ld_library_path); } PolicyBuilder& PolicyBuilder::AddDirectory(absl::string_view path, bool is_ro) { return AddDirectoryAt(path, path, is_ro); } PolicyBuilder& PolicyBuilder::AddDirectoryAt(absl::string_view outside, absl::string_view inside, bool is_ro) { EnableNamespaces(); // NOLINT(clang-diagnostic-deprecated-declarations) return AddDirectoryAtIfNamespaced(outside, inside, is_ro); } PolicyBuilder& PolicyBuilder::AddDirectoryIfNamespaced(absl::string_view path, bool is_ro) { return AddDirectoryAtIfNamespaced(path, path, is_ro); } PolicyBuilder& PolicyBuilder::AddDirectoryAtIfNamespaced( absl::string_view outside, absl::string_view inside, bool is_ro) { auto valid_outside = ValidateAbsolutePath(outside); if (!valid_outside.ok()) { SetError(valid_outside.status()); return *this; } if (absl::StartsWith(*valid_outside, "/proc/self")) { SetError(absl::InvalidArgumentError( absl::StrCat("Cannot add /proc/self mounts, you need to mount the " "whole /proc instead. You tried to mount ", outside))); return *this; } if (!is_ro && IsOnReadOnlyDev(*valid_outside)) { SetError(absl::FailedPreconditionError( absl::StrCat("Cannot add ", outside, " as read-write as it's on a read-only device"))); return *this; } if (absl::Status status = mounts_.AddDirectoryAt(*valid_outside, inside, is_ro); !status.ok()) { SetError(absl::InternalError(absl::StrCat("Could not add directory ", outside, " => ", inside, ": ", status.message()))); return *this; } return *this; } PolicyBuilder& PolicyBuilder::AddTmpfs(absl::string_view inside, size_t size) { EnableNamespaces(); // NOLINT(clang-diagnostic-deprecated-declarations) if (auto status = mounts_.AddTmpfs(inside, size); !status.ok()) { SetError(absl::InternalError(absl::StrCat("Could not mount tmpfs ", inside, ": ", status.message()))); } return *this; } // Use Allow(UnrestrictedNetworking()) instead. PolicyBuilder& PolicyBuilder::AllowUnrestrictedNetworking() { return Allow(UnrestrictedNetworking()); } PolicyBuilder& PolicyBuilder::SetHostname(absl::string_view hostname) { EnableNamespaces(); // NOLINT(clang-diagnostic-deprecated-declarations) hostname_ = std::string(hostname); return *this; } PolicyBuilder& PolicyBuilder::CollectStacktracesOnViolation(bool enable) { collect_stacktrace_on_violation_ = enable; return *this; } PolicyBuilder& PolicyBuilder::CollectStacktracesOnSignal(bool enable) { collect_stacktrace_on_signal_ = enable; return *this; } PolicyBuilder& PolicyBuilder::CollectStacktracesOnTimeout(bool enable) { collect_stacktrace_on_timeout_ = enable; return *this; } PolicyBuilder& PolicyBuilder::CollectStacktracesOnKill(bool enable) { collect_stacktrace_on_kill_ = enable; return *this; } PolicyBuilder& PolicyBuilder::CollectStacktracesOnExit(bool enable) { collect_stacktrace_on_exit_ = enable; return *this; } PolicyBuilder& PolicyBuilder::AddNetworkProxyPolicy() { if (allowed_hosts_) { SetError(absl::FailedPreconditionError( "AddNetworkProxyPolicy or AddNetworkProxyHandlerPolicy can be called " "at most once")); return *this; } allowed_hosts_ = AllowedHosts(); AllowFutexOp(FUTEX_WAKE); AllowFutexOp(FUTEX_WAIT); AllowFutexOp(FUTEX_WAIT_BITSET); AllowDup(); AllowSyscalls({ __NR_recvmsg, __NR_close, __NR_gettid, }); AddPolicyOnSyscall(__NR_socket, { ARG_32(0), JEQ32(AF_INET, ALLOW), JEQ32(AF_INET6, ALLOW), }); AddPolicyOnSyscall(__NR_getsockopt, [](bpf_labels& labels) -> std::vector { return { ARG_32(1), JNE32(SOL_SOCKET, JUMP(&labels, getsockopt_end)), ARG_32(2), JEQ32(SO_TYPE, ALLOW), LABEL(&labels, getsockopt_end), }; }); #ifdef SAPI_PPC64_LE AddPolicyOnSyscall(__NR_socketcall, { ARG_32(0), JEQ32(SYS_SOCKET, ALLOW), JEQ32(SYS_GETSOCKOPT, ALLOW), JEQ32(SYS_RECVMSG, ALLOW), }); #endif return *this; } PolicyBuilder& PolicyBuilder::AddNetworkProxyHandlerPolicy() { AddNetworkProxyPolicy(); AllowSyscall(__NR_rt_sigreturn); AddPolicyOnSyscall(__NR_rt_sigaction, { ARG_32(0), JEQ32(SIGSYS, ALLOW), }); AddPolicyOnSyscall(__NR_rt_sigprocmask, { ARG_32(0), JEQ32(SIG_UNBLOCK, ALLOW), }); AddPolicyOnSyscall(__NR_connect, {TRAP(0)}); #ifdef SAPI_PPC64_LE AddPolicyOnSyscall(__NR_socketcall, { ARG_32(0), JEQ32(SYS_CONNECT, TRAP(0)), }); #endif return *this; } PolicyBuilder& PolicyBuilder::TrapPtrace() { if (handled_syscalls_.insert(__NR_ptrace).second) { user_policy_.insert(user_policy_.end(), {SYSCALL(__NR_ptrace, TRAP(0))}); user_policy_handles_ptrace_ = true; } return *this; } PolicyBuilder& PolicyBuilder::SetRootWritable() { EnableNamespaces(); // NOLINT(clang-diagnostic-deprecated-declarations) mounts_.SetRootWritable(); return *this; } void PolicyBuilder::StoreDescription(PolicyBuilderDescription* pb_description) { for (const auto& handled_syscall : handled_syscalls_) { pb_description->add_handled_syscalls(handled_syscall); } } PolicyBuilder& PolicyBuilder::AllowIPv4(const std::string& ip_and_mask, uint32_t port) { if (!allowed_hosts_) { SetError(absl::FailedPreconditionError( "AddNetworkProxyPolicy or AddNetworkProxyHandlerPolicy must be called " "before adding IP rules")); return *this; } absl::Status status = allowed_hosts_->AllowIPv4(ip_and_mask, port); if (!status.ok()) { SetError(status); } return *this; } PolicyBuilder& PolicyBuilder::AllowIPv6(const std::string& ip_and_mask, uint32_t port) { if (!allowed_hosts_) { SetError(absl::FailedPreconditionError( "AddNetworkProxyPolicy or AddNetworkProxyHandlerPolicy must be called " "before adding IP rules")); return *this; } absl::Status status = allowed_hosts_->AllowIPv6(ip_and_mask, port); if (!status.ok()) { SetError(status); } return *this; } PolicyBuilder& PolicyBuilder::SetError(const absl::Status& status) { LOG(ERROR) << status; last_status_ = status; return *this; } } // namespace sandbox2