// 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_SANDBOX_BPF_H__ #define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H__ #include <stddef.h> #include <sys/types.h> #include <sys/wait.h> #include <algorithm> #include <limits> #include <map> #include <set> #include <utility> #include <vector> #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/errorcode.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.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]; }; struct arch_sigsys { void* ip; int nr; unsigned int arch; }; class CodeGen; class SandboxBPFPolicy; class SandboxUnittestHelper; struct Instruction; class SANDBOX_EXPORT SandboxBPF { public: enum SandboxStatus { STATUS_UNKNOWN, // Status prior to calling supportsSeccompSandbox() STATUS_UNSUPPORTED, // The kernel does not appear to support sandboxing STATUS_UNAVAILABLE, // Currently unavailable but might work again later STATUS_AVAILABLE, // Sandboxing is available but not currently active STATUS_ENABLED // The sandbox is now active }; // Depending on the level of kernel support, seccomp-bpf may require the // process to be single-threaded in order to enable it. When calling // StartSandbox(), the program should indicate whether or not the sandbox // should try and engage with multi-thread support. enum SandboxThreadState { PROCESS_INVALID, PROCESS_SINGLE_THREADED, // The program is currently single-threaded. // Note: PROCESS_MULTI_THREADED requires experimental kernel support that // has not been contributed to upstream Linux. 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 // sandbox. Strictly speaking, that means we should disallow calling // the destructor, if StartSandbox() has ever been called. In practice, // this makes it needlessly complicated to operate on "Sandbox" // objects. So, we instead opted to allow object destruction. But it // should be noted that during its lifetime, the object probably made // irreversible state changes to the runtime environment. These changes // stay in effect even after the destructor has been run. SandboxBPF(); ~SandboxBPF(); // 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 // system calls. static bool IsValidSyscallNumber(int sysnum); // There are a lot of reasons why the Seccomp sandbox might not be available. // This could be because the kernel does not support Seccomp mode, or it // could be because another sandbox is already active. // "proc_fd" should be a file descriptor for "/proc", or -1 if not // provided by the caller. static SandboxStatus SupportsSeccompSandbox(int proc_fd); // The sandbox needs to be able to access files in "/proc/self". If this // directory is not accessible when "startSandbox()" gets called, the caller // can provide an already opened file descriptor by calling "set_proc_fd()". // The sandbox becomes the new owner of this file descriptor and will // eventually close it when "StartSandbox()" executes. void set_proc_fd(int proc_fd); // Set the BPF policy as |policy|. Ownership of |policy| is transfered here // to the sandbox object. void SetSandboxPolicy(SandboxBPFPolicy* policy); // 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. 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. 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 // makes this easy. It is more efficient than calling glibc's syscall() // function, as it avoid the extra round-trip to the signal handler. And // it automatically does the correct thing to report kernel-style error // conditions, rather than setting errno. See the comments for TrapFnc for // details. In other words, the return value from ForwardSyscall() is // 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 compared to "value" using comparator // "op". If the condition is true "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 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. 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. // The calling process must specify its current SandboxThreadState, as a way // to tell the sandbox which type of kernel support it should engage. // It is possible to stack multiple sandboxes by creating separate "Sandbox" // objects and calling "StartSandbox()" on each of them. Please note, that // this requires special care, though, as newly stacked sandboxes can never // relax restrictions imposed by earlier sandboxes. Furthermore, installing // a new policy requires making system calls, that might already be // disallowed. // Finally, stacking does add more kernel overhead than having a single // combined policy. So, it should only be used if there are no alternatives. bool StartSandbox(SandboxThreadState thread_state) WARN_UNUSED_RESULT; // Assembles a BPF filter program from the current policy. After calling this // function, you must not call any other sandboxing function. // Typically, AssembleFilter() is only used by unit tests and by sandbox // internals. It should not be used by production code. // For performance reasons, we normally only run the assembled BPF program // 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. ErrorCode Unexpected64bitArgument(); 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; // Get a file descriptor pointing to "/proc", if currently available. int proc_fd() { return proc_fd_; } // Creates a subprocess and runs "code_in_sandbox" inside of the specified // policy. The caller has to make sure that "this" has not yet been // initialized with any other policies. bool RunFunctionInPolicy(void (*code_in_sandbox)(), scoped_ptr<SandboxBPFPolicy> policy); // Performs a couple of sanity checks to verify that the kernel supports the // features that we need for successful sandboxing. // The caller has to make sure that "this" has not yet been initialized with // any other policies. bool KernelSupportSeccompBPF(); // Verify that the current policy passes some basic sanity checks. void PolicySanityChecks(SandboxBPFPolicy* policy); // Assembles and installs a filter based on the policy that has previously // been configured with SetSandboxPolicy(). void InstallFilter(SandboxThreadState thread_state); // 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, 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 // 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); static SandboxStatus status_; bool quiet_; int proc_fd_; scoped_ptr<const SandboxBPFPolicy> policy_; Conds* conds_; bool sandbox_has_started_; DISALLOW_COPY_AND_ASSIGN(SandboxBPF); }; } // namespace sandbox #endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H__