diff options
author | mdempsky <mdempsky@chromium.org> | 2014-10-16 09:49:54 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-16 16:50:17 +0000 |
commit | 27e78ad7c43bd01d607d414552cffdfe92e9dece (patch) | |
tree | df02c5b0120d38b89aa6eadd7b360a0c2a62baee /sandbox/linux/seccomp-bpf | |
parent | 3a721cdeaa08279e20010c88e282b4a12bcc3ee5 (diff) | |
download | chromium_src-27e78ad7c43bd01d607d414552cffdfe92e9dece.zip chromium_src-27e78ad7c43bd01d607d414552cffdfe92e9dece.tar.gz chromium_src-27e78ad7c43bd01d607d414552cffdfe92e9dece.tar.bz2 |
bpf_dsl: add TrapRegistry and extract PolicyCompiler
This splits out the policy-to-BPF-program handling code from
SandboxBPF into a new "PolicyCompiler" class. Additionally, it
extracts an interface "TrapRegistry" to decouple PolicyCompiler from
Trap (which should probably eventually be renamed something like
SIGSYSTrapRegistry).
Most significantly this CL means bpf_dsl no longer depends on
SandboxBPF, which also now focuses primarily on the task of
installing a compiled policy.
BUG=414363
Review URL: https://codereview.chromium.org/660433002
Cr-Commit-Position: refs/heads/master@{#299905}
Diffstat (limited to 'sandbox/linux/seccomp-bpf')
-rw-r--r-- | sandbox/linux/seccomp-bpf/codegen.cc | 10 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/codegen.h | 21 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/codegen_unittest.cc | 7 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/errorcode.cc | 7 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/errorcode.h | 6 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/errorcode_unittest.cc | 50 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.cc | 562 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.h | 165 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc | 4 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/trap.cc | 19 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/trap.h | 43 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/verifier.cc | 38 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/verifier.h | 6 |
13 files changed, 132 insertions, 806 deletions
diff --git a/sandbox/linux/seccomp-bpf/codegen.cc b/sandbox/linux/seccomp-bpf/codegen.cc index 9c347930..8169840 100644 --- a/sandbox/linux/seccomp-bpf/codegen.cc +++ b/sandbox/linux/seccomp-bpf/codegen.cc @@ -13,6 +13,7 @@ #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/instruction.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" +#include "sandbox/linux/seccomp-bpf/trap.h" namespace sandbox { @@ -31,9 +32,8 @@ CodeGen::~CodeGen() { } } -void CodeGen::PrintProgram(const SandboxBPF::Program& program) { - for (SandboxBPF::Program::const_iterator iter = program.begin(); - iter != program.end(); +void CodeGen::PrintProgram(const Program& program) { + for (Program::const_iterator iter = program.begin(); iter != program.end(); ++iter) { int ip = (int)(iter - program.begin()); fprintf(stderr, "%3d) ", ip); @@ -654,7 +654,7 @@ void CodeGen::ComputeRelativeJumps(BasicBlocks* basic_blocks, } void CodeGen::ConcatenateBasicBlocks(const BasicBlocks& basic_blocks, - SandboxBPF::Program* program) { + Program* program) { // Our basic blocks have been sorted and relative jump offsets have been // computed. The last remaining step is for all the instructions in our // basic blocks to be concatenated into a BPF program. @@ -674,7 +674,7 @@ void CodeGen::ConcatenateBasicBlocks(const BasicBlocks& basic_blocks, return; } -void CodeGen::Compile(Instruction* instructions, SandboxBPF::Program* program) { +void CodeGen::Compile(Instruction* instructions, Program* program) { if (compiled_) { SANDBOX_DIE( "Cannot call Compile() multiple times. Create a new code " diff --git a/sandbox/linux/seccomp-bpf/codegen.h b/sandbox/linux/seccomp-bpf/codegen.h index 6081138..817b17c 100644 --- a/sandbox/linux/seccomp-bpf/codegen.h +++ b/sandbox/linux/seccomp-bpf/codegen.h @@ -5,12 +5,15 @@ #ifndef SANDBOX_LINUX_SECCOMP_BPF_CODEGEN_H__ #define SANDBOX_LINUX_SECCOMP_BPF_CODEGEN_H__ +#include <stdint.h> + #include <map> #include <vector> -#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" #include "sandbox/sandbox_export.h" +struct sock_filter; + namespace sandbox { struct BasicBlock; struct Instruction; @@ -28,7 +31,7 @@ typedef std::map<const BasicBlock*, int> IncomingBranches; // // Callers would typically create a new CodeGen object and then use it to // build a DAG of Instructions. They'll eventually call Compile() to convert -// this DAG to a SandboxBPF::Program. +// this DAG to a Program. // // CodeGen gen; // Instruction *allow, *branch, *dag; @@ -45,7 +48,7 @@ typedef std::map<const BasicBlock*, int> IncomingBranches; // // // Simplified code follows; in practice, it is important to avoid calling // // any C++ destructors after starting the sandbox. -// SandboxBPF::Program program; +// CodeGen::Program program; // gen.Compile(dag, program); // const struct sock_fprog prog = { // static_cast<unsigned short>(program->size()), &program[0] }; @@ -53,12 +56,16 @@ typedef std::map<const BasicBlock*, int> IncomingBranches; // class SANDBOX_EXPORT CodeGen { public: + // A vector of BPF instructions that need to be installed as a filter + // program in the kernel. + typedef std::vector<struct sock_filter> Program; + CodeGen(); ~CodeGen(); // This is a helper method that can be used for debugging purposes. It is // not normally called. - static void PrintProgram(const SandboxBPF::Program& program); + static void PrintProgram(const Program& program); // Create a new instruction. Instructions form a DAG. The instruction objects // are owned by the CodeGen object. They do not need to be explicitly @@ -66,7 +73,7 @@ class SANDBOX_EXPORT CodeGen { // For details on the possible parameters refer to <linux/filter.h> Instruction* MakeInstruction(uint16_t code, uint32_t k, - Instruction* next = NULL); + Instruction* next = nullptr); Instruction* MakeInstruction(uint16_t code, uint32_t k, Instruction* jt, @@ -75,7 +82,7 @@ class SANDBOX_EXPORT CodeGen { // Compiles the graph of instructions into a BPF program that can be passed // to the kernel. Please note that this function modifies the graph in place // and must therefore only be called once per graph. - void Compile(Instruction* instructions, SandboxBPF::Program* program); + void Compile(Instruction* instructions, Program* program); private: friend class CodeGenUnittestHelper; @@ -125,7 +132,7 @@ class SANDBOX_EXPORT CodeGen { // Concatenate instructions from all basic blocks into a BPF program that // can be passed to the kernel. - void ConcatenateBasicBlocks(const BasicBlocks&, SandboxBPF::Program* program); + void ConcatenateBasicBlocks(const BasicBlocks&, Program* program); // We stick all instructions and basic blocks into pools that get destroyed // when the CodeGen object is destroyed. This way, we neither need to worry diff --git a/sandbox/linux/seccomp-bpf/codegen_unittest.cc b/sandbox/linux/seccomp-bpf/codegen_unittest.cc index 5c1db24..d4760a5 100644 --- a/sandbox/linux/seccomp-bpf/codegen_unittest.cc +++ b/sandbox/linux/seccomp-bpf/codegen_unittest.cc @@ -19,11 +19,6 @@ namespace sandbox { -class SandboxUnittestHelper : public SandboxBPF { - public: - typedef SandboxBPF::Program Program; -}; - // We want to access some of the private methods in the code generator. We // do so by defining a "friend" that makes these methods public for us. class CodeGenUnittestHelper : public CodeGen { @@ -497,7 +492,7 @@ void CompileAndCompare(CodeGenUnittestHelper* codegen, Instruction* prg, int) { } // Compile the program - SandboxUnittestHelper::Program bpf; + CodeGen::Program bpf; codegen->Compile(prg, &bpf); // Serialize the resulting BPF instructions. diff --git a/sandbox/linux/seccomp-bpf/errorcode.cc b/sandbox/linux/seccomp-bpf/errorcode.cc index 8154f93..ebae130 100644 --- a/sandbox/linux/seccomp-bpf/errorcode.cc +++ b/sandbox/linux/seccomp-bpf/errorcode.cc @@ -32,12 +32,15 @@ ErrorCode::ErrorCode(int err) { } } -ErrorCode::ErrorCode(Trap::TrapFnc fnc, const void* aux, bool safe) +ErrorCode::ErrorCode(uint16_t trap_id, + Trap::TrapFnc fnc, + const void* aux, + bool safe) : error_type_(ET_TRAP), fnc_(fnc), aux_(const_cast<void*>(aux)), safe_(safe), - err_(SECCOMP_RET_TRAP + Trap::MakeTrap(fnc, aux, safe)) { + err_(SECCOMP_RET_TRAP + trap_id) { } ErrorCode::ErrorCode(int argno, diff --git a/sandbox/linux/seccomp-bpf/errorcode.h b/sandbox/linux/seccomp-bpf/errorcode.h index a322411..fb86fe8 100644 --- a/sandbox/linux/seccomp-bpf/errorcode.h +++ b/sandbox/linux/seccomp-bpf/errorcode.h @@ -9,6 +9,9 @@ #include "sandbox/sandbox_export.h" namespace sandbox { +namespace bpf_dsl { +class PolicyCompiler; +} // This class holds all the possible values that can be returned by a sandbox // policy. @@ -145,6 +148,7 @@ class SANDBOX_EXPORT ErrorCode { }; private: + friend bpf_dsl::PolicyCompiler; friend class CodeGen; friend class SandboxBPF; friend class Trap; @@ -152,7 +156,7 @@ class SANDBOX_EXPORT ErrorCode { // If we are wrapping a callback, we must assign a unique id. This id is // how the kernel tells us which one of our different SECCOMP_RET_TRAP // cases has been triggered. - ErrorCode(Trap::TrapFnc fnc, const void* aux, bool safe); + ErrorCode(uint16_t trap_id, Trap::TrapFnc fnc, const void* aux, bool safe); // Some system calls require inspection of arguments. This constructor // allows us to specify additional constraints. diff --git a/sandbox/linux/seccomp-bpf/errorcode_unittest.cc b/sandbox/linux/seccomp-bpf/errorcode_unittest.cc index 5a39373..41bd358 100644 --- a/sandbox/linux/seccomp-bpf/errorcode_unittest.cc +++ b/sandbox/linux/seccomp-bpf/errorcode_unittest.cc @@ -6,14 +6,30 @@ #include <errno.h> +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" -#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/trap.h" #include "sandbox/linux/tests/unit_tests.h" namespace sandbox { namespace { +class DummyPolicy : public bpf_dsl::SandboxBPFDSLPolicy { + public: + DummyPolicy() {} + virtual ~DummyPolicy() {} + + virtual bpf_dsl::ResultExpr EvaluateSyscall(int sysno) const override { + return bpf_dsl::Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(DummyPolicy); +}; + SANDBOX_TEST(ErrorCode, ErrnoConstructor) { ErrorCode e0; SANDBOX_ASSERT(e0.err() == SECCOMP_RET_INVALID); @@ -24,8 +40,9 @@ SANDBOX_TEST(ErrorCode, ErrnoConstructor) { ErrorCode e2(EPERM); SANDBOX_ASSERT(e2.err() == SECCOMP_RET_ERRNO + EPERM); - SandboxBPF sandbox; - ErrorCode e3 = sandbox.Trap(NULL, NULL); + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e3 = compiler.Trap(NULL, NULL); SANDBOX_ASSERT((e3.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP); uint16_t data = 0xdead; @@ -41,13 +58,14 @@ SANDBOX_DEATH_TEST(ErrorCode, } SANDBOX_TEST(ErrorCode, Trap) { - SandboxBPF sandbox; - ErrorCode e0 = sandbox.Trap(NULL, "a"); - ErrorCode e1 = sandbox.Trap(NULL, "b"); + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e0 = compiler.Trap(NULL, "a"); + ErrorCode e1 = compiler.Trap(NULL, "b"); SANDBOX_ASSERT((e0.err() & SECCOMP_RET_DATA) + 1 == (e1.err() & SECCOMP_RET_DATA)); - ErrorCode e2 = sandbox.Trap(NULL, "a"); + ErrorCode e2 = compiler.Trap(NULL, "a"); SANDBOX_ASSERT((e0.err() & SECCOMP_RET_DATA) == (e2.err() & SECCOMP_RET_DATA)); } @@ -62,10 +80,11 @@ SANDBOX_TEST(ErrorCode, Equals) { ErrorCode e3(EPERM); SANDBOX_ASSERT(!e1.Equals(e3)); - SandboxBPF sandbox; - ErrorCode e4 = sandbox.Trap(NULL, "a"); - ErrorCode e5 = sandbox.Trap(NULL, "b"); - ErrorCode e6 = sandbox.Trap(NULL, "a"); + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e4 = compiler.Trap(NULL, "a"); + ErrorCode e5 = compiler.Trap(NULL, "b"); + ErrorCode e6 = compiler.Trap(NULL, "a"); SANDBOX_ASSERT(!e1.Equals(e4)); SANDBOX_ASSERT(!e3.Equals(e4)); SANDBOX_ASSERT(!e5.Equals(e4)); @@ -83,10 +102,11 @@ SANDBOX_TEST(ErrorCode, LessThan) { SANDBOX_ASSERT(!e1.LessThan(e3)); SANDBOX_ASSERT( e3.LessThan(e1)); - SandboxBPF sandbox; - ErrorCode e4 = sandbox.Trap(NULL, "a"); - ErrorCode e5 = sandbox.Trap(NULL, "b"); - ErrorCode e6 = sandbox.Trap(NULL, "a"); + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e4 = compiler.Trap(NULL, "a"); + ErrorCode e5 = compiler.Trap(NULL, "b"); + ErrorCode e6 = compiler.Trap(NULL, "a"); SANDBOX_ASSERT(e1.LessThan(e4)); SANDBOX_ASSERT(e3.LessThan(e4)); SANDBOX_ASSERT(e4.LessThan(e5)); diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index 58b6390..f1073c1 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -23,18 +23,16 @@ #include <time.h> #include <unistd.h> -#include <limits> - #include "base/compiler_specific.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/posix/eintr_wrapper.h" #include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" #include "sandbox/linux/seccomp-bpf/codegen.h" #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/errorcode.h" -#include "sandbox/linux/seccomp-bpf/instruction.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" #include "sandbox/linux/seccomp-bpf/syscall.h" #include "sandbox/linux/seccomp-bpf/syscall_iterator.h" @@ -53,33 +51,6 @@ 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; -} - #if !defined(NDEBUG) void WriteFailedStderrSetupMessage(int out_fd) { const char* error_string = strerror(errno); @@ -169,52 +140,13 @@ bool IsSingleThreaded(int proc_fd) { return true; } -bool IsDenied(const ErrorCode& code) { - return (code.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP || - (code.err() >= (SECCOMP_RET_ERRNO + ErrorCode::ERR_MIN_ERRNO) && - code.err() <= (SECCOMP_RET_ERRNO + ErrorCode::ERR_MAX_ERRNO)); -} - -// 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; -} - -intptr_t BPFFailure(const struct arch_seccomp_data&, void* aux) { - SANDBOX_DIE(static_cast<char*>(aux)); -} - } // namespace SandboxBPF::SandboxBPF() - : quiet_(false), - proc_fd_(-1), - conds_(new Conds), - sandbox_has_started_(false), - has_unsafe_traps_(false) { + : quiet_(false), proc_fd_(-1), sandbox_has_started_(false), policy_() { } SandboxBPF::~SandboxBPF() { - // It is generally unsafe to call any memory allocator operations or to even - // call arbitrary destructors after having installed a new policy. We just - // have no way to tell whether this policy would allow the system calls that - // the constructors can trigger. - // So, we normally destroy all of our complex state prior to starting the - // sandbox. But this won't happen, if the Sandbox object was created and - // never actually used to set up a sandbox. So, just in case, we are - // destroying any remaining state. - // The "if ()" statements are technically superfluous. But let's be explicit - // that we really don't want to run any code, when we already destroyed - // objects before setting up the sandbox. - if (conds_) { - delete conds_; - } } bool SandboxBPF::IsValidSyscallNumber(int sysnum) { @@ -435,7 +367,7 @@ bool SandboxBPF::StartSandbox(SandboxThreadState thread_state) { "Trying to start sandbox, even though it is known to be " "unavailable"); return false; - } else if (sandbox_has_started_ || !conds_) { + } else if (sandbox_has_started_) { SANDBOX_DIE( "Cannot repeatedly start sandbox. Create a separate Sandbox " "object instead."); @@ -490,20 +422,12 @@ bool SandboxBPF::StartSandbox(SandboxThreadState thread_state) { return true; } -void SandboxBPF::PolicySanityChecks(bpf_dsl::SandboxBPFDSLPolicy* policy) { - if (!IsDenied(policy->InvalidSyscall(this))) { - SANDBOX_DIE("Policies should deny invalid system calls."); - } - return; -} - // Don't take a scoped_ptr here, polymorphism make their use awkward. void SandboxBPF::SetSandboxPolicy(bpf_dsl::SandboxBPFDSLPolicy* policy) { DCHECK(!policy_); - if (sandbox_has_started_ || !conds_) { + if (sandbox_has_started_) { SANDBOX_DIE("Cannot change policy after sandbox has started"); } - PolicySanityChecks(policy); policy_.reset(policy); } @@ -519,7 +443,7 @@ void SandboxBPF::InstallFilter(bool must_sync_threads) { // installed the BPF filter program in the kernel. Depending on the // system memory allocator that is in effect, these operators can result // in system calls to things like munmap() or brk(). - Program* program = AssembleFilter(false /* force_verification */); + CodeGen::Program* program = AssembleFilter(false).release(); struct sock_filter bpf[program->size()]; const struct sock_fprog prog = {static_cast<unsigned short>(program->size()), @@ -530,8 +454,6 @@ void SandboxBPF::InstallFilter(bool must_sync_threads) { // Make an attempt to release memory that is no longer needed here, rather // than in the destructor. Try to avoid as much as possible to presume of // what will be possible to do in the new (sandboxed) execution environment. - delete conds_; - conds_ = NULL; policy_.reset(); if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { @@ -557,57 +479,14 @@ void SandboxBPF::InstallFilter(bool must_sync_threads) { sandbox_has_started_ = true; } -SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) { +scoped_ptr<CodeGen::Program> SandboxBPF::AssembleFilter( + bool force_verification) { #if !defined(NDEBUG) force_verification = true; #endif - // Verify that the user pushed a policy. - DCHECK(policy_); - - // If our BPF program has unsafe traps, enable support for them. - has_unsafe_traps_ = policy_->HasUnsafeTraps(); - if (has_unsafe_traps_) { - // 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"); - } - } - - // Assemble the BPF filter program. - CodeGen* gen = new CodeGen(); - if (!gen) { - SANDBOX_DIE("Out of memory"); - } - Instruction* head = CompilePolicy(gen); - - // Turn the DAG into a vector of instructions. - Program* program = new Program(); - gen->Compile(head, program); - delete gen; + bpf_dsl::PolicyCompiler compiler(policy_.get(), Trap::Registry()); + scoped_ptr<CodeGen::Program> program = compiler.Compile(); // Make sure compilation resulted in BPF program that executes // correctly. Otherwise, there is an internal error in our BPF compiler. @@ -616,371 +495,19 @@ SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) { // Verification is expensive. We only perform this step, if we are // compiled in debug mode, or if the caller explicitly requested // verification. - VerifyProgram(*program); - } - - return program; -} - -Instruction* SandboxBPF::CompilePolicy(CodeGen* gen) { - // 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, 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, - Instruction* rest) { - // If no unsafe traps, then simply return |rest|. - if (!has_unsafe_traps_) { - return rest; - } - - // 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) { - const char* err = NULL; - if (!Verifier::VerifyBPF(this, program, *policy_, &err)) { - CodeGen::PrintProgram(program); - SANDBOX_DIE(err); - } -} - -void SandboxBPF::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 ErrorCode. - const ErrorCode invalid_err = policy_->InvalidSyscall(this); - uint32_t old_sysnum = 0; - ErrorCode old_err = IsValidSyscallNumber(old_sysnum) - ? policy_->EvaluateSyscall(this, old_sysnum) - : invalid_err; - - for (SyscallIterator iter(false); !iter.Done();) { - uint32_t sysnum = iter.Next(); - ErrorCode err = - IsValidSyscallNumber(sysnum) - ? policy_->EvaluateSyscall(this, static_cast<int>(sysnum)) - : invalid_err; - if (!err.Equals(old_err) || iter.Done()) { - ranges->push_back(Range(old_sysnum, sysnum - 1, old_err)); - old_sysnum = sysnum; - old_err = err; - } - } -} - -Instruction* SandboxBPF::AssembleJumpTable(CodeGen* gen, - 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. - if (stop - start <= 0) { - SANDBOX_DIE("Invalid set of system call ranges"); - } else if (stop - start == 1) { - // If we have narrowed things down to a single range object, we can - // return from the BPF filter program. - return RetExpression(gen, start->err); - } - - // 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 + (stop - start) / 2; - - // Sub-divide the list of ranges and continue recursively. - Instruction* jf = AssembleJumpTable(gen, start, mid); - Instruction* jt = AssembleJumpTable(gen, mid, stop); - return gen->MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf); -} - -Instruction* SandboxBPF::RetExpression(CodeGen* gen, const ErrorCode& err) { - switch (err.error_type()) { - case ErrorCode::ET_COND: - return CondExpression(gen, err); - case ErrorCode::ET_SIMPLE: - case ErrorCode::ET_TRAP: - return gen->MakeInstruction(BPF_RET + BPF_K, err.err()); - default: - SANDBOX_DIE("ErrorCode is not suitable for returning from a BPF program"); - } -} -Instruction* SandboxBPF::CondExpression(CodeGen* gen, const ErrorCode& cond) { - // Sanity check that |cond| makes sense. - if (cond.argno_ < 0 || cond.argno_ >= 6) { - SANDBOX_DIE("sandbox_bpf: invalid argument number"); - } - if (cond.width_ != ErrorCode::TP_32BIT && - cond.width_ != ErrorCode::TP_64BIT) { - SANDBOX_DIE("sandbox_bpf: invalid argument width"); - } - if (cond.mask_ == 0) { - SANDBOX_DIE("sandbox_bpf: zero mask is invalid"); - } - if ((cond.value_ & cond.mask_) != cond.value_) { - SANDBOX_DIE("sandbox_bpf: value contains masked out bits"); - } - if (cond.width_ == ErrorCode::TP_32BIT && - ((cond.mask_ >> 32) != 0 || (cond.value_ >> 32) != 0)) { - SANDBOX_DIE("sandbox_bpf: test exceeds argument size"); - } - // TODO(mdempsky): Reject TP_64BIT on 32-bit platforms. For now we allow it - // because some SandboxBPF unit tests exercise it. - - Instruction* passed = RetExpression(gen, *cond.passed_); - Instruction* failed = RetExpression(gen, *cond.failed_); - - // 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 CondExpressionHalf( - gen, - cond, - UpperHalf, - CondExpressionHalf(gen, cond, LowerHalf, passed, failed), - failed); -} - -Instruction* SandboxBPF::CondExpressionHalf(CodeGen* gen, - const ErrorCode& cond, - ArgHalf half, - Instruction* passed, - Instruction* failed) { - if (cond.width_ == ErrorCode::TP_32BIT && half == UpperHalf) { - // Special logic for sanity checking the upper 32-bits of 32-bit system - // call arguments. - - // TODO(mdempsky): Compile Unexpected64bitArgument() just per program. - Instruction* invalid_64bit = RetExpression(gen, Unexpected64bitArgument()); - - const uint32_t upper = SECCOMP_ARG_MSB_IDX(cond.argno_); - const uint32_t lower = SECCOMP_ARG_LSB_IDX(cond.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)); + const char* err = NULL; + if (!Verifier::VerifyBPF(&compiler, *program, *policy_, &err)) { + CodeGen::PrintProgram(*program); + SANDBOX_DIE(err); } - - // 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 == UpperHalf) ? SECCOMP_ARG_MSB_IDX(cond.argno_) - : SECCOMP_ARG_LSB_IDX(cond.argno_); - const uint32_t mask = (half == UpperHalf) ? cond.mask_ >> 32 : cond.mask_; - const uint32_t value = (half == UpperHalf) ? cond.value_ >> 32 : cond.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))); -} - -ErrorCode SandboxBPF::Unexpected64bitArgument() { - return Kill("Unexpected 64bit argument detected"); -} - -ErrorCode SandboxBPF::Error(int err) { - if (has_unsafe_traps_) { - // 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*>(err)); - } - - return ErrorCode(err); -} - -ErrorCode SandboxBPF::Trap(Trap::TrapFnc fnc, const void* aux) { - return ErrorCode(fnc, aux, true /* Safe Trap */); -} - -ErrorCode SandboxBPF::UnsafeTrap(Trap::TrapFnc fnc, const void* aux) { - return ErrorCode(fnc, aux, false /* Unsafe Trap */); + return program.Pass(); } bool SandboxBPF::IsRequiredForUnsafeTrap(int sysno) { - for (size_t i = 0; i < arraysize(kSyscallsRequiredForUnsafeTraps); ++i) { - if (sysno == kSyscallsRequiredForUnsafeTraps[i]) { - return true; - } - } - return false; + return bpf_dsl::PolicyCompiler::IsRequiredForUnsafeTrap(sysno); } intptr_t SandboxBPF::ForwardSyscall(const struct arch_seccomp_data& args) { @@ -993,65 +520,6 @@ intptr_t SandboxBPF::ForwardSyscall(const struct arch_seccomp_data& args) { static_cast<intptr_t>(args.args[5])); } -ErrorCode SandboxBPF::CondMaskedEqual(int argno, - ErrorCode::ArgType width, - uint64_t mask, - uint64_t value, - const ErrorCode& passed, - const ErrorCode& failed) { - return ErrorCode(argno, - width, - mask, - value, - &*conds_->insert(passed).first, - &*conds_->insert(failed).first); -} - -ErrorCode SandboxBPF::Cond(int argno, - ErrorCode::ArgType width, - ErrorCode::Operation op, - uint64_t value, - const ErrorCode& passed, - const ErrorCode& failed) { - // CondExpression() currently rejects mask==0 as invalid, but there are - // SandboxBPF unit tests that (questionably) expect OP_HAS_{ANY,ALL}_BITS to - // work with value==0. To keep those tests working for now, we specially - // convert value==0 here. - - switch (op) { - case ErrorCode::OP_EQUAL: { - // Convert to "(arg & ~0) == value". - const uint64_t mask = (width == ErrorCode::TP_64BIT) - ? std::numeric_limits<uint64_t>::max() - : std::numeric_limits<uint32_t>::max(); - return CondMaskedEqual(argno, width, mask, value, passed, failed); - } - - case ErrorCode::OP_HAS_ALL_BITS: - if (value == 0) { - // Always passes. - return passed; - } - // Convert to "(arg & value) == value". - return CondMaskedEqual(argno, width, value, value, passed, failed); - - case ErrorCode::OP_HAS_ANY_BITS: - if (value == 0) { - // Always fails. - return failed; - } - // Convert to "(arg & value) == 0", but swap passed and failed. - return CondMaskedEqual(argno, width, value, 0, failed, passed); - - default: - SANDBOX_DIE("Not implemented"); - } -} - -ErrorCode SandboxBPF::Kill(const char* msg) { - return Trap(BPFFailure, const_cast<char*>(msg)); -} - SandboxBPF::SandboxStatus SandboxBPF::status_ = STATUS_UNKNOWN; } // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h index 2bffad6..866e764 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -7,25 +7,17 @@ #include <stdint.h> -#include <map> -#include <set> -#include <vector> - #include "base/compiler_specific.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" -#include "sandbox/linux/seccomp-bpf/errorcode.h" -#include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/linux/seccomp-bpf/codegen.h" #include "sandbox/sandbox_export.h" -struct sock_filter; - namespace sandbox { +struct arch_seccomp_data; namespace bpf_dsl { class SandboxBPFDSLPolicy; } -class CodeGen; -class SandboxUnittestHelper; -struct Instruction; class SANDBOX_EXPORT SandboxBPF { public: @@ -49,10 +41,6 @@ class SANDBOX_EXPORT SandboxBPF { PROCESS_MULTI_THREADED, // The program may be multi-threaded. }; - // A vector of BPF instructions that need to be installed as a filter - // program in the kernel. - typedef std::vector<struct sock_filter> Program; - // Constructors and destructors. // NOTE: Setting a policy and starting the sandbox is a one-way operation. // The kernel does not provide any option for unloading a loaded @@ -93,30 +81,6 @@ class SANDBOX_EXPORT SandboxBPF { // to the sandbox object. void SetSandboxPolicy(bpf_dsl::SandboxBPFDSLPolicy* policy); - // Error returns an ErrorCode to indicate the system call should fail with - // the specified error number. - ErrorCode Error(int err); - - // We can use ErrorCode to request calling of a trap handler. This method - // performs the required wrapping of the callback function into an - // ErrorCode object. - // The "aux" field can carry a pointer to arbitrary data. See EvaluateSyscall - // for a description of how to pass data from SetSandboxPolicy() to a Trap() - // handler. - static ErrorCode Trap(Trap::TrapFnc fnc, const void* aux); - - // Calls a user-space trap handler and disables all sandboxing for system - // calls made from this trap handler. - // This feature is available only if explicitly enabled by the user having - // set the CHROME_SANDBOX_DEBUGGING environment variable. - // Returns an ET_INVALID ErrorCode, if called when not enabled. - // NOTE: This feature, by definition, disables all security features of - // the sandbox. It should never be used in production, but it can be - // very useful to diagnose code that is incompatible with the sandbox. - // If even a single system call returns "UnsafeTrap", the security of - // entire sandbox should be considered compromised. - static ErrorCode UnsafeTrap(Trap::TrapFnc fnc, const void* aux); - // UnsafeTraps require some syscalls to always be allowed. // This helper function returns true for these calls. static bool IsRequiredForUnsafeTrap(int sysno); @@ -131,34 +95,6 @@ class SANDBOX_EXPORT SandboxBPF { // directly suitable as a return value for a trap handler. static intptr_t ForwardSyscall(const struct arch_seccomp_data& args); - // We can also use ErrorCode to request evaluation of a conditional - // statement based on inspection of system call parameters. - // This method wrap an ErrorCode object around the conditional statement. - // Argument "argno" (1..6) will be bitwise-AND'd with "mask" and compared - // to "value"; if equal, then "passed" will be returned, otherwise "failed". - // If "is32bit" is set, the argument must in the range of 0x0..(1u << 32 - 1) - // If it is outside this range, the sandbox treats the system call just - // the same as any other ABI violation (i.e. it aborts with an error - // message). - ErrorCode CondMaskedEqual(int argno, - ErrorCode::ArgType is_32bit, - uint64_t mask, - uint64_t value, - const ErrorCode& passed, - const ErrorCode& failed); - - // Legacy variant of CondMaskedEqual that supports a few comparison - // operations, which get converted into masked-equality comparisons. - ErrorCode Cond(int argno, - ErrorCode::ArgType is_32bit, - ErrorCode::Operation op, - uint64_t value, - const ErrorCode& passed, - const ErrorCode& failed); - - // Kill the program and print an error message. - static ErrorCode Kill(const char* msg); - // This is the main public entry point. It finds all system calls that // need rewriting, sets up the resources needed by the sandbox, and // enters Seccomp mode. @@ -182,35 +118,9 @@ class SANDBOX_EXPORT SandboxBPF { // through the verifier, iff the program was built in debug mode. // But by setting "force_verification", the caller can request that the // verifier is run unconditionally. This is useful for unittests. - Program* AssembleFilter(bool force_verification); - - // Returns the fatal ErrorCode that is used to indicate that somebody - // attempted to pass a 64bit value in a 32bit system call argument. - // This method is primarily needed for testing purposes. - static ErrorCode Unexpected64bitArgument(); + scoped_ptr<CodeGen::Program> AssembleFilter(bool force_verification); private: - friend class CodeGen; - friend class SandboxUnittestHelper; - friend class ErrorCode; - - struct Range { - Range(uint32_t f, uint32_t t, const ErrorCode& e) - : from(f), to(t), err(e) {} - uint32_t from, to; - ErrorCode err; - }; - typedef std::vector<Range> Ranges; - typedef std::map<uint32_t, ErrorCode> ErrMap; - typedef std::set<ErrorCode, struct ErrorCode::LessThan> Conds; - - // Used by CondExpressionHalf to track which half of the argument it's - // emitting instructions for. - enum ArgHalf { - LowerHalf, - UpperHalf, - }; - // Get a file descriptor pointing to "/proc", if currently available. int proc_fd() { return proc_fd_; } @@ -226,84 +136,21 @@ class SANDBOX_EXPORT SandboxBPF { // any other policies. bool KernelSupportSeccompBPF(); - // Verify that the current policy passes some basic sanity checks. - void PolicySanityChecks(bpf_dsl::SandboxBPFDSLPolicy* policy); - // Assembles and installs a filter based on the policy that has previously // been configured with SetSandboxPolicy(). void InstallFilter(bool must_sync_threads); - // Compile the configured policy into a complete instruction sequence. - Instruction* CompilePolicy(CodeGen* gen); - - // Return an instruction sequence that checks the - // arch_seccomp_data's "arch" field is valid, and then passes - // control to |passed| if so. - Instruction* CheckArch(CodeGen* gen, Instruction* passed); - - // If |has_unsafe_traps_| is true, returns an instruction sequence - // that allows all system calls from Syscall::Call(), and otherwise - // passes control to |rest|. Otherwise, simply returns |rest|. - Instruction* MaybeAddEscapeHatch(CodeGen* gen, - Instruction* rest); - - // Return an instruction sequence that loads and checks the system - // call number, performs a binary search, and then dispatches to an - // appropriate instruction sequence compiled from the current - // policy. - Instruction* DispatchSyscall(CodeGen* gen); - - // Return an instruction sequence that checks the system call number - // (expected to be loaded in register A) and if valid, passes - // control to |passed| (with register A still valid). - Instruction* CheckSyscallNumber(CodeGen* gen, Instruction* passed); - // Verify the correctness of a compiled program by comparing it against the // current policy. This function should only ever be called by unit tests and // by the sandbox internals. It should not be used by production code. - void VerifyProgram(const Program& program); - - // Finds all the ranges of system calls that need to be handled. Ranges are - // sorted in ascending order of system call numbers. There are no gaps in the - // ranges. System calls with identical ErrorCodes are coalesced into a single - // range. - void FindRanges(Ranges* ranges); - - // Returns a BPF program snippet that implements a jump table for the - // given range of system call numbers. This function runs recursively. - Instruction* AssembleJumpTable(CodeGen* gen, - Ranges::const_iterator start, - Ranges::const_iterator stop); - - // Returns a BPF program snippet that makes the BPF filter program exit - // with the given ErrorCode "err". N.B. the ErrorCode may very well be a - // conditional expression; if so, this function will recursively call - // CondExpression() and possibly RetExpression() to build a complex set of - // instructions. - Instruction* RetExpression(CodeGen* gen, const ErrorCode& err); - - // Returns a BPF program that evaluates the conditional expression in - // "cond" and returns the appropriate value from the BPF filter program. - // This function recursively calls RetExpression(); it should only ever be - // called from RetExpression(). - Instruction* CondExpression(CodeGen* gen, const ErrorCode& cond); - - // Returns a BPF program that evaluates half of a conditional expression; - // it should only ever be called from CondExpression(). - Instruction* CondExpressionHalf(CodeGen* gen, - const ErrorCode& cond, - ArgHalf half, - Instruction* passed, - Instruction* failed); + void VerifyProgram(const CodeGen::Program& program); static SandboxStatus status_; bool quiet_; int proc_fd_; - scoped_ptr<const bpf_dsl::SandboxBPFDSLPolicy> policy_; - Conds* conds_; bool sandbox_has_started_; - bool has_unsafe_traps_; + scoped_ptr<bpf_dsl::SandboxBPFDSLPolicy> policy_; DISALLOW_COPY_AND_ASSIGN(SandboxBPF); }; diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc index 11d3c0d..19d3e8a 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc @@ -60,9 +60,7 @@ void SandboxBPFTestRunner::Run() { // if we don't have kernel support. sandbox::SandboxBPF sandbox; sandbox.SetSandboxPolicy(policy.release()); - sandbox::SandboxBPF::Program* program = - sandbox.AssembleFilter(true /* force_verification */); - delete program; + sandbox.AssembleFilter(true /* force_verification */); sandbox::UnitTests::IgnoreThisTest(); } } diff --git a/sandbox/linux/seccomp-bpf/trap.cc b/sandbox/linux/seccomp-bpf/trap.cc index 8fa3174..dce6b7b 100644 --- a/sandbox/linux/seccomp-bpf/trap.cc +++ b/sandbox/linux/seccomp-bpf/trap.cc @@ -104,7 +104,7 @@ Trap::Trap() } } -Trap* Trap::GetInstance() { +bpf_dsl::TrapRegistry* Trap::Registry() { // Note: This class is not thread safe. It is the caller's responsibility // to avoid race conditions. Normally, this is a non-issue as the sandbox // can only be initialized if there are no other threads present. @@ -251,10 +251,10 @@ bool Trap::TrapKey::operator<(const TrapKey& o) const { } uint16_t Trap::MakeTrap(TrapFnc fnc, const void* aux, bool safe) { - return GetInstance()->MakeTrapImpl(fnc, aux, safe); + return Registry()->Add(fnc, aux, safe); } -uint16_t Trap::MakeTrapImpl(TrapFnc fnc, const void* aux, bool safe) { +uint16_t Trap::Add(TrapFnc fnc, const void* aux, bool safe) { if (!safe && !SandboxDebuggingAllowedByUser()) { // Unless the user set the CHROME_SANDBOX_DEBUGGING environment variable, // we never return an ErrorCode that is marked as "unsafe". This also @@ -358,18 +358,21 @@ bool Trap::SandboxDebuggingAllowedByUser() const { } bool Trap::EnableUnsafeTrapsInSigSysHandler() { - Trap* trap = GetInstance(); - if (!trap->has_unsafe_traps_) { + return Registry()->EnableUnsafeTraps(); +} + +bool Trap::EnableUnsafeTraps() { + if (!has_unsafe_traps_) { // Unsafe traps are a one-way fuse. Once enabled, they can never be turned // off again. // We only allow enabling unsafe traps, if the user explicitly set an // appropriate environment variable. This prevents bugs that accidentally // disable all sandboxing for all users. - if (trap->SandboxDebuggingAllowedByUser()) { + if (SandboxDebuggingAllowedByUser()) { // We only ever print this message once, when we enable unsafe traps the // first time. SANDBOX_INFO("WARNING! Disabling sandbox for debugging purposes"); - trap->has_unsafe_traps_ = true; + has_unsafe_traps_ = true; } else { SANDBOX_INFO( "Cannot disable sandbox and use unsafe traps unless " @@ -377,7 +380,7 @@ bool Trap::EnableUnsafeTrapsInSigSysHandler() { } } // Returns the, possibly updated, value of has_unsafe_traps_. - return trap->has_unsafe_traps_; + return has_unsafe_traps_; } Trap* Trap::global_trap_; diff --git a/sandbox/linux/seccomp-bpf/trap.h b/sandbox/linux/seccomp-bpf/trap.h index 71dd2c7..0a184e6 100644 --- a/sandbox/linux/seccomp-bpf/trap.h +++ b/sandbox/linux/seccomp-bpf/trap.h @@ -11,18 +11,11 @@ #include <map> #include "base/macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" #include "sandbox/sandbox_export.h" namespace sandbox { -// This must match the kernel's seccomp_data structure. -struct arch_seccomp_data { - int nr; - uint32_t arch; - uint64_t instruction_pointer; - uint64_t args[6]; -}; - // The Trap class allows a BPF filter program to branch out to user space by // raising a SIGSYS signal. // N.B.: This class does not perform any synchronization operations. If @@ -31,27 +24,21 @@ struct arch_seccomp_data { // Preferably, that means that no other threads should be running at that // time. For the purposes of our sandbox, this assertion should always be // true. Threads are incompatible with the seccomp sandbox anyway. -class SANDBOX_EXPORT Trap { +class SANDBOX_EXPORT Trap : public bpf_dsl::TrapRegistry { public: - // TrapFnc is a pointer to a function that handles Seccomp traps in - // user-space. The seccomp policy can request that a trap handler gets - // installed; it does so by returning a suitable ErrorCode() from the - // syscallEvaluator. See the ErrorCode() constructor for how to pass in - // the function pointer. - // Please note that TrapFnc is executed from signal context and must be - // async-signal safe: - // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html - // Also note that it follows the calling convention of native system calls. - // In other words, it reports an error by returning an exit code in the - // range -1..-4096. It should not set errno when reporting errors; on the - // other hand, accidentally modifying errno is harmless and the changes will - // be undone afterwards. - typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void* aux); + virtual uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override; + + virtual bool EnableUnsafeTraps() override; + + // Registry returns the trap registry used by Trap's SIGSYS handler, + // creating it if necessary. + static bpf_dsl::TrapRegistry* Registry(); // Registers a new trap handler and sets up the appropriate SIGSYS handler // as needed. // N.B.: This makes a permanent state change. Traps cannot be unregistered, // as that would break existing BPF filters that are still active. + // TODO(mdempsky): Deprecated; remove. static uint16_t MakeTrap(TrapFnc fnc, const void* aux, bool safe); // Enables support for unsafe traps in the SIGSYS signal handler. This is a @@ -62,6 +49,7 @@ class SANDBOX_EXPORT Trap { // But this is still a very useful feature for debugging purposes. Use with // care. This feature is availably only if enabled by the user (see above). // Returns "true", if unsafe traps were turned on. + // TODO(mdempsky): Deprecated; remove. static bool EnableUnsafeTrapsInSigSysHandler(); private: @@ -83,21 +71,12 @@ class SANDBOX_EXPORT Trap { // object. It'll break subsequent system calls that trigger a SIGSYS. ~Trap(); - // We only have a very small number of methods. We opt to make them static - // and have them internally call GetInstance(). This is a little more - // convenient than having each caller obtain short-lived reference to the - // singleton. - // It also gracefully deals with methods that should check for the singleton, - // but avoid instantiating it, if it doesn't exist yet - // (e.g. ErrorCodeFromTrapId()). - static Trap* GetInstance(); static void SigSysAction(int nr, siginfo_t* info, void* void_context); // Make sure that SigSys is not inlined in order to get slightly better crash // dumps. void SigSys(int nr, siginfo_t* info, void* void_context) __attribute__((noinline)); - uint16_t MakeTrapImpl(TrapFnc fnc, const void* aux, bool safe); bool SandboxDebuggingAllowedByUser() const; // We have a global singleton that handles all of our SIGSYS traps. This diff --git a/sandbox/linux/seccomp-bpf/verifier.cc b/sandbox/linux/seccomp-bpf/verifier.cc index 6bd0754..69a8e62 100644 --- a/sandbox/linux/seccomp-bpf/verifier.cc +++ b/sandbox/linux/seccomp-bpf/verifier.cc @@ -9,6 +9,8 @@ #include <limits> #include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" #include "sandbox/linux/seccomp-bpf/syscall_iterator.h" @@ -35,7 +37,7 @@ struct State { DISALLOW_IMPLICIT_CONSTRUCTORS(State); }; -uint32_t EvaluateErrorCode(SandboxBPF* sandbox, +uint32_t EvaluateErrorCode(bpf_dsl::PolicyCompiler* compiler, const ErrorCode& code, const struct arch_seccomp_data& data) { if (code.error_type() == ErrorCode::ET_SIMPLE || @@ -46,17 +48,17 @@ uint32_t EvaluateErrorCode(SandboxBPF* sandbox, (data.args[code.argno()] >> 32) && (data.args[code.argno()] & 0xFFFFFFFF80000000ull) != 0xFFFFFFFF80000000ull) { - return sandbox->Unexpected64bitArgument().err(); + return compiler->Unexpected64bitArgument().err(); } bool equal = (data.args[code.argno()] & code.mask()) == code.value(); return EvaluateErrorCode( - sandbox, equal ? *code.passed() : *code.failed(), data); + compiler, equal ? *code.passed() : *code.failed(), data); } else { return SECCOMP_RET_INVALID; } } -bool VerifyErrorCode(SandboxBPF* sandbox, +bool VerifyErrorCode(bpf_dsl::PolicyCompiler* compiler, const std::vector<struct sock_filter>& program, struct arch_seccomp_data* data, const ErrorCode& root_code, @@ -67,7 +69,7 @@ bool VerifyErrorCode(SandboxBPF* sandbox, uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err); if (*err) { return false; - } else if (computed_ret != EvaluateErrorCode(sandbox, root_code, *data)) { + } else if (computed_ret != EvaluateErrorCode(compiler, root_code, *data)) { // For efficiency's sake, we'd much rather compare "computed_ret" // against "code.err()". This works most of the time, but it doesn't // always work for nested conditional expressions. The test values @@ -93,7 +95,7 @@ bool VerifyErrorCode(SandboxBPF* sandbox, // Verify that we can check a value for simple equality. data->args[code.argno()] = code.value(); if (!VerifyErrorCode( - sandbox, program, data, root_code, *code.passed(), err)) { + compiler, program, data, root_code, *code.passed(), err)) { return false; } @@ -106,14 +108,14 @@ bool VerifyErrorCode(SandboxBPF* sandbox, if ((ignored_bits & kLower32Bits) != 0) { data->args[code.argno()] = code.value() | (ignored_bits & kLower32Bits); if (!VerifyErrorCode( - sandbox, program, data, root_code, *code.passed(), err)) { + compiler, program, data, root_code, *code.passed(), err)) { return false; } } if ((ignored_bits & kUpper32Bits) != 0) { data->args[code.argno()] = code.value() | (ignored_bits & kUpper32Bits); if (!VerifyErrorCode( - sandbox, program, data, root_code, *code.passed(), err)) { + compiler, program, data, root_code, *code.passed(), err)) { return false; } } @@ -122,14 +124,14 @@ bool VerifyErrorCode(SandboxBPF* sandbox, if ((code.mask() & kLower32Bits) != 0) { data->args[code.argno()] = code.value() ^ (code.mask() & kLower32Bits); if (!VerifyErrorCode( - sandbox, program, data, root_code, *code.failed(), err)) { + compiler, program, data, root_code, *code.failed(), err)) { return false; } } if ((code.mask() & kUpper32Bits) != 0) { data->args[code.argno()] = code.value() ^ (code.mask() & kUpper32Bits); if (!VerifyErrorCode( - sandbox, program, data, root_code, *code.failed(), err)) { + compiler, program, data, root_code, *code.failed(), err)) { return false; } } @@ -140,11 +142,11 @@ bool VerifyErrorCode(SandboxBPF* sandbox, // Arbitrary 64-bit values should be rejected. data->args[code.argno()] = 1ULL << 32; - if (!VerifyErrorCode(sandbox, + if (!VerifyErrorCode(compiler, program, data, root_code, - sandbox->Unexpected64bitArgument(), + compiler->Unexpected64bitArgument(), err)) { return false; } @@ -152,11 +154,11 @@ bool VerifyErrorCode(SandboxBPF* sandbox, // Upper 32-bits set without the MSB of the lower 32-bits set should be // rejected too. data->args[code.argno()] = kUpper32Bits; - if (!VerifyErrorCode(sandbox, + if (!VerifyErrorCode(compiler, program, data, root_code, - sandbox->Unexpected64bitArgument(), + compiler->Unexpected64bitArgument(), err)) { return false; } @@ -310,7 +312,7 @@ void Alu(State* state, const struct sock_filter& insn, const char** err) { } // namespace -bool Verifier::VerifyBPF(SandboxBPF* sandbox, +bool Verifier::VerifyBPF(bpf_dsl::PolicyCompiler* compiler, const std::vector<struct sock_filter>& program, const bpf_dsl::SandboxBPFDSLPolicy& policy, const char** err) { @@ -338,9 +340,9 @@ bool Verifier::VerifyBPF(SandboxBPF* sandbox, #endif #endif ErrorCode code = iter.IsValid(sysnum) - ? policy.EvaluateSyscall(sandbox, sysnum) - : policy.InvalidSyscall(sandbox); - if (!VerifyErrorCode(sandbox, program, &data, code, code, err)) { + ? policy.EvaluateSyscall(compiler, sysnum) + : policy.InvalidSyscall(compiler); + if (!VerifyErrorCode(compiler, program, &data, code, code, err)) { return false; } } diff --git a/sandbox/linux/seccomp-bpf/verifier.h b/sandbox/linux/seccomp-bpf/verifier.h index 8ec9c55..66a3ee0 100644 --- a/sandbox/linux/seccomp-bpf/verifier.h +++ b/sandbox/linux/seccomp-bpf/verifier.h @@ -14,11 +14,11 @@ struct sock_filter; namespace sandbox { +struct arch_seccomp_data; namespace bpf_dsl { class SandboxBPFDSLPolicy; +class PolicyCompiler; } -struct arch_seccomp_data; -class SandboxBPF; class Verifier { public: @@ -29,7 +29,7 @@ class Verifier { // set by the "evaluators". // Upon success, "err" is set to NULL. Upon failure, it contains a static // error message that does not need to be free()'d. - static bool VerifyBPF(SandboxBPF* sandbox, + static bool VerifyBPF(bpf_dsl::PolicyCompiler* compiler, const std::vector<struct sock_filter>& program, const bpf_dsl::SandboxBPFDSLPolicy& policy, const char** err); |