// 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/trap.h"
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <limits>
#include "base/logging.h"
#include "sandbox/linux/seccomp-bpf/codegen.h"
#include "sandbox/linux/seccomp-bpf/die.h"
#include "sandbox/linux/seccomp-bpf/syscall.h"
// Android's signal.h doesn't define ucontext etc.
#if defined(OS_ANDROID)
#include "sandbox/linux/services/android_ucontext.h"
#endif
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");
}
}
bool IsDefaultSignalAction(const struct sigaction& sa) {
if (sa.sa_flags & SA_SIGINFO || sa.sa_handler != SIG_DFL) {
return false;
}
return true;
}
} // namespace
namespace sandbox {
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;
struct sigaction old_sa;
if (sigaction(SIGSYS, &sa, &old_sa) < 0) {
SANDBOX_DIE("Failed to configure SIGSYS handler");
}
if (!IsDefaultSignalAction(old_sa)) {
static const char kExistingSIGSYSMsg[] =
"Existing signal handler when trying to install SIGSYS. SIGSYS needs "
"to be reserved for seccomp-bpf.";
DLOG(FATAL) << kExistingSIGSYSMsg;
LOG(ERROR) << kExistingSIGSYSMsg;
}
// 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_) {
RAW_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) {
// Signal handlers should always preserve "errno". Otherwise, we could
// trigger really subtle bugs.
const int old_errno = errno;
// 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_) {
// ATI drivers seem to send SIGSYS, so this cannot be FATAL.
// See crbug.com/178166.
// TODO(jln): add a DCHECK or move back to FATAL.
RAW_LOG(ERROR, "Unexpected SIGSYS received.");
errno = old_errno;
return;
}
// 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) {
// TODO(markus):
// 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.
RAW_SANDBOX_DIE("Sanity checks are failing after receiving SIGSYS.");
}
intptr_t rc;
if (has_unsafe_traps_ && GetIsInSigHandler(ctx)) {
errno = old_errno;
if (sigsys.nr == __NR_clone) {
RAW_SANDBOX_DIE("Cannot call clone() from an UnsafeTrap() handler.");
}
rc = Syscall::Call(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 Syscall::Call(-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 sandbox