diff options
author | markus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-31 08:22:25 +0000 |
---|---|---|
committer | markus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-31 08:22:25 +0000 |
commit | 7ecfa14fb92867f827de6853a058ccbde556c663 (patch) | |
tree | 25b0094d959e4afa1e4a698850192375daffac3a /sandbox | |
parent | cbfd582f44dbd2b3b25ac0f02049f4d5b72e0d62 (diff) | |
download | chromium_src-7ecfa14fb92867f827de6853a058ccbde556c663.zip chromium_src-7ecfa14fb92867f827de6853a058ccbde556c663.tar.gz chromium_src-7ecfa14fb92867f827de6853a058ccbde556c663.tar.bz2 |
Pass a pointer with auxilliary data to a policy function.
This data can be used by the policy to communicate with the method that set up policy.
In BPF_TEST()s it allows us to avoid global variables.
BUG=130662
TEST=sandbox_linux_unittests
Review URL: https://chromiumcodereview.appspot.com/11230048
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@165123 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox')
-rw-r--r-- | sandbox/linux/sandbox_linux.gypi | 1 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/bpf_tests.cc | 35 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/bpf_tests.h | 60 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.cc | 34 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.h | 40 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc | 67 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/verifier.cc | 3 |
7 files changed, 143 insertions, 97 deletions
diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi index 1d49b47..c02cd31 100644 --- a/sandbox/linux/sandbox_linux.gypi +++ b/sandbox/linux/sandbox_linux.gypi @@ -53,7 +53,6 @@ [ 'OS=="linux" and (target_arch=="ia32" or target_arch=="x64" ' 'or target_arch=="arm")', { 'sources': [ - 'seccomp-bpf/bpf_tests.cc', 'seccomp-bpf/bpf_tests.h', 'seccomp-bpf/codegen_unittest.cc', 'seccomp-bpf/errorcode_unittest.cc', diff --git a/sandbox/linux/seccomp-bpf/bpf_tests.cc b/sandbox/linux/seccomp-bpf/bpf_tests.cc deleted file mode 100644 index efe4020..0000000 --- a/sandbox/linux/seccomp-bpf/bpf_tests.cc +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "sandbox/linux/seccomp-bpf/bpf_tests.h" - -using playground2::Die; -using playground2::Sandbox; - -namespace sandbox { - -void BpfTests::TestWrapper(void *void_arg) { - TestArgs *arg = reinterpret_cast<TestArgs *>(void_arg); - Die::EnableSimpleExit(); - if (Sandbox::supportsSeccompSandbox(-1) == - Sandbox::STATUS_AVAILABLE) { - // Ensure the the sandbox is actually available at this time - int proc_fd; - BPF_ASSERT((proc_fd = open("/proc", O_RDONLY|O_DIRECTORY)) >= 0); - BPF_ASSERT(Sandbox::supportsSeccompSandbox(proc_fd) == - Sandbox::STATUS_AVAILABLE); - - // Initialize and then start the sandbox with our custom policy - Sandbox::setProcFd(proc_fd); - Sandbox::setSandboxPolicy(arg->policy(), NULL); - Sandbox::startSandbox(); - arg->test()(); - } else { - // TODO(markus): (crbug.com/141545) Call the compiler and verify the - // policy. That's the least we can do, if we don't have kernel support. - Sandbox::setSandboxPolicy(arg->policy(), NULL); - } -} - -} // namespace diff --git a/sandbox/linux/seccomp-bpf/bpf_tests.h b/sandbox/linux/seccomp-bpf/bpf_tests.h index ace0ce3..8da25f9 100644 --- a/sandbox/linux/seccomp-bpf/bpf_tests.h +++ b/sandbox/linux/seccomp-bpf/bpf_tests.h @@ -16,36 +16,74 @@ namespace sandbox { // Also, it takes advantage of the Die class to avoid calling LOG(FATAL), from // inside our tests, as we don't need or even want all the error handling that // LOG(FATAL) would do. -#define BPF_TEST(test_case_name, test_name, policy) \ - void BPF_TEST_##test_name(); \ +// BPF_TEST() takes a C++ data type as an optional fourth parameter. If +// present, this sets up a variable that can be accessed as "BPF_AUX". This +// variable will be passed as an argument to the "policy" function. Policies +// would typically use it as an argument to Sandbox::Trap(), if they want to +// communicate data between the BPF_TEST() and a Trap() function. +#define BPF_TEST(test_case_name, test_name, policy, aux...) \ + void BPF_TEST_##test_name(sandbox::BpfTests<aux>::AuxType& BPF_AUX); \ TEST(test_case_name, test_name) { \ - sandbox::BpfTests::TestArgs arg(BPF_TEST_##test_name, policy); \ - sandbox::BpfTests::RunTestInProcess(sandbox::BpfTests::TestWrapper, &arg);\ + sandbox::BpfTests<aux>::TestArgs arg(BPF_TEST_##test_name, policy); \ + sandbox::BpfTests<aux>::RunTestInProcess( \ + sandbox::BpfTests<aux>::TestWrapper, &arg);\ } \ - void BPF_TEST_##test_name() + void BPF_TEST_##test_name(sandbox::BpfTests<aux>::AuxType& BPF_AUX) // Assertions are handled exactly the same as with a normal SANDBOX_TEST() #define BPF_ASSERT SANDBOX_ASSERT +// The "Aux" type is optional. We use an "empty" type by default, so that if +// the caller doesn't provide any type, all the BPF_AUX related data compiles +// to nothing. +template<class Aux = int[0]> class BpfTests : public UnitTests { public: + typedef Aux AuxType; + class TestArgs { public: - TestArgs(void (*test)(), playground2::Sandbox::EvaluateSyscall policy) - : test_(test), - policy_(policy) { + TestArgs(void (*t)(AuxType&), playground2::Sandbox::EvaluateSyscall p) + : test_(t), + policy_(p), + aux_() { } - void (*test() const)() { return test_; } + void (*test() const)(AuxType&) { return test_; } playground2::Sandbox::EvaluateSyscall policy() const { return policy_; } private: - void (*test_)(); + friend class BpfTests; + + void (*test_)(AuxType&); playground2::Sandbox::EvaluateSyscall policy_; + AuxType aux_; }; - static void TestWrapper(void *void_arg); + static void TestWrapper(void *void_arg) { + TestArgs *arg = reinterpret_cast<TestArgs *>(void_arg); + playground2::Die::EnableSimpleExit(); + if (playground2::Sandbox::supportsSeccompSandbox(-1) == + playground2::Sandbox::STATUS_AVAILABLE) { + // Ensure the the sandbox is actually available at this time + int proc_fd; + BPF_ASSERT((proc_fd = open("/proc", O_RDONLY|O_DIRECTORY)) >= 0); + BPF_ASSERT(playground2::Sandbox::supportsSeccompSandbox(proc_fd) == + playground2::Sandbox::STATUS_AVAILABLE); + + // Initialize and then start the sandbox with our custom policy + playground2::Sandbox::setProcFd(proc_fd); + playground2::Sandbox::setSandboxPolicy(arg->policy(), &arg->aux_); + playground2::Sandbox::startSandbox(); + + arg->test()(arg->aux_); + } else { + // TODO(markus): (crbug.com/141545) Call the compiler and verify the + // policy. That's the least we can do, if we don't have kernel support. + playground2::Sandbox::setSandboxPolicy(arg->policy(), NULL); + } + } private: DISALLOW_IMPLICIT_CONSTRUCTORS(BpfTests); diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index 6e3a6ba..eb03995 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -29,8 +29,8 @@ const int kExpectedExitCode = 100; // We define a really simple sandbox policy. It is just good enough for us // to tell that the sandbox has actually been activated. -ErrorCode Sandbox::probeEvaluator(int signo) { - switch (signo) { +ErrorCode Sandbox::probeEvaluator(int sysnum, void *) { + switch (sysnum) { case __NR_getpid: // Return EPERM so that we can check that the filter actually ran. return ErrorCode(EPERM); @@ -53,7 +53,7 @@ bool Sandbox::isValidSyscallNumber(int sysnum) { return SyscallIterator::IsValid(sysnum); } -ErrorCode Sandbox::allowAllEvaluator(int sysnum) { +ErrorCode Sandbox::allowAllEvaluator(int sysnum, void *) { if (!isValidSyscallNumber(sysnum)) { return ErrorCode(ENOSYS); } @@ -72,6 +72,7 @@ void Sandbox::tryVsyscallProcess(void) { bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), EvaluateSyscall syscallEvaluator, + void *aux, int proc_fd) { // Block all signals before forking a child process. This prevents an // attacker from manipulating our test by sending us an unexpected signal. @@ -126,7 +127,7 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), } evaluators_.clear(); - setSandboxPolicy(syscallEvaluator, NULL); + setSandboxPolicy(syscallEvaluator, aux); setProcFd(proc_fd); // By passing "quiet=true" to "startSandboxInternal()" we suppress @@ -187,9 +188,10 @@ bool Sandbox::kernelSupportSeccompBPF(int proc_fd) { } #endif - return RunFunctionInPolicy(probeProcess, Sandbox::probeEvaluator, proc_fd) && - RunFunctionInPolicy(tryVsyscallProcess, Sandbox::allowAllEvaluator, - proc_fd); + return + RunFunctionInPolicy(probeProcess, Sandbox::probeEvaluator, 0, proc_fd) && + RunFunctionInPolicy(tryVsyscallProcess, Sandbox::allowAllEvaluator, 0, + proc_fd); } Sandbox::SandboxStatus Sandbox::supportsSeccompSandbox(int proc_fd) { @@ -306,10 +308,10 @@ bool Sandbox::isDenied(const ErrorCode& code) { } void Sandbox::policySanityChecks(EvaluateSyscall syscallEvaluator, - EvaluateArguments) { + void *aux) { for (SyscallIterator iter(true); !iter.Done(); ) { uint32_t sysnum = iter.Next(); - if (!isDenied(syscallEvaluator(sysnum))) { + if (!isDenied(syscallEvaluator(sysnum, aux))) { SANDBOX_DIE("Policies should deny system calls that are outside the " "expected range (typically MIN_SYSCALL..MAX_SYSCALL)"); } @@ -317,13 +319,12 @@ void Sandbox::policySanityChecks(EvaluateSyscall syscallEvaluator, return; } -void Sandbox::setSandboxPolicy(EvaluateSyscall syscallEvaluator, - EvaluateArguments argumentEvaluator) { +void Sandbox::setSandboxPolicy(EvaluateSyscall syscallEvaluator, void *aux) { if (status_ == STATUS_ENABLED) { SANDBOX_DIE("Cannot change policy after sandbox has started"); } - policySanityChecks(syscallEvaluator, argumentEvaluator); - evaluators_.push_back(std::make_pair(syscallEvaluator, argumentEvaluator)); + policySanityChecks(syscallEvaluator, aux); + evaluators_.push_back(std::make_pair(syscallEvaluator, aux)); } void Sandbox::installFilter(bool quiet) { @@ -472,12 +473,13 @@ void Sandbox::findRanges(Ranges *ranges) { // and then verifying that the rest of the number range (both positive and // negative) all return the same ErrorCode. EvaluateSyscall evaluateSyscall = evaluators_.begin()->first; + void *aux = evaluators_.begin()->second; uint32_t oldSysnum = 0; - ErrorCode oldErr = evaluateSyscall(oldSysnum); - ErrorCode invalidErr = evaluateSyscall(MIN_SYSCALL - 1); + ErrorCode oldErr = evaluateSyscall(oldSysnum, aux); + ErrorCode invalidErr = evaluateSyscall(MIN_SYSCALL - 1, aux); for (SyscallIterator iter(false); !iter.Done(); ) { uint32_t sysnum = iter.Next(); - ErrorCode err = evaluateSyscall(static_cast<int>(sysnum)); + ErrorCode err = evaluateSyscall(static_cast<int>(sysnum), aux); if (!iter.IsValid(sysnum) && !invalidErr.Equals(err)) { // A proper sandbox policy should always treat system calls outside of // the range MIN_SYSCALL..MAX_SYSCALL (i.e. anything that returns diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h index d0764dd..a50ddb3 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -223,10 +223,13 @@ class Sandbox { ErrorCode failed; }; - typedef ErrorCode (*EvaluateSyscall)(int sysno); - typedef int (*EvaluateArguments)(int sysno, int arg, - Constraint *constraint); - typedef std::vector<std::pair<EvaluateSyscall,EvaluateArguments> >Evaluators; + // When calling setSandboxPolicy(), the caller can provide an arbitrary + // pointer. This pointer will then be forwarded to the sandbox policy + // each time a call is made through an EvaluateSyscall function pointer. + // One common use case would be to pass the "aux" pointer as an argument + // to Trap() functions. + typedef ErrorCode (*EvaluateSyscall)(int sysnum, void *aux); + typedef std::vector<std::pair<EvaluateSyscall, void *> >Evaluators; // Checks whether a particular system call number is valid on the current // architecture. E.g. on ARM there's a non-contiguous range of private @@ -249,21 +252,23 @@ class Sandbox { // The system call evaluator function is called with the system // call number. It can decide to allow the system call unconditionally - // by returning "0"; it can deny the system call unconditionally by + // by returning ERR_ALLOWED; it can deny the system call unconditionally by // returning an appropriate "errno" value; or it can request inspection - // of system call argument(s) by returning a suitable combination of - // SB_INSPECT_ARG_x bits. - // The system argument evaluator is called (if needed) to query additional - // constraints for the system call arguments. In the vast majority of - // cases, it will set a "Constraint" that forces a new "errno" value. - // But for more complex filters, it is possible to return another mask - // of SB_INSPECT_ARG_x bits. - static void setSandboxPolicy(EvaluateSyscall syscallEvaluator, - EvaluateArguments argumentEvaluator); + // of system call argument(s) by returning a suitable ErrorCode. + // The "aux" parameter can be used to pass optional data to the system call + // evaluator. There are different possible uses for this data, but one of the + // use cases would be for the policy to then forward this pointer to a Trap() + // handler. In this case, of course, the data that is pointed to must remain + // valid for the entire time that Trap() handlers can be called; typically, + // this would be the lifetime of the program. + static void setSandboxPolicy(EvaluateSyscall syscallEvaluator, void *aux); // 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(ErrorCode::TrapFnc fnc, const void *aux); // Kill the program and print an error message. @@ -300,20 +305,21 @@ class Sandbox { // Get a file descriptor pointing to "/proc", if currently available. static int proc_fd() { return proc_fd_; } - static ErrorCode probeEvaluator(int signo) __attribute__((const)); + static ErrorCode probeEvaluator(int sysnum, void *) __attribute__((const)); static void probeProcess(void); - static ErrorCode allowAllEvaluator(int sysnum); + static ErrorCode allowAllEvaluator(int sysnum, void *aux); static void tryVsyscallProcess(void); static bool kernelSupportSeccompBPF(int proc_fd); static bool RunFunctionInPolicy(void (*function)(), EvaluateSyscall syscallEvaluator, + void *aux, int proc_fd); static void startSandboxInternal(bool quiet); static bool isSingleThreaded(int proc_fd); static bool isDenied(const ErrorCode& code); static bool disableFilesystem(); static void policySanityChecks(EvaluateSyscall syscallEvaluator, - EvaluateArguments argumentEvaluator); + void *aux); static void installFilter(bool quiet); static void findRanges(Ranges *ranges); static Instruction *assembleJumpTable(CodeGen *gen, diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc index 91d30ae..8ea23d9 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -35,9 +35,51 @@ SANDBOX_TEST(SandboxBpf, CallSupportsTwice) { Sandbox::supportsSeccompSandbox(-1); } +// BPF_TEST does a lot of the boiler-plate code around setting up a +// policy and optional passing data between the caller, the policy and +// any Trap() handlers. This is great for writing short and concise tests, +// and it helps us accidentally forgetting any of the crucial steps in +// setting up the sandbox. But it wouldn't hurt to have at least one test +// that explicitly walks through all these steps. + +intptr_t FakeGetPid(const struct arch_seccomp_data& args, void *aux) { + BPF_ASSERT(aux); + pid_t *pid_ptr = static_cast<pid_t *>(aux); + return (*pid_ptr)++; +} + +ErrorCode VerboseAPITestingPolicy(int sysno, void *aux) { + if (!Sandbox::isValidSyscallNumber(sysno)) { + return ErrorCode(ENOSYS); + } else if (sysno == __NR_getpid) { + return Sandbox::Trap(FakeGetPid, aux); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +SANDBOX_TEST(SandboxBpf, VerboseAPITesting) { + if (Sandbox::supportsSeccompSandbox(-1) == + playground2::Sandbox::STATUS_AVAILABLE) { + pid_t test_var = 0; + playground2::Sandbox::setSandboxPolicy(VerboseAPITestingPolicy, &test_var); + playground2::Sandbox::startSandbox(); + + BPF_ASSERT(test_var == 0); + BPF_ASSERT(syscall(__NR_getpid) == 0); + BPF_ASSERT(test_var == 1); + BPF_ASSERT(syscall(__NR_getpid) == 1); + BPF_ASSERT(test_var == 2); + + // N.B.: Any future call to getpid() would corrupt the stack. + // This is OK. The SANDBOX_TEST() macro is guaranteed to + // only ever call _exit() after the test completes. + } +} + // A simple blacklist test -ErrorCode BlacklistNanosleepPolicy(int sysno) { +ErrorCode BlacklistNanosleepPolicy(int sysno, void *) { if (!Sandbox::isValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); @@ -61,7 +103,7 @@ BPF_TEST(SandboxBpf, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) { // Now do a simple whitelist test -ErrorCode WhitelistGetpidPolicy(int sysno) { +ErrorCode WhitelistGetpidPolicy(int sysno, void *) { switch (sysno) { case __NR_getpid: case __NR_exit_group: @@ -84,11 +126,6 @@ BPF_TEST(SandboxBpf, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) { // A simple blacklist policy, with a SIGSYS handler -// TODO: provide an API to provide the auxiliary data pointer -// to the evaluator - -static int BlacklistNanosleepPolicySigsysAuxData; - intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) { // We also check that the auxiliary data is correct SANDBOX_ASSERT(aux); @@ -96,7 +133,7 @@ intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) { return -ENOMEM; } -ErrorCode BlacklistNanosleepPolicySigsys(int sysno) { +ErrorCode BlacklistNanosleepPolicySigsys(int sysno, void *aux) { if (!Sandbox::isValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); @@ -104,29 +141,27 @@ ErrorCode BlacklistNanosleepPolicySigsys(int sysno) { switch (sysno) { case __NR_nanosleep: - return Sandbox::Trap(EnomemHandler, - static_cast<void *>(&BlacklistNanosleepPolicySigsysAuxData)); + return Sandbox::Trap(EnomemHandler, aux); default: return ErrorCode(ErrorCode::ERR_ALLOWED); } } BPF_TEST(SandboxBpf, BasicBlacklistWithSigsys, - BlacklistNanosleepPolicySigsys) { + BlacklistNanosleepPolicySigsys, int /* BPF_AUX */) { // getpid() should work properly errno = 0; BPF_ASSERT(syscall(__NR_getpid) > 0); BPF_ASSERT(errno == 0); // Our Auxiliary Data, should be reset by the signal handler - BlacklistNanosleepPolicySigsysAuxData = -1; + BPF_AUX = -1; const struct timespec ts = {0, 0}; BPF_ASSERT(syscall(__NR_nanosleep, &ts, NULL) == -1); BPF_ASSERT(errno == ENOMEM); // We expect the signal handler to modify AuxData - BPF_ASSERT( - BlacklistNanosleepPolicySigsysAuxData == kExpectedReturnValue); + BPF_ASSERT(BPF_AUX == kExpectedReturnValue); } // A more complex, but synthetic policy. This tests the correctness of the BPF @@ -144,7 +179,7 @@ int SysnoToRandomErrno(int sysno) { return ((sysno & ~3) >> 2) % 29 + 1; } -ErrorCode SyntheticPolicy(int sysno) { +ErrorCode SyntheticPolicy(int sysno, void *) { if (!Sandbox::isValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); @@ -202,7 +237,7 @@ int ArmPrivateSysnoToErrno(int sysno) { } } -ErrorCode ArmPrivatePolicy(int sysno) { +ErrorCode ArmPrivatePolicy(int sysno, void *) { if (!Sandbox::isValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy. return ErrorCode(ENOSYS); diff --git a/sandbox/linux/seccomp-bpf/verifier.cc b/sandbox/linux/seccomp-bpf/verifier.cc index 641e433..40a1aa2 100644 --- a/sandbox/linux/seccomp-bpf/verifier.cc +++ b/sandbox/linux/seccomp-bpf/verifier.cc @@ -18,6 +18,7 @@ bool Verifier::VerifyBPF(const std::vector<struct sock_filter>& program, return false; } Sandbox::EvaluateSyscall evaluate_syscall = evaluators.begin()->first; + void *aux = evaluators.begin()->second; for (SyscallIterator iter(false); !iter.Done(); ) { uint32_t sysnum = iter.Next(); // We ideally want to iterate over the full system call range and values @@ -40,7 +41,7 @@ bool Verifier::VerifyBPF(const std::vector<struct sock_filter>& program, } #endif #endif - ErrorCode code = evaluate_syscall(sysnum); + ErrorCode code = evaluate_syscall(sysnum, aux); uint32_t computed_ret = EvaluateBPF(program, data, err); if (*err) { return false; |