summaryrefslogtreecommitdiff
path: root/sandbox/linux/bpf_dsl/policy_compiler.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/linux/bpf_dsl/policy_compiler.cc')
-rw-r--r--sandbox/linux/bpf_dsl/policy_compiler.cc466
1 files changed, 0 insertions, 466 deletions
diff --git a/sandbox/linux/bpf_dsl/policy_compiler.cc b/sandbox/linux/bpf_dsl/policy_compiler.cc
deleted file mode 100644
index 7ce517a5d5..0000000000
--- a/sandbox/linux/bpf_dsl/policy_compiler.cc
+++ /dev/null
@@ -1,466 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sandbox/linux/bpf_dsl/policy_compiler.h"
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/syscall.h>
-
-#include <limits>
-
-#include "base/logging.h"
-#include "base/macros.h"
-#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
-#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h"
-#include "sandbox/linux/bpf_dsl/codegen.h"
-#include "sandbox/linux/bpf_dsl/policy.h"
-#include "sandbox/linux/bpf_dsl/seccomp_macros.h"
-#include "sandbox/linux/bpf_dsl/syscall_set.h"
-#include "sandbox/linux/system_headers/linux_filter.h"
-#include "sandbox/linux/system_headers/linux_seccomp.h"
-#include "sandbox/linux/system_headers/linux_syscalls.h"
-
-namespace sandbox {
-namespace bpf_dsl {
-
-namespace {
-
-#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;
-}
-
-ResultExpr DefaultPanic(const char* error) {
- return Kill();
-}
-
-// A Trap() handler that returns an "errno" value. The value is encoded
-// in the "aux" parameter.
-intptr_t ReturnErrno(const struct arch_seccomp_data&, void* aux) {
- // TrapFnc functions report error by following the native kernel convention
- // of returning an exit code in the range of -1..-4096. They do not try to
- // set errno themselves. The glibc wrapper that triggered the SIGSYS will
- // ultimately do so for us.
- int err = reinterpret_cast<intptr_t>(aux) & SECCOMP_RET_DATA;
- return -err;
-}
-
-bool HasUnsafeTraps(const Policy* policy) {
- DCHECK(policy);
- for (uint32_t sysnum : SyscallSet::ValidOnly()) {
- if (policy->EvaluateSyscall(sysnum)->HasUnsafeTraps()) {
- return true;
- }
- }
- return policy->InvalidSyscall()->HasUnsafeTraps();
-}
-
-} // namespace
-
-struct PolicyCompiler::Range {
- uint32_t from;
- CodeGen::Node node;
-};
-
-PolicyCompiler::PolicyCompiler(const Policy* policy, TrapRegistry* registry)
- : policy_(policy),
- registry_(registry),
- escapepc_(0),
- panic_func_(DefaultPanic),
- gen_(),
- has_unsafe_traps_(HasUnsafeTraps(policy_)) {
- DCHECK(policy);
-}
-
-PolicyCompiler::~PolicyCompiler() {
-}
-
-CodeGen::Program PolicyCompiler::Compile() {
- CHECK(policy_->InvalidSyscall()->IsDeny())
- << "Policies should deny invalid system calls";
-
- // If our BPF program has unsafe traps, enable support for them.
- if (has_unsafe_traps_) {
- CHECK_NE(0U, escapepc_) << "UnsafeTrap() requires a valid escape PC";
-
- for (int sysnum : kSyscallsRequiredForUnsafeTraps) {
- CHECK(policy_->EvaluateSyscall(sysnum)->IsAllow())
- << "Policies that use UnsafeTrap() must unconditionally allow all "
- "required system calls";
- }
-
- CHECK(registry_->EnableUnsafeTraps())
- << "We'd rather die than enable unsafe traps";
- }
-
- // Assemble the BPF filter program.
- return gen_.Compile(AssemblePolicy());
-}
-
-void PolicyCompiler::DangerousSetEscapePC(uint64_t escapepc) {
- escapepc_ = escapepc;
-}
-
-void PolicyCompiler::SetPanicFunc(PanicFunc panic_func) {
- panic_func_ = panic_func;
-}
-
-CodeGen::Node PolicyCompiler::AssemblePolicy() {
- // 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(MaybeAddEscapeHatch(DispatchSyscall()));
-}
-
-CodeGen::Node PolicyCompiler::CheckArch(CodeGen::Node 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,
- CompileResult(panic_func_(
- "Invalid audit architecture in BPF filter"))));
-}
-
-CodeGen::Node PolicyCompiler::MaybeAddEscapeHatch(CodeGen::Node rest) {
- // If no unsafe traps, then simply return |rest|.
- if (!has_unsafe_traps_) {
- return rest;
- }
-
- // We already enabled unsafe traps in Compile, but enable them again to give
- // the trap registry a second chance to complain before we add the backdoor.
- CHECK(registry_->EnableUnsafeTraps());
-
- // Allow system calls, if they originate from our magic return address.
- const uint32_t lopc = static_cast<uint32_t>(escapepc_);
- const uint32_t hipc = static_cast<uint32_t>(escapepc_ >> 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, lopc,
- gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_MSB_IDX,
- gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, hipc,
- CompileResult(Allow()), rest)),
- rest));
-}
-
-CodeGen::Node PolicyCompiler::DispatchSyscall() {
- // Evaluate all possible system calls and group their Nodes into
- // ranges of identical codes.
- Ranges ranges;
- FindRanges(&ranges);
-
- // Compile the system call ranges to an optimized BPF jumptable
- CodeGen::Node jumptable = AssembleJumpTable(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(jumptable));
-}
-
-CodeGen::Node PolicyCompiler::CheckSyscallNumber(CodeGen::Node passed) {
- if (kIsIntel) {
- // On Intel architectures, verify that system call numbers are in the
- // expected number range.
- CodeGen::Node invalidX32 =
- CompileResult(panic_func_("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 PolicyCompiler::FindRanges(Ranges* ranges) {
- // Please note that "struct seccomp_data" defines system calls as a signed
- // int32_t, but BPF instructions always operate on unsigned quantities. We
- // deal with this disparity by enumerating from MIN_SYSCALL to MAX_SYSCALL,
- // and then verifying that the rest of the number range (both positive and
- // negative) all return the same Node.
- const CodeGen::Node invalid_node = CompileResult(policy_->InvalidSyscall());
- uint32_t old_sysnum = 0;
- CodeGen::Node old_node =
- SyscallSet::IsValid(old_sysnum)
- ? CompileResult(policy_->EvaluateSyscall(old_sysnum))
- : invalid_node;
-
- for (uint32_t sysnum : SyscallSet::All()) {
- CodeGen::Node node =
- SyscallSet::IsValid(sysnum)
- ? CompileResult(policy_->EvaluateSyscall(static_cast<int>(sysnum)))
- : invalid_node;
- // N.B., here we rely on CodeGen folding (i.e., returning the same
- // node value for) identical code sequences, otherwise our jump
- // table will blow up in size.
- if (node != old_node) {
- ranges->push_back(Range{old_sysnum, old_node});
- old_sysnum = sysnum;
- old_node = node;
- }
- }
- ranges->push_back(Range{old_sysnum, old_node});
-}
-
-CodeGen::Node PolicyCompiler::AssembleJumpTable(Ranges::const_iterator start,
- Ranges::const_iterator stop) {
- // We convert the list of system call ranges into jump table that performs
- // a binary search over the ranges.
- // As a sanity check, we need to have at least one distinct ranges for us
- // to be able to build a jump table.
- CHECK(start < stop) << "Invalid iterator range";
- const auto n = stop - start;
- if (n == 1) {
- // If we have narrowed things down to a single range object, we can
- // return from the BPF filter program.
- return start->node;
- }
-
- // Pick the range object that is located at the mid point of our list.
- // We compare our system call number against the lowest valid system call
- // number in this range object. If our number is lower, it is outside of
- // this range object. If it is greater or equal, it might be inside.
- Ranges::const_iterator mid = start + n / 2;
-
- // Sub-divide the list of ranges and continue recursively.
- CodeGen::Node jf = AssembleJumpTable(start, mid);
- CodeGen::Node jt = AssembleJumpTable(mid, stop);
- return gen_.MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf);
-}
-
-CodeGen::Node PolicyCompiler::CompileResult(const ResultExpr& res) {
- return res->Compile(this);
-}
-
-CodeGen::Node PolicyCompiler::MaskedEqual(int argno,
- size_t width,
- uint64_t mask,
- uint64_t value,
- CodeGen::Node passed,
- CodeGen::Node failed) {
- // Sanity check that arguments make sense.
- CHECK(argno >= 0 && argno < 6) << "Invalid argument number " << argno;
- CHECK(width == 4 || width == 8) << "Invalid argument width " << width;
- CHECK_NE(0U, mask) << "Zero mask is invalid";
- CHECK_EQ(value, value & mask) << "Value contains masked out bits";
- if (sizeof(void*) == 4) {
- CHECK_EQ(4U, width) << "Invalid width on 32-bit platform";
- }
- if (width == 4) {
- CHECK_EQ(0U, mask >> 32) << "Mask exceeds argument size";
- CHECK_EQ(0U, value >> 32) << "Value exceeds argument size";
- }
-
- // We want to emit code to check "(arg & mask) == value" where arg, mask, and
- // value are 64-bit values, but the BPF machine is only 32-bit. We implement
- // this by independently testing the upper and lower 32-bits and continuing to
- // |passed| if both evaluate true, or to |failed| if either evaluate false.
- return MaskedEqualHalf(argno, width, mask, value, ArgHalf::UPPER,
- MaskedEqualHalf(argno, width, mask, value,
- ArgHalf::LOWER, passed, failed),
- failed);
-}
-
-CodeGen::Node PolicyCompiler::MaskedEqualHalf(int argno,
- size_t width,
- uint64_t full_mask,
- uint64_t full_value,
- ArgHalf half,
- CodeGen::Node passed,
- CodeGen::Node failed) {
- if (width == 4 && half == ArgHalf::UPPER) {
- // Special logic for sanity checking the upper 32-bits of 32-bit system
- // call arguments.
-
- // TODO(mdempsky): Compile Unexpected64bitArgument() just per program.
- CodeGen::Node invalid_64bit = Unexpected64bitArgument();
-
- const uint32_t upper = SECCOMP_ARG_MSB_IDX(argno);
- const uint32_t lower = SECCOMP_ARG_LSB_IDX(argno);
-
- if (sizeof(void*) == 4) {
- // On 32-bit platforms, the upper 32-bits should always be 0:
- // LDW [upper]
- // JEQ 0, passed, invalid
- return gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS,
- upper,
- gen_.MakeInstruction(
- BPF_JMP + BPF_JEQ + BPF_K, 0, passed, invalid_64bit));
- }
-
- // On 64-bit platforms, the upper 32-bits may be 0 or ~0; but we only allow
- // ~0 if the sign bit of the lower 32-bits is set too:
- // LDW [upper]
- // JEQ 0, passed, (next)
- // JEQ ~0, (next), invalid
- // LDW [lower]
- // JSET (1<<31), passed, invalid
- //
- // TODO(mdempsky): The JSET instruction could perhaps jump to passed->next
- // instead, as the first instruction of passed should be "LDW [lower]".
- return gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS,
- upper,
- gen_.MakeInstruction(
- BPF_JMP + BPF_JEQ + BPF_K,
- 0,
- passed,
- gen_.MakeInstruction(
- BPF_JMP + BPF_JEQ + BPF_K,
- std::numeric_limits<uint32_t>::max(),
- gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS,
- lower,
- gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K,
- 1U << 31,
- passed,
- invalid_64bit)),
- invalid_64bit)));
- }
-
- const uint32_t idx = (half == ArgHalf::UPPER) ? SECCOMP_ARG_MSB_IDX(argno)
- : SECCOMP_ARG_LSB_IDX(argno);
- const uint32_t mask = (half == ArgHalf::UPPER) ? full_mask >> 32 : full_mask;
- const uint32_t value =
- (half == ArgHalf::UPPER) ? full_value >> 32 : full_value;
-
- // Emit a suitable instruction sequence for (arg & mask) == value.
-
- // For (arg & 0) == 0, just return passed.
- if (mask == 0) {
- CHECK_EQ(0U, value);
- return passed;
- }
-
- // For (arg & ~0) == value, emit:
- // LDW [idx]
- // JEQ value, passed, failed
- if (mask == std::numeric_limits<uint32_t>::max()) {
- return gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS,
- idx,
- gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed));
- }
-
- // For (arg & mask) == 0, emit:
- // LDW [idx]
- // JSET mask, failed, passed
- // (Note: failed and passed are intentionally swapped.)
- if (value == 0) {
- return gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS,
- idx,
- gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, failed, passed));
- }
-
- // For (arg & x) == x where x is a single-bit value, emit:
- // LDW [idx]
- // JSET mask, passed, failed
- if (mask == value && HasExactlyOneBit(mask)) {
- return gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS,
- idx,
- gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, passed, failed));
- }
-
- // Generic fallback:
- // LDW [idx]
- // AND mask
- // JEQ value, passed, failed
- return gen_.MakeInstruction(
- BPF_LD + BPF_W + BPF_ABS,
- idx,
- gen_.MakeInstruction(
- BPF_ALU + BPF_AND + BPF_K,
- mask,
- gen_.MakeInstruction(
- BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed)));
-}
-
-CodeGen::Node PolicyCompiler::Unexpected64bitArgument() {
- return CompileResult(panic_func_("Unexpected 64bit argument detected"));
-}
-
-CodeGen::Node PolicyCompiler::Return(uint32_t ret) {
- if (has_unsafe_traps_ && (ret & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) {
- // When inside an UnsafeTrap() callback, we want to allow all system calls.
- // This means, we must conditionally disable the sandbox -- and that's not
- // something that kernel-side BPF filters can do, as they cannot inspect
- // any state other than the syscall arguments.
- // But if we redirect all error handlers to user-space, then we can easily
- // make this decision.
- // The performance penalty for this extra round-trip to user-space is not
- // actually that bad, as we only ever pay it for denied system calls; and a
- // typical program has very few of these.
- return Trap(ReturnErrno, reinterpret_cast<void*>(ret & SECCOMP_RET_DATA),
- true);
- }
-
- return gen_.MakeInstruction(BPF_RET + BPF_K, ret);
-}
-
-CodeGen::Node PolicyCompiler::Trap(TrapRegistry::TrapFnc fnc,
- const void* aux,
- bool safe) {
- uint16_t trap_id = registry_->Add(fnc, aux, safe);
- return gen_.MakeInstruction(BPF_RET + BPF_K, SECCOMP_RET_TRAP + trap_id);
-}
-
-bool PolicyCompiler::IsRequiredForUnsafeTrap(int sysno) {
- for (size_t i = 0; i < arraysize(kSyscallsRequiredForUnsafeTraps); ++i) {
- if (sysno == kSyscallsRequiredForUnsafeTraps[i]) {
- return true;
- }
- }
- return false;
-}
-
-} // namespace bpf_dsl
-} // namespace sandbox