Home | History | Annotate | Download | only in bpf_dsl
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #ifndef SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_
      6 #define SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_
      7 
      8 #include <stdint.h>
      9 
     10 #include <utility>
     11 #include <vector>
     12 
     13 #include "base/macros.h"
     14 #include "base/memory/ref_counted.h"
     15 #include "sandbox/linux/bpf_dsl/cons.h"
     16 #include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
     17 #include "sandbox/linux/seccomp-bpf/trap.h"
     18 #include "sandbox/sandbox_export.h"
     19 
     20 namespace sandbox {
     21 class ErrorCode;
     22 class SandboxBPF;
     23 }
     24 
     25 // The sandbox::bpf_dsl namespace provides a domain-specific language
     26 // to make writing BPF policies more expressive.  In general, the
     27 // object types all have value semantics (i.e., they can be copied
     28 // around, returned from or passed to function calls, etc. without any
     29 // surprising side effects), though not all support assignment.
     30 //
     31 // An idiomatic and demonstrative (albeit silly) example of this API
     32 // would be:
     33 //
     34 //      #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
     35 //
     36 //      using namespace sandbox::bpf_dsl;
     37 //
     38 //      class SillyPolicy : public SandboxBPFDSLPolicy {
     39 //       public:
     40 //        SillyPolicy() {}
     41 //        virtual ~SillyPolicy() {}
     42 //        virtual ResultExpr EvaluateSyscall(int sysno) const OVERRIDE {
     43 //          if (sysno == __NR_fcntl) {
     44 //            Arg<int> fd(0), cmd(1);
     45 //            Arg<unsigned long> flags(2);
     46 //            const uint64_t kGoodFlags = O_ACCMODE | O_NONBLOCK;
     47 //            return If(fd == 0 && cmd == F_SETFL && (flags & ~kGoodFlags) == 0,
     48 //                      Allow())
     49 //                .ElseIf(cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC,
     50 //                        Error(EMFILE))
     51 //                .Else(Trap(SetFlagHandler, NULL));
     52 //          } else {
     53 //            return Allow();
     54 //          }
     55 //        }
     56 //
     57 //       private:
     58 //        DISALLOW_COPY_AND_ASSIGN(SillyPolicy);
     59 //      };
     60 //
     61 // More generally, the DSL currently supports the following grammar:
     62 //
     63 //   result = Allow() | Error(errno) | Kill(msg) | Trace(aux)
     64 //          | Trap(trap_func, aux) | UnsafeTrap(trap_func, aux)
     65 //          | If(bool, result)[.ElseIf(bool, result)].Else(result)
     66 //          | Switch(arg)[.Case(val, result)].Default(result)
     67 //   bool   = BoolConst(boolean) | !bool | bool && bool | bool || bool
     68 //          | arg == val | arg != val
     69 //   arg    = Arg<T>(num) | arg & mask
     70 //
     71 // The semantics of each function and operator are intended to be
     72 // intuitive, but are described in more detail below.
     73 //
     74 // (Credit to Sean Parent's "Inheritance is the Base Class of Evil"
     75 // talk at Going Native 2013 for promoting value semantics via shared
     76 // pointers to immutable state.)
     77 
     78 namespace sandbox {
     79 namespace bpf_dsl {
     80 
     81 // Forward declarations of classes; see below for proper documentation.
     82 class Elser;
     83 template <typename T>
     84 class Caser;
     85 namespace internal {
     86 class ResultExprImpl;
     87 class BoolExprImpl;
     88 }
     89 
     90 // ResultExpr is an opaque reference to an immutable result expression tree.
     91 typedef scoped_refptr<const internal::ResultExprImpl> ResultExpr;
     92 
     93 // BoolExpr is an opaque reference to an immutable boolean expression tree.
     94 typedef scoped_refptr<const internal::BoolExprImpl> BoolExpr;
     95 
     96 // Helper class to make writing policies easier.
     97 class SANDBOX_EXPORT SandboxBPFDSLPolicy : public SandboxBPFPolicy {
     98  public:
     99   SandboxBPFDSLPolicy() : SandboxBPFPolicy() {}
    100   virtual ~SandboxBPFDSLPolicy() {}
    101 
    102   // User extension point for writing custom sandbox policies.
    103   virtual ResultExpr EvaluateSyscall(int sysno) const = 0;
    104 
    105   // Optional overload for specifying alternate behavior for invalid
    106   // system calls.  The default is to return ENOSYS.
    107   virtual ResultExpr InvalidSyscall() const;
    108 
    109   // Override implementations from SandboxBPFPolicy.  Marked as FINAL
    110   // to prevent mixups with child classes accidentally overloading
    111   // these instead of the above methods.
    112   virtual ErrorCode EvaluateSyscall(SandboxBPF* sb,
    113                                     int sysno) const OVERRIDE FINAL;
    114   virtual ErrorCode InvalidSyscall(SandboxBPF* sb) const OVERRIDE FINAL;
    115 
    116   // Helper method so policies can just write Trap(func, aux).
    117   static ResultExpr Trap(Trap::TrapFnc trap_func, const void* aux);
    118 
    119  private:
    120   DISALLOW_COPY_AND_ASSIGN(SandboxBPFDSLPolicy);
    121 };
    122 
    123 // Allow specifies a result that the system call should be allowed to
    124 // execute normally.
    125 SANDBOX_EXPORT ResultExpr Allow();
    126 
    127 // Error specifies a result that the system call should fail with
    128 // error number |err|.  As a special case, Error(0) will result in the
    129 // system call appearing to have succeeded, but without having any
    130 // side effects.
    131 SANDBOX_EXPORT ResultExpr Error(int err);
    132 
    133 // Kill specifies a result to kill the program and print an error message.
    134 SANDBOX_EXPORT ResultExpr Kill(const char* msg);
    135 
    136 // Trace specifies a result to notify a tracing process via the
    137 // PTRACE_EVENT_SECCOMP event and allow it to change or skip the system call.
    138 // The value of |aux| will be available to the tracer via PTRACE_GETEVENTMSG.
    139 SANDBOX_EXPORT ResultExpr Trace(uint16_t aux);
    140 
    141 // Trap specifies a result that the system call should be handled by
    142 // trapping back into userspace and invoking |trap_func|, passing
    143 // |aux| as the second parameter.
    144 SANDBOX_EXPORT ResultExpr Trap(Trap::TrapFnc trap_func, const void* aux);
    145 
    146 // UnsafeTrap is like Trap, except the policy is marked as "unsafe"
    147 // and allowed to use SandboxSyscall to invoke any system call.
    148 //
    149 // NOTE: This feature, by definition, disables all security features of
    150 //   the sandbox. It should never be used in production, but it can be
    151 //   very useful to diagnose code that is incompatible with the sandbox.
    152 //   If even a single system call returns "UnsafeTrap", the security of
    153 //   entire sandbox should be considered compromised.
    154 SANDBOX_EXPORT ResultExpr UnsafeTrap(Trap::TrapFnc trap_func, const void* aux);
    155 
    156 // BoolConst converts a bool value into a BoolExpr.
    157 SANDBOX_EXPORT BoolExpr BoolConst(bool value);
    158 
    159 // Various ways to combine boolean expressions into more complex expressions.
    160 // They follow standard boolean algebra laws.
    161 SANDBOX_EXPORT BoolExpr operator!(const BoolExpr& cond);
    162 SANDBOX_EXPORT BoolExpr operator&&(const BoolExpr& lhs, const BoolExpr& rhs);
    163 SANDBOX_EXPORT BoolExpr operator||(const BoolExpr& lhs, const BoolExpr& rhs);
    164 
    165 template <typename T>
    166 class SANDBOX_EXPORT Arg {
    167  public:
    168   // Initializes the Arg to represent the |num|th system call
    169   // argument (indexed from 0), which is of type |T|.
    170   explicit Arg(int num);
    171 
    172   Arg(const Arg& arg) : num_(arg.num_), mask_(arg.mask_) {}
    173 
    174   // Returns an Arg representing the current argument, but after
    175   // bitwise-and'ing it with |rhs|.
    176   friend Arg operator&(const Arg& lhs, uint64_t rhs) {
    177     return Arg(lhs.num_, lhs.mask_ & rhs);
    178   }
    179 
    180   // Returns a boolean expression comparing whether the system call argument
    181   // (after applying any bitmasks, if appropriate) equals |rhs|.
    182   friend BoolExpr operator==(const Arg& lhs, T rhs) { return lhs.EqualTo(rhs); }
    183 
    184   // Returns a boolean expression comparing whether the system call argument
    185   // (after applying any bitmasks, if appropriate) does not equal |rhs|.
    186   friend BoolExpr operator!=(const Arg& lhs, T rhs) { return !(lhs == rhs); }
    187 
    188  private:
    189   Arg(int num, uint64_t mask) : num_(num), mask_(mask) {}
    190 
    191   BoolExpr EqualTo(T val) const;
    192 
    193   int num_;
    194   uint64_t mask_;
    195 
    196   DISALLOW_ASSIGN(Arg);
    197 };
    198 
    199 // If begins a conditional result expression predicated on the
    200 // specified boolean expression.
    201 SANDBOX_EXPORT Elser If(const BoolExpr& cond, const ResultExpr& then_result);
    202 
    203 class SANDBOX_EXPORT Elser {
    204  public:
    205   Elser(const Elser& elser);
    206   ~Elser();
    207 
    208   // ElseIf extends the conditional result expression with another
    209   // "if then" clause, predicated on the specified boolean expression.
    210   Elser ElseIf(const BoolExpr& cond, const ResultExpr& then_result) const;
    211 
    212   // Else terminates a conditional result expression using |else_result| as
    213   // the default fallback result expression.
    214   ResultExpr Else(const ResultExpr& else_result) const;
    215 
    216  private:
    217   typedef std::pair<BoolExpr, ResultExpr> Clause;
    218 
    219   explicit Elser(Cons<Clause>::List clause_list);
    220 
    221   Cons<Clause>::List clause_list_;
    222 
    223   friend Elser If(const BoolExpr&, const ResultExpr&);
    224   template <typename T>
    225   friend Caser<T> Switch(const Arg<T>&);
    226   DISALLOW_ASSIGN(Elser);
    227 };
    228 
    229 // Switch begins a switch expression dispatched according to the
    230 // specified argument value.
    231 template <typename T>
    232 SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg);
    233 
    234 template <typename T>
    235 class SANDBOX_EXPORT Caser {
    236  public:
    237   Caser(const Caser<T>& caser) : arg_(caser.arg_), elser_(caser.elser_) {}
    238   ~Caser() {}
    239 
    240   // Case adds a single-value "case" clause to the switch.
    241   Caser<T> Case(T value, ResultExpr result) const;
    242 
    243   // Cases adds a multiple-value "case" clause to the switch.
    244   // See also the SANDBOX_BPF_DSL_CASES macro below for a more idiomatic way
    245   // of using this function.
    246   Caser<T> Cases(const std::vector<T>& values, ResultExpr result) const;
    247 
    248   // Terminate the switch with a "default" clause.
    249   ResultExpr Default(ResultExpr result) const;
    250 
    251  private:
    252   Caser(const Arg<T>& arg, Elser elser) : arg_(arg), elser_(elser) {}
    253 
    254   Arg<T> arg_;
    255   Elser elser_;
    256 
    257   template <typename U>
    258   friend Caser<U> Switch(const Arg<U>&);
    259   DISALLOW_ASSIGN(Caser);
    260 };
    261 
    262 // Recommended usage is to put
    263 //    #define CASES SANDBOX_BPF_DSL_CASES
    264 // near the top of the .cc file (e.g., nearby any "using" statements), then
    265 // use like:
    266 //    Switch(arg).CASES((3, 5, 7), result)...;
    267 #define SANDBOX_BPF_DSL_CASES(values, result) \
    268   Cases(SANDBOX_BPF_DSL_CASES_HELPER values, result)
    269 
    270 // Helper macro to construct a std::vector from an initializer list.
    271 // TODO(mdempsky): Convert to use C++11 initializer lists instead.
    272 #define SANDBOX_BPF_DSL_CASES_HELPER(value, ...)                           \
    273   ({                                                                       \
    274     const __typeof__(value) bpf_dsl_cases_values[] = {value, __VA_ARGS__}; \
    275     std::vector<__typeof__(value)>(                                        \
    276         bpf_dsl_cases_values,                                              \
    277         bpf_dsl_cases_values + arraysize(bpf_dsl_cases_values));           \
    278   })
    279 
    280 // =====================================================================
    281 // Official API ends here.
    282 // =====================================================================
    283 
    284 // Definitions below are necessary here only for C++03 compatibility.
    285 // Once C++11 is available, they should be moved into bpf_dsl.cc via extern
    286 // templates.
    287 namespace internal {
    288 
    289 // Make argument-dependent lookup work.  This is necessary because although
    290 // BoolExpr is defined in bpf_dsl, since it's merely a typedef for
    291 // scoped_refptr<const internal::BoolExplImpl>, argument-dependent lookup only
    292 // searches the "internal" nested namespace.
    293 using bpf_dsl::operator!;
    294 using bpf_dsl::operator||;
    295 using bpf_dsl::operator&&;
    296 
    297 // Returns a boolean expression that represents whether system call
    298 // argument |num| of size |size| is equal to |val|, when masked
    299 // according to |mask|.  Users should use the Arg template class below
    300 // instead of using this API directly.
    301 SANDBOX_EXPORT BoolExpr
    302     ArgEq(int num, size_t size, uint64_t mask, uint64_t val);
    303 
    304 // Returns the default mask for a system call argument of the specified size.
    305 SANDBOX_EXPORT uint64_t DefaultMask(size_t size);
    306 
    307 // Internal interface implemented by BoolExpr implementations.
    308 class SANDBOX_EXPORT BoolExprImpl : public base::RefCounted<BoolExprImpl> {
    309  public:
    310   BoolExprImpl() {}
    311   virtual ErrorCode Compile(SandboxBPF* sb,
    312                             ErrorCode true_ec,
    313                             ErrorCode false_ec) const = 0;
    314 
    315  protected:
    316   virtual ~BoolExprImpl() {}
    317 
    318  private:
    319   friend class base::RefCounted<BoolExprImpl>;
    320   DISALLOW_COPY_AND_ASSIGN(BoolExprImpl);
    321 };
    322 
    323 // Internal interface implemented by ResultExpr implementations.
    324 class SANDBOX_EXPORT ResultExprImpl : public base::RefCounted<ResultExprImpl> {
    325  public:
    326   ResultExprImpl() {}
    327   virtual ErrorCode Compile(SandboxBPF* sb) const = 0;
    328 
    329  protected:
    330   virtual ~ResultExprImpl() {}
    331 
    332  private:
    333   friend class base::RefCounted<ResultExprImpl>;
    334   DISALLOW_COPY_AND_ASSIGN(ResultExprImpl);
    335 };
    336 
    337 }  // namespace internal
    338 
    339 template <typename T>
    340 Arg<T>::Arg(int num)
    341     : num_(num), mask_(internal::DefaultMask(sizeof(T))) {
    342 }
    343 
    344 // Definition requires ArgEq to have been declared.  Moved out-of-line
    345 // to minimize how much internal clutter users have to ignore while
    346 // reading the header documentation.
    347 //
    348 // Additionally, we use this helper member function to avoid linker errors
    349 // caused by defining operator== out-of-line.  For a more detailed explanation,
    350 // see http://www.parashift.com/c++-faq-lite/template-friends.html.
    351 template <typename T>
    352 BoolExpr Arg<T>::EqualTo(T val) const {
    353   return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint64_t>(val));
    354 }
    355 
    356 template <typename T>
    357 SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg) {
    358   return Caser<T>(arg, Elser(Cons<Elser::Clause>::List()));
    359 }
    360 
    361 template <typename T>
    362 Caser<T> Caser<T>::Case(T value, ResultExpr result) const {
    363   return SANDBOX_BPF_DSL_CASES((value), result);
    364 }
    365 
    366 template <typename T>
    367 Caser<T> Caser<T>::Cases(const std::vector<T>& values,
    368                          ResultExpr result) const {
    369   // Theoretically we could evaluate arg_ just once and emit a more efficient
    370   // dispatch table, but for now we simply translate into an equivalent
    371   // If/ElseIf/Else chain.
    372 
    373   typedef typename std::vector<T>::const_iterator Iter;
    374   BoolExpr test = BoolConst(false);
    375   for (Iter i = values.begin(), end = values.end(); i != end; ++i) {
    376     test = test || (arg_ == *i);
    377   }
    378   return Caser<T>(arg_, elser_.ElseIf(test, result));
    379 }
    380 
    381 template <typename T>
    382 ResultExpr Caser<T>::Default(ResultExpr result) const {
    383   return elser_.Else(result);
    384 }
    385 
    386 }  // namespace bpf_dsl
    387 }  // namespace sandbox
    388 
    389 #endif  // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_
    390