diff options
author | markus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-26 04:19:54 +0000 |
---|---|---|
committer | markus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-26 04:19:54 +0000 |
commit | ef747cffdfb4d48eafb35cf38980890e1012a7d5 (patch) | |
tree | 7cb1c7bc353a7d0530dacd21cab8231adc00e98a /sandbox | |
parent | d07fa11fab1edcaea0cc095caba0c560db0066b4 (diff) | |
download | chromium_src-ef747cffdfb4d48eafb35cf38980890e1012a7d5.zip chromium_src-ef747cffdfb4d48eafb35cf38980890e1012a7d5.tar.gz chromium_src-ef747cffdfb4d48eafb35cf38980890e1012a7d5.tar.bz2 |
SECCOMP-BPF: Refactoring in preparation for sandbox stacking.
We have to keep a few globally shared bits of data, because of the way the
kernel exposes the sandboxing API. In the past, we solved this problem by
having a monolithic "static" class for all of the sandboxing code.
This is sub-optimal and makes it difficult to implement stacking of multiple
BPF sandboxes -- a feature that we would like to have.
I believe, all that needs to be kept static is the handling of SIGSYS
traps. So, we are pulling those into their own class. In the next step, we'll
clean up the actual sandbox class.
This changelist introduces one new feature. Going forward, we won't allow
"unsafe" traps (e.g. used for grey listing) unless the user explicitly sets
the CHROME_SANDBOX_DEBUGGING environment variable. This prevents accidental
bugs from unintentionally disabling the entire sandbox.
BUG=130662
TEST=sandbox_linux_unittests
Review URL: https://chromiumcodereview.appspot.com/11929036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@179026 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox')
-rw-r--r-- | sandbox/linux/sandbox_linux.gypi | 2 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/Makefile | 2 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/bpf_tests.h | 1 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/die.h | 4 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/errorcode.cc | 2 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/errorcode.h | 21 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.cc | 258 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.h | 53 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc | 20 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/trap.cc | 343 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/trap.h | 114 |
11 files changed, 539 insertions, 281 deletions
diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi index 2fac989..a223feb 100644 --- a/sandbox/linux/sandbox_linux.gypi +++ b/sandbox/linux/sandbox_linux.gypi @@ -122,6 +122,8 @@ 'seccomp-bpf/syscall.h', 'seccomp-bpf/syscall_iterator.cc', 'seccomp-bpf/syscall_iterator.h', + 'seccomp-bpf/trap.cc', + 'seccomp-bpf/trap.h', 'seccomp-bpf/verifier.cc', 'seccomp-bpf/verifier.h', ], diff --git a/sandbox/linux/seccomp-bpf/Makefile b/sandbox/linux/seccomp-bpf/Makefile index 78ff704..b7c1f9d 100644 --- a/sandbox/linux/seccomp-bpf/Makefile +++ b/sandbox/linux/seccomp-bpf/Makefile @@ -2,7 +2,7 @@ DEF_CFLAGS = -g -O3 -Wall -Werror -Wextra -Wno-missing-field-initializers -fPIC DEF_CPPFLAGS = -D_GNU_SOURCE -DSECCOMP_BPF_STANDALONE -iquote ../../.. DEF_LDFLAGS = -g -lpthread DEPFLAGS = -MMD -MF .$@.d -MODS := demo sandbox_bpf basicblock codegen die errorcode syscall syscall_iterator util verifier +MODS := demo sandbox_bpf basicblock codegen die errorcode syscall syscall_iterator trap util verifier OBJS64 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o64/') OBJS32 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o32/') ALL_OBJS = $(OBJS32) $(OBJS64) diff --git a/sandbox/linux/seccomp-bpf/bpf_tests.h b/sandbox/linux/seccomp-bpf/bpf_tests.h index 931a39e..2540089 100644 --- a/sandbox/linux/seccomp-bpf/bpf_tests.h +++ b/sandbox/linux/seccomp-bpf/bpf_tests.h @@ -92,7 +92,6 @@ class BpfTests : public UnitTests { playground2::Sandbox::SetSandboxPolicy(arg->policy(), &arg->aux_); playground2::Sandbox::Program *program = playground2::Sandbox::AssembleFilter(); - playground2::Sandbox::VerifyProgram(*program); delete program; sandbox::UnitTests::IgnoreThisTest(); } diff --git a/sandbox/linux/seccomp-bpf/die.h b/sandbox/linux/seccomp-bpf/die.h index c0ad8fd..cecb506 100644 --- a/sandbox/linux/seccomp-bpf/die.h +++ b/sandbox/linux/seccomp-bpf/die.h @@ -11,10 +11,10 @@ class Die { public: // This is the main API for using this file. Prints a error message and // exits with a fatal error. - #define SANDBOX_DIE(m) Die::SandboxDie(m, __FILE__, __LINE__) + #define SANDBOX_DIE(m) playground2::Die::SandboxDie(m, __FILE__, __LINE__) // Adds an informational message to the log file or stderr as appropriate. - #define SANDBOX_INFO(m) Die::SandboxInfo(m, __FILE__, __LINE__) + #define SANDBOX_INFO(m) playground2::Die::SandboxInfo(m, __FILE__, __LINE__) // Terminate the program, even if the current sandbox policy prevents some // of the more commonly used functions used for exiting. diff --git a/sandbox/linux/seccomp-bpf/errorcode.cc b/sandbox/linux/seccomp-bpf/errorcode.cc index 4d21b792..c9d4988 100644 --- a/sandbox/linux/seccomp-bpf/errorcode.cc +++ b/sandbox/linux/seccomp-bpf/errorcode.cc @@ -22,7 +22,7 @@ ErrorCode::ErrorCode(int err) { } } -ErrorCode::ErrorCode(ErrorCode::TrapFnc fnc, const void *aux, bool safe, +ErrorCode::ErrorCode(Trap::TrapFnc fnc, const void *aux, bool safe, uint16_t id) : error_type_(ET_TRAP), fnc_(fnc), diff --git a/sandbox/linux/seccomp-bpf/errorcode.h b/sandbox/linux/seccomp-bpf/errorcode.h index d2661db..e3f66e6 100644 --- a/sandbox/linux/seccomp-bpf/errorcode.h +++ b/sandbox/linux/seccomp-bpf/errorcode.h @@ -5,6 +5,8 @@ #ifndef SANDBOX_LINUX_SECCOMP_BPF_ERRORCODE_H__ #define SANDBOX_LINUX_SECCOMP_BPF_ERRORCODE_H__ +#include "sandbox/linux/seccomp-bpf/trap.h" + namespace playground2 { struct arch_seccomp_data; @@ -31,16 +33,6 @@ class ErrorCode { ERR_INVALID = -1, }; - // 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 - typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void *aux); - enum ArgType { TP_32BIT, TP_64BIT, }; @@ -85,6 +77,7 @@ class ErrorCode { private: friend class CodeGen; friend class Sandbox; + friend class Trap; friend class Verifier; enum ErrorType { @@ -94,7 +87,7 @@ class 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(TrapFnc fnc, const void *aux, bool safe, uint16_t id); + ErrorCode(Trap::TrapFnc fnc, const void *aux, bool safe, uint16_t id); // Some system calls require inspection of arguments. This constructor // allows us to specify additional constraints. @@ -106,9 +99,9 @@ class ErrorCode { union { // Fields needed for SECCOMP_RET_TRAP callbacks struct { - TrapFnc fnc_; // Callback function and arg, if trap was - void *aux_; // triggered by the kernel's BPF filter. - bool safe_; // Keep sandbox active while calling fnc_() + Trap::TrapFnc fnc_; // Callback function and arg, if trap was + void *aux_; // triggered by the kernel's BPF filter. + bool safe_; // Keep sandbox active while calling fnc_() }; // Fields needed when inspecting additional arguments. diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index dc21b7d..5521ba0 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -2,7 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <signal.h> +// Some headers on Android are missing cdefs: crbug.com/172337. +// (We can't use OS_ANDROID here since build_config.h is not included). +#if defined(ANDROID) +#include <sys/cdefs.h> +#endif + #include <sys/prctl.h> #include <sys/syscall.h> @@ -17,11 +22,6 @@ #include "sandbox/linux/seccomp-bpf/syscall_iterator.h" #include "sandbox/linux/seccomp-bpf/verifier.h" -// Android's signal.h doesn't define ucontext etc. -#if defined(OS_ANDROID) -#include "sandbox/linux/services/android_ucontext.h" -#endif - namespace { void WriteFailedStderrSetupMessage(int out_fd) { @@ -35,31 +35,6 @@ void WriteFailedStderrSetupMessage(int out_fd) { } } -// We need to tell whether we are performing a "normal" callback, or -// whether we were called recursively from within a UnsafeTrap() callback. -// This is a little tricky to do, because we need to somehow get access to -// per-thread data from within a signal context. Normal TLS storage is not -// safely accessible at this time. We could roll our own, but that involves -// a lot of complexity. Instead, we co-opt one bit in the signal mask. -// If BUS is blocked, we assume that we have been called recursively. -// There is a possibility for collision with other code that needs to do -// this, but in practice the risks are low. -// If SIGBUS turns out to be a problem, we could instead co-opt one of the -// realtime signals. There are plenty of them. Unfortunately, there is no -// way to mark a signal as allocated. So, the potential for collision is -// possibly even worse. -bool GetIsInSigHandler(const ucontext_t *ctx) { - // Note: on Android, sigismember does not take a pointer to const. - return sigismember(const_cast<sigset_t*>(&ctx->uc_sigmask), SIGBUS); -} - -void SetIsInSigHandler() { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGBUS); - sigprocmask(SIG_BLOCK, &mask, NULL); -} - } // namespace // The kernel gives us a sandbox, we turn it into a playground :-) @@ -367,13 +342,16 @@ void Sandbox::PolicySanityChecks(EvaluateSyscall syscall_evaluator, } void Sandbox::CheckForUnsafeErrorCodes(Instruction *insn, void *aux) { - if (BPF_CLASS(insn->code) == BPF_RET && - insn->k > SECCOMP_RET_TRAP && - insn->k - SECCOMP_RET_TRAP <= trap_array_size_) { - const ErrorCode& err = trap_array_[insn->k - SECCOMP_RET_TRAP - 1]; - if (!err.safe_) { - bool *is_unsafe = static_cast<bool *>(aux); - *is_unsafe = true; + bool *is_unsafe = static_cast<bool *>(aux); + if (!*is_unsafe) { + if (BPF_CLASS(insn->code) == BPF_RET && + insn->k > SECCOMP_RET_TRAP && + insn->k - SECCOMP_RET_TRAP <= SECCOMP_RET_DATA) { + const ErrorCode& err = + Trap::ErrorCodeFromTrapId(insn->k & SECCOMP_RET_DATA); + if (err.error_type_ != ErrorCode::ET_INVALID && !err.safe_) { + *is_unsafe = true; + } } } } @@ -430,13 +408,6 @@ void Sandbox::InstallFilter(bool quiet) { // in system calls to things like munmap() or brk(). Program *program = AssembleFilter(); - // Make sure compilation resulted in BPF program that executes - // correctly. Otherwise, there is an internal error in our BPF compiler. - // There is really nothing the caller can do until the bug is fixed. -#ifndef NDEBUG - VerifyProgram(*program); -#endif - struct sock_filter bpf[program->size()]; const struct sock_fprog prog = { static_cast<unsigned short>(program->size()), bpf }; @@ -462,27 +433,9 @@ void Sandbox::InstallFilter(bool quiet) { Sandbox::Program *Sandbox::AssembleFilter() { // Verify that the user pushed a policy. if (evaluators_.empty()) { - filter_failed: SANDBOX_DIE("Failed to configure system call filters"); } - // Set new SIGSYS handler - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = SigSys; - sa.sa_flags = SA_SIGINFO | SA_NODEFER; - if (sigaction(SIGSYS, &sa, NULL) < 0) { - goto filter_failed; - } - - // Unmask SIGSYS - sigset_t mask; - if (sigemptyset(&mask) || - sigaddset(&mask, SIGSYS) || - sigprocmask(SIG_UNBLOCK, &mask, NULL)) { - goto filter_failed; - } - // We can't handle stacked evaluators, yet. We'll get there eventually // though. Hang tight. if (evaluators_.size() != 1) { @@ -506,6 +459,7 @@ Sandbox::Program *Sandbox::AssembleFilter() { gen->MakeInstruction(BPF_RET+BPF_K, Kill("Invalid audit architecture in BPF filter")))); + bool has_unsafe_traps = false; { // Evaluate all possible system calls and group their ErrorCodes into // ranges of identical codes. @@ -521,8 +475,7 @@ Sandbox::Program *Sandbox::AssembleFilter() { // SECCOMP_RET_ALLOW ErrorCodes are handled in user-space. This will then // allow us to temporarily disable sandboxing rules inside of callbacks to // UnsafeTrap(). - has_unsafe_traps_ = false; - gen->Traverse(jumptable, CheckForUnsafeErrorCodes, &has_unsafe_traps_); + gen->Traverse(jumptable, CheckForUnsafeErrorCodes, &has_unsafe_traps); // Grab the system call number, so that we can implement jump tables. Instruction *load_nr = @@ -535,7 +488,7 @@ Sandbox::Program *Sandbox::AssembleFilter() { // measures that the sandbox provides, we print a big warning message -- // and of course, we make sure to only ever enable this feature if it // is actually requested by the sandbox policy. - if (has_unsafe_traps_) { + if (has_unsafe_traps) { if (SandboxSyscall(-1) == -1 && errno == ENOSYS) { SANDBOX_DIE("Support for UnsafeTrap() has not yet been ported to this " "architecture"); @@ -560,7 +513,14 @@ Sandbox::Program *Sandbox::AssembleFilter() { "unconditionally allow sigreturn() and sigprocmask()"); } - SANDBOX_INFO("WARNING! Disabling sandbox for debugging purposes"); + if (!Trap::EnableUnsafeTrapsInSigSysHandler()) { + // We should never be able to get here, as UnsafeTrap() should never + // actually return a valid ErrorCode object unless the user set the + // CHROME_SANDBOX_DEBUGGING environment variable; and therefore, + // "has_unsafe_traps" would always be false. But better double-check + // than enabling dangerous code. + SANDBOX_DIE("We'd rather die than enable unsafe traps"); + } gen->Traverse(jumptable, RedirectToUserspace, NULL); // Allow system calls, if they originate from our magic return address @@ -620,10 +580,17 @@ Sandbox::Program *Sandbox::AssembleFilter() { gen->Compile(head, program); delete gen; + // Make sure compilation resulted in BPF program that executes + // correctly. Otherwise, there is an internal error in our BPF compiler. + // There is really nothing the caller can do until the bug is fixed. +#ifndef NDEBUG + VerifyProgram(*program, has_unsafe_traps); +#endif + return program; } -void Sandbox::VerifyProgram(const Program& program) { +void Sandbox::VerifyProgram(const Program& program, bool has_unsafe_traps) { // If we previously rewrote the BPF program so that it calls user-space // whenever we return an "errno" value from the filter, then we have to // wrap our system call evaluator to perform the same operation. Otherwise, @@ -635,7 +602,7 @@ void Sandbox::VerifyProgram(const Program& program) { const char *err = NULL; if (!Verifier::VerifyBPF( program, - has_unsafe_traps_ ? redirected_evaluators : evaluators_, + has_unsafe_traps ? redirected_evaluators : evaluators_, &err)) { SANDBOX_DIE(err); } @@ -787,152 +754,12 @@ ErrorCode Sandbox::Unexpected64bitArgument() { return Kill("Unexpected 64bit argument detected"); } -void Sandbox::SigSys(int nr, siginfo_t *info, void *void_context) { - // Various sanity checks to make sure we actually received a signal - // triggered by a BPF filter. If something else triggered SIGSYS - // (e.g. kill()), there is really nothing we can do with this signal. - if (nr != SIGSYS || info->si_code != SYS_SECCOMP || !void_context || - info->si_errno <= 0 || - static_cast<size_t>(info->si_errno) > trap_array_size_) { - // SANDBOX_DIE() can call LOG(FATAL). This is not normally async-signal - // safe and can lead to bugs. We should eventually implement a different - // logging and reporting mechanism that is safe to be called from - // the sigSys() handler. - // TODO: If we feel confident that our code otherwise works correctly, we - // could actually make an argument that spurious SIGSYS should - // just get silently ignored. TBD - sigsys_err: - SANDBOX_DIE("Unexpected SIGSYS received"); - } - - // Signal handlers should always preserve "errno". Otherwise, we could - // trigger really subtle bugs. - int old_errno = errno; - - // Obtain the signal context. This, most notably, gives us access to - // all CPU registers at the time of the signal. - ucontext_t *ctx = reinterpret_cast<ucontext_t *>(void_context); - - // Obtain the siginfo information that is specific to SIGSYS. Unfortunately, - // most versions of glibc don't include this information in siginfo_t. So, - // we need to explicitly copy it into a arch_sigsys structure. - struct arch_sigsys sigsys; - memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); - - // Some more sanity checks. - if (sigsys.ip != reinterpret_cast<void *>(SECCOMP_IP(ctx)) || - sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)) || - sigsys.arch != SECCOMP_ARCH) { - goto sigsys_err; - } - - intptr_t rc; - if (has_unsafe_traps_ && GetIsInSigHandler(ctx)) { - errno = old_errno; - if (sigsys.nr == __NR_clone) { - SANDBOX_DIE("Cannot call clone() from an UnsafeTrap() handler"); - } - rc = SandboxSyscall(sigsys.nr, - SECCOMP_PARM1(ctx), SECCOMP_PARM2(ctx), - SECCOMP_PARM3(ctx), SECCOMP_PARM4(ctx), - SECCOMP_PARM5(ctx), SECCOMP_PARM6(ctx)); - } else { - const ErrorCode& err = trap_array_[info->si_errno - 1]; - if (!err.safe_) { - SetIsInSigHandler(); - } - - // Copy the seccomp-specific data into a arch_seccomp_data structure. This - // is what we are showing to TrapFnc callbacks that the system call - // evaluator registered with the sandbox. - struct arch_seccomp_data data = { - sigsys.nr, - SECCOMP_ARCH, - reinterpret_cast<uint64_t>(sigsys.ip), - { - static_cast<uint64_t>(SECCOMP_PARM1(ctx)), - static_cast<uint64_t>(SECCOMP_PARM2(ctx)), - static_cast<uint64_t>(SECCOMP_PARM3(ctx)), - static_cast<uint64_t>(SECCOMP_PARM4(ctx)), - static_cast<uint64_t>(SECCOMP_PARM5(ctx)), - static_cast<uint64_t>(SECCOMP_PARM6(ctx)) - } - }; - - // Now call the TrapFnc callback associated with this particular instance - // of SECCOMP_RET_TRAP. - rc = err.fnc_(data, err.aux_); - } - - // Update the CPU register that stores the return code of the system call - // that we just handled, and restore "errno" to the value that it had - // before entering the signal handler. - SECCOMP_RESULT(ctx) = static_cast<greg_t>(rc); - errno = old_errno; - - return; -} - -bool Sandbox::TrapKey::operator<(const Sandbox::TrapKey& o) const { - if (fnc != o.fnc) { - return fnc < o.fnc; - } else if (aux != o.aux) { - return aux < o.aux; - } else { - return safe < o.safe; - } -} - -ErrorCode Sandbox::MakeTrap(ErrorCode::TrapFnc fnc, const void *aux, - bool safe) { - // Each unique pair of TrapFnc and auxiliary data make up a distinct instance - // of a SECCOMP_RET_TRAP. - TrapKey key(fnc, aux, safe); - TrapIds::const_iterator iter = trap_ids_.find(key); - uint16_t id; - if (iter != trap_ids_.end()) { - // We have seen this pair before. Return the same id that we assigned - // earlier. - id = iter->second; - } else { - // This is a new pair. Remember it and assign a new id. - // Please note that we have to store traps in memory that doesn't get - // deallocated when the program is shutting down. A memory leak is - // intentional, because we might otherwise not be able to execute - // system calls part way through the program shutting down - if (!traps_) { - traps_ = new Traps(); - } - if (traps_->size() >= SECCOMP_RET_DATA) { - // In practice, this is pretty much impossible to trigger, as there - // are other kernel limitations that restrict overall BPF program sizes. - SANDBOX_DIE("Too many SECCOMP_RET_TRAP callback instances"); - } - id = traps_->size() + 1; - - traps_->push_back(ErrorCode(fnc, aux, safe, id)); - trap_ids_[key] = id; - - // We want to access the traps_ vector from our signal handler. But - // we are not assured that doing so is async-signal safe. On the other - // hand, C++ guarantees that the contents of a vector is stored in a - // contiguous C-style array. - // So, we look up the address and size of this array outside of the - // signal handler, where we can safely do so. - trap_array_ = &(*traps_)[0]; - trap_array_size_ = id; - return traps_->back(); - } - - return ErrorCode(fnc, aux, safe, id); -} - -ErrorCode Sandbox::Trap(ErrorCode::TrapFnc fnc, const void *aux) { - return MakeTrap(fnc, aux, true /* Safe Trap */); +ErrorCode Sandbox::Trap(Trap::TrapFnc fnc, const void *aux) { + return Trap::MakeTrap(fnc, aux, true /* Safe Trap */); } -ErrorCode Sandbox::UnsafeTrap(ErrorCode::TrapFnc fnc, const void *aux) { - return MakeTrap(fnc, aux, false /* Unsafe Trap */); +ErrorCode Sandbox::UnsafeTrap(Trap::TrapFnc fnc, const void *aux) { + return Trap::MakeTrap(fnc, aux, false /* Unsafe Trap */); } intptr_t Sandbox::ForwardSyscall(const struct arch_seccomp_data& args) { @@ -973,11 +800,6 @@ ErrorCode Sandbox::Kill(const char *msg) { Sandbox::SandboxStatus Sandbox::status_ = STATUS_UNKNOWN; int Sandbox::proc_fd_ = -1; Sandbox::Evaluators Sandbox::evaluators_; -Sandbox::Traps *Sandbox::traps_ = NULL; -Sandbox::TrapIds Sandbox::trap_ids_; -ErrorCode *Sandbox::trap_array_ = NULL; -size_t Sandbox::trap_array_size_ = 0; - bool Sandbox::has_unsafe_traps_ = false; Sandbox::Conds Sandbox::conds_; } // namespace diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h index e440840..1d48991 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -269,21 +269,6 @@ class Sandbox { STATUS_ENABLED // The sandbox is now active }; - // 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); - // 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. @@ -334,16 +319,19 @@ class Sandbox { // 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); + 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(ErrorCode::TrapFnc fnc, const void *aux); + static ErrorCode UnsafeTrap(Trap::TrapFnc fnc, const void *aux); // From within an UnsafeTrap() it is often useful to be able to execute // the system call that triggered the trap. The ForwardSyscall() method @@ -384,11 +372,6 @@ class Sandbox { // internals. It should not be used by production code. static Program *AssembleFilter(); - // 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. - static void VerifyProgram(const Program& program); - private: friend class CodeGen; friend class SandboxUnittestHelper; @@ -405,21 +388,8 @@ class Sandbox { uint32_t from, to; ErrorCode err; }; - struct TrapKey { - TrapKey(TrapFnc f, const void *a, bool s) - : fnc(f), - aux(a), - safe(s) { - } - TrapFnc fnc; - const void *aux; - bool safe; - bool operator<(const TrapKey&) const; - }; typedef std::vector<Range> Ranges; typedef std::map<uint32_t, ErrorCode> ErrMap; - typedef std::vector<ErrorCode> Traps; - typedef std::map<TrapKey, uint16_t> TrapIds; typedef std::set<ErrorCode, struct ErrorCode::LessThan> Conds; // Get a file descriptor pointing to "/proc", if currently available. @@ -462,6 +432,11 @@ class Sandbox { // been configured with SetSandboxPolicy(). static void InstallFilter(bool quiet); + // 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. + static void VerifyProgram(const Program& program, bool has_unsafe_traps); + // 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 @@ -491,9 +466,6 @@ class Sandbox { // attempted to pass a 64bit value in a 32bit system call argument. static ErrorCode Unexpected64bitArgument(); - static void SigSys(int nr, siginfo_t *info, void *void_context); - static ErrorCode MakeTrap(ErrorCode::TrapFnc fn, const void *aux, bool safe); - // A Trap() handler that returns an "errno" value. The value is encoded // in the "aux" parameter. static intptr_t ReturnErrno(const struct arch_seccomp_data&, void *aux); @@ -503,11 +475,6 @@ class Sandbox { static SandboxStatus status_; static int proc_fd_; static Evaluators evaluators_; - static Traps *traps_; - static TrapIds trap_ids_; - static ErrorCode *trap_array_; - static size_t trap_array_size_; - static bool has_unsafe_traps_; static Conds conds_; DISALLOW_IMPLICIT_CONSTRUCTORS(Sandbox); diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc index 90ec8ff..e9bbb34 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -10,6 +10,7 @@ #include "base/memory/scoped_ptr.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" #include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" #include "sandbox/linux/seccomp-bpf/verifier.h" #include "sandbox/linux/services/broker_process.h" #include "sandbox/linux/services/linux_syscalls.h" @@ -26,7 +27,8 @@ using sandbox::BrokerProcess; namespace { -const int kExpectedReturnValue = 42; +const int kExpectedReturnValue = 42; +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; // This test should execute no matter whether we have kernel support. So, // we make it a TEST() instead of a BPF_TEST(). @@ -299,6 +301,7 @@ ErrorCode GreyListedPolicy(int sysno, void *aux) { // expect any messages on "stderr". So, temporarily disable messages. The // BPF_TEST() is guaranteed to turn messages back on, after the policy // function has completed. + setenv(kSandboxDebuggingEnv, "t", 0); Die::SuppressInfoMessages(true); // Some system calls must always be allowed, if our policy wants to make @@ -338,6 +341,19 @@ BPF_TEST(SandboxBpf, GreyListedPolicy, BPF_ASSERT(*name); } +SANDBOX_TEST(SandboxBpf, EnableUnsafeTrapsInSigSysHandler) { + // Disabling warning messages that could confuse our test framework. + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + unsetenv(kSandboxDebuggingEnv); + SANDBOX_ASSERT(Trap::EnableUnsafeTrapsInSigSysHandler() == false); + setenv(kSandboxDebuggingEnv, "", 1); + SANDBOX_ASSERT(Trap::EnableUnsafeTrapsInSigSysHandler() == false); + setenv(kSandboxDebuggingEnv, "t", 1); + SANDBOX_ASSERT(Trap::EnableUnsafeTrapsInSigSysHandler() == true); +} + intptr_t PrctlHandler(const struct arch_seccomp_data& args, void *) { if (args.args[0] == PR_CAPBSET_DROP && static_cast<int>(args.args[1]) == -1) { @@ -350,6 +366,7 @@ intptr_t PrctlHandler(const struct arch_seccomp_data& args, void *) { } ErrorCode PrctlPolicy(int sysno, void *aux) { + setenv(kSandboxDebuggingEnv, "t", 0); Die::SuppressInfoMessages(true); if (sysno == __NR_prctl) { @@ -391,6 +408,7 @@ intptr_t AllowRedirectedSyscall(const struct arch_seccomp_data& args, void *) { } ErrorCode RedirectAllSyscallsPolicy(int sysno, void *aux) { + setenv(kSandboxDebuggingEnv, "t", 0); Die::SuppressInfoMessages(true); // Some system calls must always be allowed, if our policy wants to make diff --git a/sandbox/linux/seccomp-bpf/trap.cc b/sandbox/linux/seccomp-bpf/trap.cc new file mode 100644 index 0000000..64875ad --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.cc @@ -0,0 +1,343 @@ +// 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 <signal.h> +#include <sys/prctl.h> +#include <sys/syscall.h> + +#ifndef SECCOMP_BPF_STANDALONE +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#endif + +#include "sandbox/linux/seccomp-bpf/codegen.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" + +// Android's signal.h doesn't define ucontext etc. +#if defined(OS_ANDROID) +#include "sandbox/linux/services/android_ucontext.h" +#endif + +#include <limits> + + +namespace { + +const int kCapacityIncrement = 20; + +// Unsafe traps can only be turned on, if the user explicitly allowed them +// by setting the CHROME_SANDBOX_DEBUGGING environment variable. +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; + +// We need to tell whether we are performing a "normal" callback, or +// whether we were called recursively from within a UnsafeTrap() callback. +// This is a little tricky to do, because we need to somehow get access to +// per-thread data from within a signal context. Normal TLS storage is not +// safely accessible at this time. We could roll our own, but that involves +// a lot of complexity. Instead, we co-opt one bit in the signal mask. +// If BUS is blocked, we assume that we have been called recursively. +// There is a possibility for collision with other code that needs to do +// this, but in practice the risks are low. +// If SIGBUS turns out to be a problem, we could instead co-opt one of the +// realtime signals. There are plenty of them. Unfortunately, there is no +// way to mark a signal as allocated. So, the potential for collision is +// possibly even worse. +bool GetIsInSigHandler(const ucontext_t *ctx) { + // Note: on Android, sigismember does not take a pointer to const. + return sigismember(const_cast<sigset_t*>(&ctx->uc_sigmask), SIGBUS); +} + +void SetIsInSigHandler() { + sigset_t mask; + if (sigemptyset(&mask) || + sigaddset(&mask, SIGBUS) || + sigprocmask(SIG_BLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to block SIGBUS"); + } +} + +} // namespace + +namespace playground2 { + +Trap::Trap() + : trap_array_(NULL), + trap_array_size_(0), + trap_array_capacity_(0), + has_unsafe_traps_(false) { + // Set new SIGSYS handler + struct sigaction sa = { }; + sa.sa_sigaction = SigSysAction; + sa.sa_flags = SA_SIGINFO | SA_NODEFER; + if (sigaction(SIGSYS, &sa, NULL) < 0) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } + + // Unmask SIGSYS + sigset_t mask; + if (sigemptyset(&mask) || + sigaddset(&mask, SIGSYS) || + sigprocmask(SIG_UNBLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } +} + +Trap *Trap::GetInstance() { + // 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. + // Also, this is not a normal singleton. Once created, the global trap + // object must never be destroyed again. + if (!global_trap_) { + global_trap_ = new Trap(); + if (!global_trap_) { + SANDBOX_DIE("Failed to allocate global trap handler"); + } + } + return global_trap_; +} + +void Trap::SigSysAction(int nr, siginfo_t *info, void *void_context) { + if (!global_trap_) { + SANDBOX_DIE("This can't happen. Found no global singleton instance " + "for Trap() handling."); + } + global_trap_->SigSys(nr, info, void_context); +} + +void Trap::SigSys(int nr, siginfo_t *info, void *void_context) { + // Various sanity checks to make sure we actually received a signal + // triggered by a BPF filter. If something else triggered SIGSYS + // (e.g. kill()), there is really nothing we can do with this signal. + if (nr != SIGSYS || info->si_code != SYS_SECCOMP || !void_context || + info->si_errno <= 0 || + static_cast<size_t>(info->si_errno) > trap_array_size_) { + // SANDBOX_DIE() can call LOG(FATAL). This is not normally async-signal + // safe and can lead to bugs. We should eventually implement a different + // logging and reporting mechanism that is safe to be called from + // the sigSys() handler. + // TODO: If we feel confident that our code otherwise works correctly, we + // could actually make an argument that spurious SIGSYS should + // just get silently ignored. TBD + sigsys_err: + SANDBOX_DIE("Unexpected SIGSYS received"); + } + + // Signal handlers should always preserve "errno". Otherwise, we could + // trigger really subtle bugs. + int old_errno = errno; + + // Obtain the signal context. This, most notably, gives us access to + // all CPU registers at the time of the signal. + ucontext_t *ctx = reinterpret_cast<ucontext_t *>(void_context); + + // Obtain the siginfo information that is specific to SIGSYS. Unfortunately, + // most versions of glibc don't include this information in siginfo_t. So, + // we need to explicitly copy it into a arch_sigsys structure. + struct arch_sigsys sigsys; + memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); + + // Some more sanity checks. + if (sigsys.ip != reinterpret_cast<void *>(SECCOMP_IP(ctx)) || + sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)) || + sigsys.arch != SECCOMP_ARCH) { + goto sigsys_err; + } + + intptr_t rc; + if (has_unsafe_traps_ && GetIsInSigHandler(ctx)) { + errno = old_errno; + if (sigsys.nr == __NR_clone) { + SANDBOX_DIE("Cannot call clone() from an UnsafeTrap() handler"); + } + rc = SandboxSyscall(sigsys.nr, + SECCOMP_PARM1(ctx), SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), SECCOMP_PARM6(ctx)); + } else { + const ErrorCode& err = trap_array_[info->si_errno - 1]; + if (!err.safe_) { + SetIsInSigHandler(); + } + + // Copy the seccomp-specific data into a arch_seccomp_data structure. This + // is what we are showing to TrapFnc callbacks that the system call + // evaluator registered with the sandbox. + struct arch_seccomp_data data = { + sigsys.nr, + SECCOMP_ARCH, + reinterpret_cast<uint64_t>(sigsys.ip), + { + static_cast<uint64_t>(SECCOMP_PARM1(ctx)), + static_cast<uint64_t>(SECCOMP_PARM2(ctx)), + static_cast<uint64_t>(SECCOMP_PARM3(ctx)), + static_cast<uint64_t>(SECCOMP_PARM4(ctx)), + static_cast<uint64_t>(SECCOMP_PARM5(ctx)), + static_cast<uint64_t>(SECCOMP_PARM6(ctx)) + } + }; + + // Now call the TrapFnc callback associated with this particular instance + // of SECCOMP_RET_TRAP. + rc = err.fnc_(data, err.aux_); + } + + // Update the CPU register that stores the return code of the system call + // that we just handled, and restore "errno" to the value that it had + // before entering the signal handler. + SECCOMP_RESULT(ctx) = static_cast<greg_t>(rc); + errno = old_errno; + + return; +} + +bool Trap::TrapKey::operator<(const TrapKey& o) const { + if (fnc != o.fnc) { + return fnc < o.fnc; + } else if (aux != o.aux) { + return aux < o.aux; + } else { + return safe < o.safe; + } +} + +ErrorCode Trap::MakeTrap(TrapFnc fnc, const void *aux, bool safe) { + return GetInstance()->MakeTrapImpl(fnc, aux, safe); +} + +ErrorCode Trap::MakeTrapImpl(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 + // means, the BPF compiler will never emit code that allow unsafe system + // calls to by-pass the filter (because they use the magic return address + // from SandboxSyscall(-1)). + + // This SANDBOX_DIE() can optionally be removed. It won't break security, + // but it might make error messages from the BPF compiler a little harder + // to understand. Removing the SANDBOX_DIE() allows callers to easyly check + // whether unsafe traps are supported (by checking whether the returned + // ErrorCode is ET_INVALID). + SANDBOX_DIE("Cannot use unsafe traps unless CHROME_SANDBOX_DEBUGGING " + "is enabled"); + + return ErrorCode(); + } + + // Each unique pair of TrapFnc and auxiliary data make up a distinct instance + // of a SECCOMP_RET_TRAP. + TrapKey key(fnc, aux, safe); + TrapIds::const_iterator iter = trap_ids_.find(key); + + // We return unique identifiers together with SECCOMP_RET_TRAP. This allows + // us to associate trap with the appropriate handler. The kernel allows us + // identifiers in the range from 0 to SECCOMP_RET_DATA (0xFFFF). We want to + // avoid 0, as it could be confused for a trap without any specific id. + // The nice thing about sequentially numbered identifiers is that we can also + // trivially look them up from our signal handler without making any system + // calls that might be async-signal-unsafe. + // In order to do so, we store all of our traps in a C-style trap_array_. + uint16_t id; + if (iter != trap_ids_.end()) { + // We have seen this pair before. Return the same id that we assigned + // earlier. + id = iter->second; + } else { + // This is a new pair. Remember it and assign a new id. + if (trap_array_size_ >= SECCOMP_RET_DATA /* 0xFFFF */ || + trap_array_size_ >= std::numeric_limits<typeof(id)>::max()) { + // In practice, this is pretty much impossible to trigger, as there + // are other kernel limitations that restrict overall BPF program sizes. + SANDBOX_DIE("Too many SECCOMP_RET_TRAP callback instances"); + } + id = trap_array_size_ + 1; + + // Our callers ensure that there are no other threads accessing trap_array_ + // concurrently (typically this is done by ensuring that we are single- + // threaded while the sandbox is being set up). But we nonetheless are + // modifying a life data structure that could be accessed any time a + // system call is made; as system calls could be triggering SIGSYS. + // So, we have to be extra careful that we update trap_array_ atomically. + // In particular, this means we shouldn't be using realloc() to resize it. + // Instead, we allocate a new array, copy the values, and then switch the + // pointer. We only really care about the pointer being updated atomically + // and the data that is pointed to being valid, as these are the only + // values accessed from the signal handler. It is OK if trap_array_size_ + // is inconsistent with the pointer, as it is monotonously increasing. + // Also, we only care about compiler barriers, as the signal handler is + // triggered synchronously from a system call. We don't have to protect + // against issues with the memory model or with completely asynchronous + // events. + if (trap_array_size_ >= trap_array_capacity_) { + trap_array_capacity_ += kCapacityIncrement; + ErrorCode *old_trap_array = trap_array_; + ErrorCode *new_trap_array = new ErrorCode[trap_array_capacity_]; + + // Language specs are unclear on whether the compiler is allowed to move + // the "delete[]" above our preceding assignments and/or memory moves, + // iff the compiler believes that "delete[]" doesn't have any other + // global side-effects. + // We insert optimization barriers to prevent this from happening. + // The first barrier is probably not needed, but better be explicit in + // what we want to tell the compiler. + // The clang developer mailing list couldn't answer whether this is a + // legitimate worry; but they at least thought that the barrier is + // sufficient to prevent the (so far hypothetical) problem of re-ordering + // of instructions by the compiler. + memcpy(new_trap_array, trap_array_, trap_array_size_*sizeof(ErrorCode)); + asm volatile("" : "=r"(new_trap_array) : "0"(new_trap_array) : "memory"); + trap_array_ = new_trap_array; + asm volatile("" : "=r"(trap_array_) : "0"(trap_array_) : "memory"); + + delete[] old_trap_array; + } + trap_ids_[key] = id; + trap_array_[trap_array_size_] = ErrorCode(fnc, aux, safe, id); + return trap_array_[trap_array_size_++]; + } + + return ErrorCode(fnc, aux, safe, id); +} + +bool Trap::SandboxDebuggingAllowedByUser() const { + const char *debug_flag = getenv(kSandboxDebuggingEnv); + return debug_flag && *debug_flag; +} + + +bool Trap::EnableUnsafeTrapsInSigSysHandler() { + Trap *trap = GetInstance(); + if (!trap->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()) { + // 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; + } else { + SANDBOX_INFO("Cannot disable sandbox and use unsafe traps unless " + "CHROME_SANDBOX_DEBUGGING is turned on first"); + } + } + // Returns the, possibly updated, value of has_unsafe_traps_. + return trap->has_unsafe_traps_; +} + +ErrorCode Trap::ErrorCodeFromTrapId(uint16_t id) { + if (global_trap_ && id > 0 && id <= global_trap_->trap_array_size_) { + return global_trap_->trap_array_[id - 1]; + } else { + return ErrorCode(); + } +} + +Trap *Trap::global_trap_; + +} // namespace playground2 diff --git a/sandbox/linux/seccomp-bpf/trap.h b/sandbox/linux/seccomp-bpf/trap.h new file mode 100644 index 0000000..40e7109 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.h @@ -0,0 +1,114 @@ +// 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. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ + +#include <map> +#include <vector> + +namespace playground2 { + +class ErrorCode; + +// 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 +// modifications are made to any of the traps, it is the caller's +// responsibility to ensure that this happens in a thread-safe fashion. +// 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 Trap { + 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); + + // 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. + static ErrorCode MakeTrap(TrapFnc fnc, const void *aux, bool safe); + + // Enables support for unsafe traps in the SIGSYS signal handler. This is a + // one-way fuse. It works in conjunction with the BPF compiler emitting code + // that unconditionally allows system calls, if they have a magic return + // address (i.e. SandboxSyscall(-1)). + // Once unsafe traps are enabled, the sandbox is essentially compromised. + // 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. + static bool EnableUnsafeTrapsInSigSysHandler(); + + // Returns the ErrorCode associate with a particular trap id. + static ErrorCode ErrorCodeFromTrapId(uint16_t id); + + private: + // The destructor is unimplemented. Don't ever attempt to destruct this + // object. It'll break subsequent system calls that trigger a SIGSYS. + ~Trap(); + + struct TrapKey { + TrapKey(TrapFnc f, const void *a, bool s) + : fnc(f), + aux(a), + safe(s) { + } + TrapFnc fnc; + const void *aux; + bool safe; + bool operator<(const TrapKey&) const; + }; + typedef std::map<TrapKey, uint16_t> TrapIds; + + // 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); + + void SigSys(int nr, siginfo_t *info, void *void_context); + ErrorCode MakeTrapImpl(TrapFnc fnc, const void *aux, bool safe); + bool SandboxDebuggingAllowedByUser() const; + + + + // We have a global singleton that handles all of our SIGSYS traps. This + // variable must never be deallocated after it has been set up initially, as + // there is no way to reset in-kernel BPF filters that generate SIGSYS + // events. + static Trap *global_trap_; + + TrapIds trap_ids_; // Maps from TrapKeys to numeric ids + ErrorCode *trap_array_; // Array of ErrorCodes indexed by ids + size_t trap_array_size_; // Currently used size of array + size_t trap_array_capacity_; // Currently allocated capacity of array + bool has_unsafe_traps_; // Whether unsafe traps have been enabled + + // Our constructor is private. A shared global instance is created + // automatically as needed. + // Copying and assigning is unimplemented. It doesn't make sense for a + // singleton. + DISALLOW_IMPLICIT_CONSTRUCTORS(Trap); +}; + +} // namespace playground2 + +#endif // SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ |