mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
36e4b80f9a
PiperOrigin-RevId: 593968486 Change-Id: I4f7d4d8a6f593d94c0a7e7672826074c4cefc230
1618 lines
46 KiB
C++
1618 lines
46 KiB
C++
// 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 <fcntl.h> // For the fcntl flags
|
|
#include <linux/bpf_common.h>
|
|
#include <linux/filter.h>
|
|
#include <linux/futex.h>
|
|
#include <linux/random.h> // For GRND_NONBLOCK
|
|
#include <linux/seccomp.h>
|
|
#include <stddef.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h> // For mmap arguments
|
|
#include <sys/prctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
#include <syscall.h>
|
|
#include <unistd.h>
|
|
|
|
#include <array>
|
|
#include <cerrno>
|
|
#include <csignal>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/container/flat_hash_set.h"
|
|
#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/str_cat.h"
|
|
#include "absl/strings/string_view.h"
|
|
#include "absl/types/span.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/syscall.h"
|
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
|
#include "sandboxed_api/sandbox2/violation.pb.h"
|
|
#include "sandboxed_api/util/path.h"
|
|
|
|
#if defined(SAPI_X86_64)
|
|
#include <asm/prctl.h>
|
|
#elif defined(SAPI_PPC64_LE)
|
|
#include <asm/termbits.h> // 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<uint32_t, 2> 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<const uint32_t> nums) {
|
|
for (auto num : nums) {
|
|
AllowSyscall(num);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
PolicyBuilder& PolicyBuilder::BlockSyscallsWithErrno(
|
|
absl::Span<const uint32_t> 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();
|
|
AllowGetPIDs();
|
|
AllowWipeOnFork();
|
|
#ifdef __NR_open
|
|
OverridableBlockSyscallWithErrno(__NR_open, ENOENT);
|
|
#endif
|
|
#ifdef __NR_openat
|
|
OverridableBlockSyscallWithErrno(__NR_openat, ENOENT);
|
|
#endif
|
|
|
|
return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector<sock_filter> {
|
|
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();
|
|
AllowGetPIDs();
|
|
|
|
AddPolicyOnSyscall(__NR_mprotect, {
|
|
ARG_32(2),
|
|
JEQ32(PROT_READ | PROT_WRITE, ALLOW),
|
|
JEQ32(PROT_NONE, ALLOW),
|
|
});
|
|
|
|
return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector<sock_filter> {
|
|
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<sock_filter> {
|
|
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()) {
|
|
return *this;
|
|
}
|
|
// *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
|
|
AllowMmapWithoutExec();
|
|
AllowSyscall(__NR_munmap);
|
|
AllowSyscall(__NR_sched_yield);
|
|
|
|
// 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);
|
|
}
|
|
if constexpr (sapi::sanitizers::IsTSan()) {
|
|
AllowSyscall(__NR_set_robust_list);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
PolicyBuilder& PolicyBuilder::AllowLlvmCoverage() {
|
|
if (!sapi::IsCoverageRun()) {
|
|
return *this;
|
|
}
|
|
AllowStat();
|
|
AllowGetPIDs();
|
|
AllowOpen();
|
|
AllowRead();
|
|
AllowWrite();
|
|
AllowMkdir();
|
|
AllowSafeFcntl();
|
|
AllowSyscalls({
|
|
__NR_munmap, __NR_close, __NR_lseek,
|
|
#ifdef __NR__llseek
|
|
__NR__llseek, // Newer glibc on PPC
|
|
#endif
|
|
});
|
|
AllowTcMalloc();
|
|
AddPolicyOnMmap([](bpf_labels& labels) -> std::vector<sock_filter> {
|
|
return {
|
|
ARG_32(2), // prot
|
|
JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)),
|
|
ARG_32(3), // flags
|
|
JEQ32(MAP_SHARED, ALLOW),
|
|
LABEL(&labels, mmap_end),
|
|
};
|
|
});
|
|
AddDirectoryIfNamespaced(getenv("COVERAGE_DIR"), /*is_ro=*/false);
|
|
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::AllowMmapWithoutExec() {
|
|
return AddPolicyOnMmap({
|
|
ARG_32(2),
|
|
BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, PROT_EXEC, 1, 0),
|
|
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<sock_filter> {
|
|
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);
|
|
AllowRead();
|
|
AllowOpen();
|
|
AllowSyscall(__NR_close);
|
|
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<uint32_t>(-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<sock_filter> {
|
|
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<sock_filter> {
|
|
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<uint32_t>(FUTEX_CMD_MASK)),
|
|
JEQ32(static_cast<uint32_t>(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
|
|
|
|
#ifdef __NR_prlimit64
|
|
OverridableBlockSyscallWithErrno(__NR_prlimit64, EPERM);
|
|
#endif
|
|
AddPolicyOnSyscall(__NR_mprotect, {
|
|
ARG_32(2),
|
|
JEQ32(PROT_READ, ALLOW),
|
|
});
|
|
|
|
OverridableBlockSyscallWithErrno(__NR_sigaltstack, ENOSYS);
|
|
|
|
return *this;
|
|
}
|
|
|
|
PolicyBuilder& PolicyBuilder::AllowDynamicStartup() {
|
|
#ifdef __ANDROID__
|
|
AllowSafeFcntl();
|
|
AllowGetIDs();
|
|
AllowGetPIDs();
|
|
AllowGetRandom();
|
|
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<sock_filter> {
|
|
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<sock_filter> {
|
|
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
|
|
|
|
AllowAccess();
|
|
AllowOpen();
|
|
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<sock_filter> {
|
|
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<const sock_filter> policy) {
|
|
return AddPolicyOnSyscalls({num}, policy);
|
|
}
|
|
|
|
PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall(uint32_t num, BpfFunc f) {
|
|
return AddPolicyOnSyscalls({num}, f);
|
|
}
|
|
|
|
PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls(
|
|
absl::Span<const uint32_t> nums, absl::Span<const sock_filter> policy) {
|
|
if (nums.empty()) {
|
|
SetError(absl::InvalidArgumentError(
|
|
"Cannot add a policy for empty list of syscalls"));
|
|
return *this;
|
|
}
|
|
std::deque<sock_filter> 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<uint32_t>::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<uint32_t>(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<uint32_t>(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<const uint32_t> nums, BpfFunc f) {
|
|
return AddPolicyOnSyscalls(nums, ResolveBpfFunc(f));
|
|
}
|
|
|
|
PolicyBuilder& PolicyBuilder::AddPolicyOnMmap(
|
|
absl::Span<const sock_filter> 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<std::string> 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<std::string> 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<sock_filter> PolicyBuilder::ResolveBpfFunc(BpfFunc f) {
|
|
bpf_labels l = {0};
|
|
|
|
std::vector<sock_filter> policy = f(l);
|
|
if (bpf_resolve_jumps(&l, policy.data(), policy.size()) != 0) {
|
|
SetError(absl::InternalError("Cannot resolve bpf jumps"));
|
|
}
|
|
|
|
return policy;
|
|
}
|
|
|
|
absl::StatusOr<std::unique_ptr<Policy>> 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<sock_filter> {
|
|
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
|