// Copyright 2014 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_BPF_DSL_BPF_DSL_H_ #define SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ #include #include #include #include #include #include "base/macros.h" #include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" #include "sandbox/linux/bpf_dsl/cons.h" #include "sandbox/linux/bpf_dsl/trap_registry.h" #include "sandbox/sandbox_export.h" // The sandbox::bpf_dsl namespace provides a domain-specific language // to make writing BPF policies more expressive. In general, the // object types all have value semantics (i.e., they can be copied // around, returned from or passed to function calls, etc. without any // surprising side effects), though not all support assignment. // // An idiomatic and demonstrative (albeit silly) example of this API // would be: // // #include "sandbox/linux/bpf_dsl/bpf_dsl.h" // // using namespace sandbox::bpf_dsl; // // class SillyPolicy : public Policy { // public: // SillyPolicy() {} // ~SillyPolicy() override {} // ResultExpr EvaluateSyscall(int sysno) const override { // if (sysno == __NR_fcntl) { // Arg fd(0), cmd(1); // Arg flags(2); // const uint64_t kGoodFlags = O_ACCMODE | O_NONBLOCK; // return If(AllOf(fd == 0, // cmd == F_SETFL, // (flags & ~kGoodFlags) == 0), // Allow()) // .ElseIf(AnyOf(cmd == F_DUPFD, cmd == F_DUPFD_CLOEXEC), // Error(EMFILE)) // .Else(Trap(SetFlagHandler, NULL)); // } else { // return Allow(); // } // } // // private: // DISALLOW_COPY_AND_ASSIGN(SillyPolicy); // }; // // More generally, the DSL currently supports the following grammar: // // result = Allow() | Error(errno) | Kill() | Trace(aux) // | Trap(trap_func, aux) | UnsafeTrap(trap_func, aux) // | If(bool, result)[.ElseIf(bool, result)].Else(result) // | Switch(arg)[.Case(val, result)].Default(result) // bool = BoolConst(boolean) | Not(bool) | AllOf(bool...) | AnyOf(bool...) // | arg == val | arg != val // arg = Arg(num) | arg & mask // // The semantics of each function and operator are intended to be // intuitive, but are described in more detail below. // // (Credit to Sean Parent's "Inheritance is the Base Class of Evil" // talk at Going Native 2013 for promoting value semantics via shared // pointers to immutable state.) namespace sandbox { namespace bpf_dsl { // ResultExpr is an opaque reference to an immutable result expression tree. using ResultExpr = std::shared_ptr; // BoolExpr is an opaque reference to an immutable boolean expression tree. using BoolExpr = std::shared_ptr; // Allow specifies a result that the system call should be allowed to // execute normally. SANDBOX_EXPORT ResultExpr Allow(); // Error specifies a result that the system call should fail with // error number |err|. As a special case, Error(0) will result in the // system call appearing to have succeeded, but without having any // side effects. SANDBOX_EXPORT ResultExpr Error(int err); // Kill specifies a result to kill the process (task) immediately. SANDBOX_EXPORT ResultExpr Kill(); // Trace specifies a result to notify a tracing process via the // PTRACE_EVENT_SECCOMP event and allow it to change or skip the system call. // The value of |aux| will be available to the tracer via PTRACE_GETEVENTMSG. SANDBOX_EXPORT ResultExpr Trace(uint16_t aux); // Trap specifies a result that the system call should be handled by // trapping back into userspace and invoking |trap_func|, passing // |aux| as the second parameter. SANDBOX_EXPORT ResultExpr Trap(TrapRegistry::TrapFnc trap_func, const void* aux); // UnsafeTrap is like Trap, except the policy is marked as "unsafe" // and allowed to use SandboxSyscall to invoke any system call. // // 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. SANDBOX_EXPORT ResultExpr UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux); // BoolConst converts a bool value into a BoolExpr. SANDBOX_EXPORT BoolExpr BoolConst(bool value); // Not returns a BoolExpr representing the logical negation of |cond|. SANDBOX_EXPORT BoolExpr Not(BoolExpr cond); // AllOf returns a BoolExpr representing the logical conjunction ("and") // of zero or more BoolExprs. SANDBOX_EXPORT BoolExpr AllOf(); SANDBOX_EXPORT BoolExpr AllOf(BoolExpr lhs, BoolExpr rhs); template SANDBOX_EXPORT BoolExpr AllOf(BoolExpr first, Rest&&... rest); // AnyOf returns a BoolExpr representing the logical disjunction ("or") // of zero or more BoolExprs. SANDBOX_EXPORT BoolExpr AnyOf(); SANDBOX_EXPORT BoolExpr AnyOf(BoolExpr lhs, BoolExpr rhs); template SANDBOX_EXPORT BoolExpr AnyOf(BoolExpr first, Rest&&... rest); template class SANDBOX_EXPORT Arg { public: // Initializes the Arg to represent the |num|th system call // argument (indexed from 0), which is of type |T|. explicit Arg(int num); Arg(const Arg& arg) : num_(arg.num_), mask_(arg.mask_) {} // Returns an Arg representing the current argument, but after // bitwise-and'ing it with |rhs|. friend Arg operator&(const Arg& lhs, uint64_t rhs) { return Arg(lhs.num_, lhs.mask_ & rhs); } // Returns a boolean expression comparing whether the system call argument // (after applying any bitmasks, if appropriate) equals |rhs|. friend BoolExpr operator==(const Arg& lhs, T rhs) { return lhs.EqualTo(rhs); } // Returns a boolean expression comparing whether the system call argument // (after applying any bitmasks, if appropriate) does not equal |rhs|. friend BoolExpr operator!=(const Arg& lhs, T rhs) { return Not(lhs == rhs); } private: Arg(int num, uint64_t mask) : num_(num), mask_(mask) {} BoolExpr EqualTo(T val) const; int num_; uint64_t mask_; DISALLOW_ASSIGN(Arg); }; // If begins a conditional result expression predicated on the // specified boolean expression. SANDBOX_EXPORT Elser If(BoolExpr cond, ResultExpr then_result); class SANDBOX_EXPORT Elser { public: Elser(const Elser& elser); ~Elser(); // ElseIf extends the conditional result expression with another // "if then" clause, predicated on the specified boolean expression. Elser ElseIf(BoolExpr cond, ResultExpr then_result) const; // Else terminates a conditional result expression using |else_result| as // the default fallback result expression. ResultExpr Else(ResultExpr else_result) const; private: using Clause = std::pair; explicit Elser(cons::List clause_list); cons::List clause_list_; friend Elser If(BoolExpr, ResultExpr); template friend Caser Switch(const Arg&); DISALLOW_ASSIGN(Elser); }; // Switch begins a switch expression dispatched according to the // specified argument value. template SANDBOX_EXPORT Caser Switch(const Arg& arg); template class SANDBOX_EXPORT Caser { public: Caser(const Caser& caser) : arg_(caser.arg_), elser_(caser.elser_) {} ~Caser() {} // Case adds a single-value "case" clause to the switch. Caser Case(T value, ResultExpr result) const; // Cases adds a multiple-value "case" clause to the switch. // See also the SANDBOX_BPF_DSL_CASES macro below for a more idiomatic way // of using this function. template Caser CasesImpl(ResultExpr result, const Values&... values) const; // Terminate the switch with a "default" clause. ResultExpr Default(ResultExpr result) const; private: Caser(const Arg& arg, Elser elser) : arg_(arg), elser_(elser) {} Arg arg_; Elser elser_; template friend Caser Switch(const Arg&); DISALLOW_ASSIGN(Caser); }; // Recommended usage is to put // #define CASES SANDBOX_BPF_DSL_CASES // near the top of the .cc file (e.g., nearby any "using" statements), then // use like: // Switch(arg).CASES((3, 5, 7), result)...; #define SANDBOX_BPF_DSL_CASES(values, result) \ CasesImpl(result, SANDBOX_BPF_DSL_CASES_HELPER values) // Helper macro to strip parentheses. #define SANDBOX_BPF_DSL_CASES_HELPER(...) __VA_ARGS__ // ===================================================================== // Official API ends here. // ===================================================================== namespace internal { // Make argument-dependent lookup work. This is necessary because although // BoolExpr is defined in bpf_dsl, since it's merely a typedef for // scoped_refptr, argument-dependent lookup only // searches the "internal" nested namespace. using bpf_dsl::Not; using bpf_dsl::AllOf; using bpf_dsl::AnyOf; // Returns a boolean expression that represents whether system call // argument |num| of size |size| is equal to |val|, when masked // according to |mask|. Users should use the Arg template class below // instead of using this API directly. SANDBOX_EXPORT BoolExpr ArgEq(int num, size_t size, uint64_t mask, uint64_t val); // Returns the default mask for a system call argument of the specified size. SANDBOX_EXPORT uint64_t DefaultMask(size_t size); } // namespace internal template Arg::Arg(int num) : num_(num), mask_(internal::DefaultMask(sizeof(T))) { } // Definition requires ArgEq to have been declared. Moved out-of-line // to minimize how much internal clutter users have to ignore while // reading the header documentation. // // Additionally, we use this helper member function to avoid linker errors // caused by defining operator== out-of-line. For a more detailed explanation, // see http://www.parashift.com/c++-faq-lite/template-friends.html. template BoolExpr Arg::EqualTo(T val) const { if (sizeof(T) == 4) { // Prevent sign-extension of negative int32_t values. return internal::ArgEq(num_, sizeof(T), mask_, static_cast(val)); } return internal::ArgEq(num_, sizeof(T), mask_, static_cast(val)); } template SANDBOX_EXPORT Caser Switch(const Arg& arg) { return Caser(arg, Elser(nullptr)); } template Caser Caser::Case(T value, ResultExpr result) const { return SANDBOX_BPF_DSL_CASES((value), std::move(result)); } template template Caser Caser::CasesImpl(ResultExpr result, const Values&... values) const { // Theoretically we could evaluate arg_ just once and emit a more efficient // dispatch table, but for now we simply translate into an equivalent // If/ElseIf/Else chain. return Caser(arg_, elser_.ElseIf(AnyOf((arg_ == values)...), std::move(result))); } template ResultExpr Caser::Default(ResultExpr result) const { return elser_.Else(std::move(result)); } template BoolExpr AllOf(BoolExpr first, Rest&&... rest) { return AllOf(std::move(first), AllOf(std::forward(rest)...)); } template BoolExpr AnyOf(BoolExpr first, Rest&&... rest) { return AnyOf(std::move(first), AnyOf(std::forward(rest)...)); } } // namespace bpf_dsl } // namespace sandbox #endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_