summaryrefslogtreecommitdiffstats
path: root/sandbox/linux/seccomp-bpf
diff options
context:
space:
mode:
authormdempsky <mdempsky@chromium.org>2014-10-16 09:49:54 -0700
committerCommit bot <commit-bot@chromium.org>2014-10-16 16:50:17 +0000
commit27e78ad7c43bd01d607d414552cffdfe92e9dece (patch)
treedf02c5b0120d38b89aa6eadd7b360a0c2a62baee /sandbox/linux/seccomp-bpf
parent3a721cdeaa08279e20010c88e282b4a12bcc3ee5 (diff)
downloadchromium_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.cc10
-rw-r--r--sandbox/linux/seccomp-bpf/codegen.h21
-rw-r--r--sandbox/linux/seccomp-bpf/codegen_unittest.cc7
-rw-r--r--sandbox/linux/seccomp-bpf/errorcode.cc7
-rw-r--r--sandbox/linux/seccomp-bpf/errorcode.h6
-rw-r--r--sandbox/linux/seccomp-bpf/errorcode_unittest.cc50
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.cc562
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.h165
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc4
-rw-r--r--sandbox/linux/seccomp-bpf/trap.cc19
-rw-r--r--sandbox/linux/seccomp-bpf/trap.h43
-rw-r--r--sandbox/linux/seccomp-bpf/verifier.cc38
-rw-r--r--sandbox/linux/seccomp-bpf/verifier.h6
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);