| Index: sandbox/linux/seccomp-bpf/sandbox_bpf.cc
|
| diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
|
| index 12667e73e6cf3c4f37fc51b98272809bb73f314c..b90a9ef5da085b591279b790a455288a17df75ed 100644
|
| --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
|
| +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
|
| @@ -12,11 +12,13 @@
|
|
|
| #include <errno.h>
|
| #include <fcntl.h>
|
| +#include <signal.h>
|
| #include <string.h>
|
| #include <sys/prctl.h>
|
| #include <sys/stat.h>
|
| #include <sys/syscall.h>
|
| #include <sys/types.h>
|
| +#include <sys/wait.h>
|
| #include <time.h>
|
| #include <unistd.h>
|
|
|
| @@ -28,9 +30,11 @@
|
| #include "base/memory/scoped_ptr.h"
|
| #include "base/posix/eintr_wrapper.h"
|
| #include "sandbox/linux/seccomp-bpf/codegen.h"
|
| +#include "sandbox/linux/seccomp-bpf/errorcode.h"
|
| #include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
|
| #include "sandbox/linux/seccomp-bpf/syscall.h"
|
| #include "sandbox/linux/seccomp-bpf/syscall_iterator.h"
|
| +#include "sandbox/linux/seccomp-bpf/trap.h"
|
| #include "sandbox/linux/seccomp-bpf/verifier.h"
|
| #include "sandbox/linux/services/linux_syscalls.h"
|
|
|
| @@ -40,6 +44,28 @@ namespace {
|
|
|
| const int kExpectedExitCode = 100;
|
|
|
| +#if defined(__i386__) || defined(__x86_64__)
|
| +const bool kIsIntel = true;
|
| +#else
|
| +const bool kIsIntel = false;
|
| +#endif
|
| +#if defined(__x86_64__) && defined(__ILP32__)
|
| +const bool kIsX32 = true;
|
| +#else
|
| +const bool kIsX32 = false;
|
| +#endif
|
| +
|
| +const int kSyscallsRequiredForUnsafeTraps[] = {
|
| + __NR_rt_sigprocmask,
|
| + __NR_rt_sigreturn,
|
| +#if defined(__NR_sigprocmask)
|
| + __NR_sigprocmask,
|
| +#endif
|
| +#if defined(__NR_sigreturn)
|
| + __NR_sigreturn,
|
| +#endif
|
| +};
|
| +
|
| bool HasExactlyOneBit(uint64_t x) {
|
| // Common trick; e.g., see http://stackoverflow.com/a/108329.
|
| return x != 0 && (x & (x - 1)) == 0;
|
| @@ -627,145 +653,8 @@ SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) {
|
| SANDBOX_DIE("Out of memory");
|
| }
|
|
|
| - // If the architecture doesn't match SECCOMP_ARCH, disallow the
|
| - // system call.
|
| - Instruction* tail;
|
| - Instruction* head = gen->MakeInstruction(
|
| - BPF_LD + BPF_W + BPF_ABS,
|
| - SECCOMP_ARCH_IDX,
|
| - tail = gen->MakeInstruction(
|
| - BPF_JMP + BPF_JEQ + BPF_K,
|
| - SECCOMP_ARCH,
|
| - NULL,
|
| - gen->MakeInstruction(
|
| - BPF_RET + BPF_K,
|
| - Kill("Invalid audit architecture in BPF filter"))));
|
| -
|
| - bool has_unsafe_traps = false;
|
| - {
|
| - // Evaluate all possible system calls and group their ErrorCodes into
|
| - // ranges of identical codes.
|
| - Ranges ranges;
|
| - FindRanges(&ranges);
|
| -
|
| - // Compile the system call ranges to an optimized BPF jumptable
|
| - Instruction* jumptable =
|
| - AssembleJumpTable(gen, ranges.begin(), ranges.end());
|
| -
|
| - // If there is at least one UnsafeTrap() in our program, the entire sandbox
|
| - // is unsafe. We need to modify the program so that all non-
|
| - // SECCOMP_RET_ALLOW ErrorCodes are handled in user-space. This will then
|
| - // allow us to temporarily disable sandboxing rules inside of callbacks to
|
| - // UnsafeTrap().
|
| - gen->Traverse(jumptable, CheckForUnsafeErrorCodes, &has_unsafe_traps);
|
| -
|
| - // Grab the system call number, so that we can implement jump tables.
|
| - Instruction* load_nr =
|
| - gen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, SECCOMP_NR_IDX);
|
| -
|
| - // If our BPF program has unsafe jumps, enable support for them. This
|
| - // test happens very early in the BPF filter program. Even before we
|
| - // consider looking at system call numbers.
|
| - // As support for unsafe jumps essentially defeats all the security
|
| - // measures that the sandbox provides, we print a big warning message --
|
| - // and of course, we make sure to only ever enable this feature if it
|
| - // is actually requested by the sandbox policy.
|
| - if (has_unsafe_traps) {
|
| - if (Syscall::Call(-1) == -1 && errno == ENOSYS) {
|
| - SANDBOX_DIE(
|
| - "Support for UnsafeTrap() has not yet been ported to this "
|
| - "architecture");
|
| - }
|
| -
|
| - if (!policy_->EvaluateSyscall(this, __NR_rt_sigprocmask)
|
| - .Equals(ErrorCode(ErrorCode::ERR_ALLOWED)) ||
|
| - !policy_->EvaluateSyscall(this, __NR_rt_sigreturn)
|
| - .Equals(ErrorCode(ErrorCode::ERR_ALLOWED))
|
| -#if defined(__NR_sigprocmask)
|
| - ||
|
| - !policy_->EvaluateSyscall(this, __NR_sigprocmask)
|
| - .Equals(ErrorCode(ErrorCode::ERR_ALLOWED))
|
| -#endif
|
| -#if defined(__NR_sigreturn)
|
| - ||
|
| - !policy_->EvaluateSyscall(this, __NR_sigreturn)
|
| - .Equals(ErrorCode(ErrorCode::ERR_ALLOWED))
|
| -#endif
|
| - ) {
|
| - SANDBOX_DIE(
|
| - "Invalid seccomp policy; if using UnsafeTrap(), you must "
|
| - "unconditionally allow sigreturn() and sigprocmask()");
|
| - }
|
| -
|
| - if (!Trap::EnableUnsafeTrapsInSigSysHandler()) {
|
| - // We should never be able to get here, as UnsafeTrap() should never
|
| - // actually return a valid ErrorCode object unless the user set the
|
| - // CHROME_SANDBOX_DEBUGGING environment variable; and therefore,
|
| - // "has_unsafe_traps" would always be false. But better double-check
|
| - // than enabling dangerous code.
|
| - SANDBOX_DIE("We'd rather die than enable unsafe traps");
|
| - }
|
| - gen->Traverse(jumptable, RedirectToUserspace, this);
|
| -
|
| - // Allow system calls, if they originate from our magic return address
|
| - // (which we can query by calling Syscall::Call(-1)).
|
| - uintptr_t syscall_entry_point = static_cast<uintptr_t>(Syscall::Call(-1));
|
| - uint32_t low = static_cast<uint32_t>(syscall_entry_point);
|
| -#if __SIZEOF_POINTER__ > 4
|
| - uint32_t hi = static_cast<uint32_t>(syscall_entry_point >> 32);
|
| -#endif
|
| -
|
| - // BPF cannot do native 64bit comparisons. On 64bit architectures, we
|
| - // have to compare both 32bit halves of the instruction pointer. If they
|
| - // match what we expect, we return ERR_ALLOWED. If either or both don't
|
| - // match, we continue evalutating the rest of the sandbox policy.
|
| - Instruction* escape_hatch = gen->MakeInstruction(
|
| - BPF_LD + BPF_W + BPF_ABS,
|
| - SECCOMP_IP_LSB_IDX,
|
| - gen->MakeInstruction(
|
| - BPF_JMP + BPF_JEQ + BPF_K,
|
| - low,
|
| -#if __SIZEOF_POINTER__ > 4
|
| - gen->MakeInstruction(
|
| - BPF_LD + BPF_W + BPF_ABS,
|
| - SECCOMP_IP_MSB_IDX,
|
| - gen->MakeInstruction(
|
| - BPF_JMP + BPF_JEQ + BPF_K,
|
| - hi,
|
| -#endif
|
| - gen->MakeInstruction(BPF_RET + BPF_K,
|
| - ErrorCode(ErrorCode::ERR_ALLOWED)),
|
| -#if __SIZEOF_POINTER__ > 4
|
| - load_nr)),
|
| -#endif
|
| - load_nr));
|
| - gen->JoinInstructions(tail, escape_hatch);
|
| - } else {
|
| - gen->JoinInstructions(tail, load_nr);
|
| - }
|
| - tail = load_nr;
|
| -
|
| -// On Intel architectures, verify that system call numbers are in the
|
| -// expected number range. The older i386 and x86-64 APIs clear bit 30
|
| -// on all system calls. The newer x32 API always sets bit 30.
|
| -#if defined(__i386__) || defined(__x86_64__)
|
| - Instruction* invalidX32 = gen->MakeInstruction(
|
| - BPF_RET + BPF_K, Kill("Illegal mixing of system call ABIs").err_);
|
| - Instruction* checkX32 =
|
| -#if defined(__x86_64__) && defined(__ILP32__)
|
| - gen->MakeInstruction(
|
| - BPF_JMP + BPF_JSET + BPF_K, 0x40000000, 0, invalidX32);
|
| -#else
|
| - gen->MakeInstruction(
|
| - BPF_JMP + BPF_JSET + BPF_K, 0x40000000, invalidX32, 0);
|
| -#endif
|
| - gen->JoinInstructions(tail, checkX32);
|
| - tail = checkX32;
|
| -#endif
|
| -
|
| - // Append jump table to our pre-amble
|
| - gen->JoinInstructions(tail, jumptable);
|
| - }
|
| + bool has_unsafe_traps;
|
| + Instruction* head = CompilePolicy(gen, &has_unsafe_traps);
|
|
|
| // Turn the DAG into a vector of instructions.
|
| Program* program = new Program();
|
| @@ -785,6 +674,146 @@ SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) {
|
| return program;
|
| }
|
|
|
| +Instruction* SandboxBPF::CompilePolicy(CodeGen* gen, bool* has_unsafe_traps) {
|
| + // A compiled policy consists of three logical parts:
|
| + // 1. Check that the "arch" field matches the expected architecture.
|
| + // 2. If the policy involves unsafe traps, check if the syscall was
|
| + // invoked by Syscall::Call, and then allow it unconditionally.
|
| + // 3. Check the system call number and jump to the appropriate compiled
|
| + // system call policy number.
|
| + return CheckArch(
|
| + gen, MaybeAddEscapeHatch(gen, has_unsafe_traps, DispatchSyscall(gen)));
|
| +}
|
| +
|
| +Instruction* SandboxBPF::CheckArch(CodeGen* gen, Instruction* passed) {
|
| + // If the architecture doesn't match SECCOMP_ARCH, disallow the
|
| + // system call.
|
| + return gen->MakeInstruction(
|
| + BPF_LD + BPF_W + BPF_ABS,
|
| + SECCOMP_ARCH_IDX,
|
| + gen->MakeInstruction(
|
| + BPF_JMP + BPF_JEQ + BPF_K,
|
| + SECCOMP_ARCH,
|
| + passed,
|
| + RetExpression(gen,
|
| + Kill("Invalid audit architecture in BPF filter"))));
|
| +}
|
| +
|
| +Instruction* SandboxBPF::MaybeAddEscapeHatch(CodeGen* gen,
|
| + bool* has_unsafe_traps,
|
| + Instruction* rest) {
|
| + // If there is at least one UnsafeTrap() in our program, the entire sandbox
|
| + // is unsafe. We need to modify the program so that all non-
|
| + // SECCOMP_RET_ALLOW ErrorCodes are handled in user-space. This will then
|
| + // allow us to temporarily disable sandboxing rules inside of callbacks to
|
| + // UnsafeTrap().
|
| + *has_unsafe_traps = false;
|
| + gen->Traverse(rest, CheckForUnsafeErrorCodes, has_unsafe_traps);
|
| + if (!*has_unsafe_traps) {
|
| + // If no unsafe traps, then simply return |rest|.
|
| + return rest;
|
| + }
|
| +
|
| + // If our BPF program has unsafe jumps, enable support for them. This
|
| + // test happens very early in the BPF filter program. Even before we
|
| + // consider looking at system call numbers.
|
| + // As support for unsafe jumps essentially defeats all the security
|
| + // measures that the sandbox provides, we print a big warning message --
|
| + // and of course, we make sure to only ever enable this feature if it
|
| + // is actually requested by the sandbox policy.
|
| + if (Syscall::Call(-1) == -1 && errno == ENOSYS) {
|
| + SANDBOX_DIE(
|
| + "Support for UnsafeTrap() has not yet been ported to this "
|
| + "architecture");
|
| + }
|
| +
|
| + for (size_t i = 0; i < arraysize(kSyscallsRequiredForUnsafeTraps); ++i) {
|
| + if (!policy_->EvaluateSyscall(this, kSyscallsRequiredForUnsafeTraps[i])
|
| + .Equals(ErrorCode(ErrorCode::ERR_ALLOWED))) {
|
| + SANDBOX_DIE(
|
| + "Policies that use UnsafeTrap() must unconditionally allow all "
|
| + "required system calls");
|
| + }
|
| + }
|
| +
|
| + if (!Trap::EnableUnsafeTrapsInSigSysHandler()) {
|
| + // We should never be able to get here, as UnsafeTrap() should never
|
| + // actually return a valid ErrorCode object unless the user set the
|
| + // CHROME_SANDBOX_DEBUGGING environment variable; and therefore,
|
| + // "has_unsafe_traps" would always be false. But better double-check
|
| + // than enabling dangerous code.
|
| + SANDBOX_DIE("We'd rather die than enable unsafe traps");
|
| + }
|
| + gen->Traverse(rest, RedirectToUserspace, this);
|
| +
|
| + // Allow system calls, if they originate from our magic return address
|
| + // (which we can query by calling Syscall::Call(-1)).
|
| + uint64_t syscall_entry_point =
|
| + static_cast<uint64_t>(static_cast<uintptr_t>(Syscall::Call(-1)));
|
| + uint32_t low = static_cast<uint32_t>(syscall_entry_point);
|
| + uint32_t hi = static_cast<uint32_t>(syscall_entry_point >> 32);
|
| +
|
| + // BPF cannot do native 64-bit comparisons, so we have to compare
|
| + // both 32-bit halves of the instruction pointer. If they match what
|
| + // we expect, we return ERR_ALLOWED. If either or both don't match,
|
| + // we continue evalutating the rest of the sandbox policy.
|
| + //
|
| + // For simplicity, we check the full 64-bit instruction pointer even
|
| + // on 32-bit architectures.
|
| + return gen->MakeInstruction(
|
| + BPF_LD + BPF_W + BPF_ABS,
|
| + SECCOMP_IP_LSB_IDX,
|
| + gen->MakeInstruction(
|
| + BPF_JMP + BPF_JEQ + BPF_K,
|
| + low,
|
| + gen->MakeInstruction(
|
| + BPF_LD + BPF_W + BPF_ABS,
|
| + SECCOMP_IP_MSB_IDX,
|
| + gen->MakeInstruction(
|
| + BPF_JMP + BPF_JEQ + BPF_K,
|
| + hi,
|
| + RetExpression(gen, ErrorCode(ErrorCode::ERR_ALLOWED)),
|
| + rest)),
|
| + rest));
|
| +}
|
| +
|
| +Instruction* SandboxBPF::DispatchSyscall(CodeGen* gen) {
|
| + // Evaluate all possible system calls and group their ErrorCodes into
|
| + // ranges of identical codes.
|
| + Ranges ranges;
|
| + FindRanges(&ranges);
|
| +
|
| + // Compile the system call ranges to an optimized BPF jumptable
|
| + Instruction* jumptable = AssembleJumpTable(gen, ranges.begin(), ranges.end());
|
| +
|
| + // Grab the system call number, so that we can check it and then
|
| + // execute the jump table.
|
| + return gen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS,
|
| + SECCOMP_NR_IDX,
|
| + CheckSyscallNumber(gen, jumptable));
|
| +}
|
| +
|
| +Instruction* SandboxBPF::CheckSyscallNumber(CodeGen* gen, Instruction* passed) {
|
| + if (kIsIntel) {
|
| + // On Intel architectures, verify that system call numbers are in the
|
| + // expected number range.
|
| + Instruction* invalidX32 =
|
| + RetExpression(gen, Kill("Illegal mixing of system call ABIs"));
|
| + if (kIsX32) {
|
| + // The newer x32 API always sets bit 30.
|
| + return gen->MakeInstruction(
|
| + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, passed, invalidX32);
|
| + } else {
|
| + // The older i386 and x86-64 APIs clear bit 30 on all system calls.
|
| + return gen->MakeInstruction(
|
| + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, invalidX32, passed);
|
| + }
|
| + }
|
| +
|
| + // TODO(mdempsky): Similar validation for other architectures?
|
| + return passed;
|
| +}
|
| +
|
| void SandboxBPF::VerifyProgram(const Program& program, bool has_unsafe_traps) {
|
| // If we previously rewrote the BPF program so that it calls user-space
|
| // whenever we return an "errno" value from the filter, then we have to
|
| @@ -1028,16 +1057,12 @@ ErrorCode SandboxBPF::UnsafeTrap(Trap::TrapFnc fnc, const void* aux) {
|
| }
|
|
|
| bool SandboxBPF::IsRequiredForUnsafeTrap(int sysno) {
|
| - return (sysno == __NR_rt_sigprocmask || sysno == __NR_rt_sigreturn
|
| -#if defined(__NR_sigprocmask)
|
| - ||
|
| - sysno == __NR_sigprocmask
|
| -#endif
|
| -#if defined(__NR_sigreturn)
|
| - ||
|
| - sysno == __NR_sigreturn
|
| -#endif
|
| - );
|
| + for (size_t i = 0; i < arraysize(kSyscallsRequiredForUnsafeTraps); ++i) {
|
| + if (sysno == kSyscallsRequiredForUnsafeTraps[i]) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| }
|
|
|
| intptr_t SandboxBPF::ForwardSyscall(const struct arch_seccomp_data& args) {
|
|
|