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 <stddef.h>
9 #include <stdint.h>
10 
11 #include <utility>
12 #include <vector>
13 
14 #include "base/macros.h"
15 #include "base/memory/ref_counted.h"
16 #include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h"
17 #include "sandbox/linux/bpf_dsl/cons.h"
18 #include "sandbox/linux/bpf_dsl/trap_registry.h"
19 #include "sandbox/sandbox_export.h"
20 
21 // The sandbox::bpf_dsl namespace provides a domain-specific language
22 // to make writing BPF policies more expressive.  In general, the
23 // object types all have value semantics (i.e., they can be copied
24 // around, returned from or passed to function calls, etc. without any
25 // surprising side effects), though not all support assignment.
26 //
27 // An idiomatic and demonstrative (albeit silly) example of this API
28 // would be:
29 //
30 //      #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
31 //
32 //      using namespace sandbox::bpf_dsl;
33 //
34 //      class SillyPolicy : public Policy {
35 //       public:
36 //        SillyPolicy() {}
37 //        ~SillyPolicy() override {}
38 //        ResultExpr EvaluateSyscall(int sysno) const override {
39 //          if (sysno == __NR_fcntl) {
40 //            Arg<int> fd(0), cmd(1);
41 //            Arg<unsigned long> flags(2);
42 //            const uint64_t kGoodFlags = O_ACCMODE | O_NONBLOCK;
43 //            return If(AllOf(fd == 0,
44 //                            cmd == F_SETFL,
45 //                            (flags & ~kGoodFlags) == 0),
46 //                      Allow())
47 //                .ElseIf(AnyOf(cmd == F_DUPFD, cmd == F_DUPFD_CLOEXEC),
48 //                        Error(EMFILE))
49 //                .Else(Trap(SetFlagHandler, NULL));
50 //          } else {
51 //            return Allow();
52 //          }
53 //        }
54 //
55 //       private:
56 //        DISALLOW_COPY_AND_ASSIGN(SillyPolicy);
57 //      };
58 //
59 // More generally, the DSL currently supports the following grammar:
60 //
61 //   result = Allow() | Error(errno) | Kill() | Trace(aux)
62 //          | Trap(trap_func, aux) | UnsafeTrap(trap_func, aux)
63 //          | If(bool, result)[.ElseIf(bool, result)].Else(result)
64 //          | Switch(arg)[.Case(val, result)].Default(result)
65 //   bool   = BoolConst(boolean) | Not(bool) | AllOf(bool...) | AnyOf(bool...)
66 //          | arg == val | arg != val
67 //   arg    = Arg<T>(num) | arg & mask
68 //
69 // The semantics of each function and operator are intended to be
70 // intuitive, but are described in more detail below.
71 //
72 // (Credit to Sean Parent's "Inheritance is the Base Class of Evil"
73 // talk at Going Native 2013 for promoting value semantics via shared
74 // pointers to immutable state.)
75 
76 namespace sandbox {
77 namespace bpf_dsl {
78 
79 // ResultExpr is an opaque reference to an immutable result expression tree.
80 typedef scoped_refptr<const internal::ResultExprImpl> ResultExpr;
81 
82 // BoolExpr is an opaque reference to an immutable boolean expression tree.
83 typedef scoped_refptr<const internal::BoolExprImpl> BoolExpr;
84 
85 // Allow specifies a result that the system call should be allowed to
86 // execute normally.
87 SANDBOX_EXPORT ResultExpr Allow();
88 
89 // Error specifies a result that the system call should fail with
90 // error number |err|.  As a special case, Error(0) will result in the
91 // system call appearing to have succeeded, but without having any
92 // side effects.
93 SANDBOX_EXPORT ResultExpr Error(int err);
94 
95 // Kill specifies a result to kill the process (task) immediately.
96 SANDBOX_EXPORT ResultExpr Kill();
97 
98 // Trace specifies a result to notify a tracing process via the
99 // PTRACE_EVENT_SECCOMP event and allow it to change or skip the system call.
100 // The value of |aux| will be available to the tracer via PTRACE_GETEVENTMSG.
101 SANDBOX_EXPORT ResultExpr Trace(uint16_t aux);
102 
103 // Trap specifies a result that the system call should be handled by
104 // trapping back into userspace and invoking |trap_func|, passing
105 // |aux| as the second parameter.
106 SANDBOX_EXPORT ResultExpr
107     Trap(TrapRegistry::TrapFnc trap_func, const void* aux);
108 
109 // UnsafeTrap is like Trap, except the policy is marked as "unsafe"
110 // and allowed to use SandboxSyscall to invoke any system call.
111 //
112 // NOTE: This feature, by definition, disables all security features of
113 //   the sandbox. It should never be used in production, but it can be
114 //   very useful to diagnose code that is incompatible with the sandbox.
115 //   If even a single system call returns "UnsafeTrap", the security of
116 //   entire sandbox should be considered compromised.
117 SANDBOX_EXPORT ResultExpr
118     UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux);
119 
120 // BoolConst converts a bool value into a BoolExpr.
121 SANDBOX_EXPORT BoolExpr BoolConst(bool value);
122 
123 // Not returns a BoolExpr representing the logical negation of |cond|.
124 SANDBOX_EXPORT BoolExpr Not(const BoolExpr& cond);
125 
126 // AllOf returns a BoolExpr representing the logical conjunction ("and")
127 // of zero or more BoolExprs.
128 SANDBOX_EXPORT BoolExpr AllOf();
129 SANDBOX_EXPORT BoolExpr AllOf(const BoolExpr& lhs, const BoolExpr& rhs);
130 template <typename... Rest>
131 SANDBOX_EXPORT BoolExpr AllOf(const BoolExpr& first, const Rest&... rest);
132 
133 // AnyOf returns a BoolExpr representing the logical disjunction ("or")
134 // of zero or more BoolExprs.
135 SANDBOX_EXPORT BoolExpr AnyOf();
136 SANDBOX_EXPORT BoolExpr AnyOf(const BoolExpr& lhs, const BoolExpr& rhs);
137 template <typename... Rest>
138 SANDBOX_EXPORT BoolExpr AnyOf(const BoolExpr& first, const Rest&... rest);
139 
140 template <typename T>
141 class SANDBOX_EXPORT Arg {
142  public:
143   // Initializes the Arg to represent the |num|th system call
144   // argument (indexed from 0), which is of type |T|.
145   explicit Arg(int num);
146 
Arg(const Arg & arg)147   Arg(const Arg& arg) : num_(arg.num_), mask_(arg.mask_) {}
148 
149   // Returns an Arg representing the current argument, but after
150   // bitwise-and'ing it with |rhs|.
151   friend Arg operator&(const Arg& lhs, uint64_t rhs) {
152     return Arg(lhs.num_, lhs.mask_ & rhs);
153   }
154 
155   // Returns a boolean expression comparing whether the system call argument
156   // (after applying any bitmasks, if appropriate) equals |rhs|.
157   friend BoolExpr operator==(const Arg& lhs, T rhs) { return lhs.EqualTo(rhs); }
158 
159   // Returns a boolean expression comparing whether the system call argument
160   // (after applying any bitmasks, if appropriate) does not equal |rhs|.
161   friend BoolExpr operator!=(const Arg& lhs, T rhs) { return Not(lhs == rhs); }
162 
163  private:
Arg(int num,uint64_t mask)164   Arg(int num, uint64_t mask) : num_(num), mask_(mask) {}
165 
166   BoolExpr EqualTo(T val) const;
167 
168   int num_;
169   uint64_t mask_;
170 
171   DISALLOW_ASSIGN(Arg);
172 };
173 
174 // If begins a conditional result expression predicated on the
175 // specified boolean expression.
176 SANDBOX_EXPORT Elser If(const BoolExpr& cond, const ResultExpr& then_result);
177 
178 class SANDBOX_EXPORT Elser {
179  public:
180   Elser(const Elser& elser);
181   ~Elser();
182 
183   // ElseIf extends the conditional result expression with another
184   // "if then" clause, predicated on the specified boolean expression.
185   Elser ElseIf(const BoolExpr& cond, const ResultExpr& then_result) const;
186 
187   // Else terminates a conditional result expression using |else_result| as
188   // the default fallback result expression.
189   ResultExpr Else(const ResultExpr& else_result) const;
190 
191  private:
192   typedef std::pair<BoolExpr, ResultExpr> Clause;
193 
194   explicit Elser(cons::List<Clause> clause_list);
195 
196   cons::List<Clause> clause_list_;
197 
198   friend Elser If(const BoolExpr&, const ResultExpr&);
199   template <typename T>
200   friend Caser<T> Switch(const Arg<T>&);
201   DISALLOW_ASSIGN(Elser);
202 };
203 
204 // Switch begins a switch expression dispatched according to the
205 // specified argument value.
206 template <typename T>
207 SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg);
208 
209 template <typename T>
210 class SANDBOX_EXPORT Caser {
211  public:
Caser(const Caser<T> & caser)212   Caser(const Caser<T>& caser) : arg_(caser.arg_), elser_(caser.elser_) {}
~Caser()213   ~Caser() {}
214 
215   // Case adds a single-value "case" clause to the switch.
216   Caser<T> Case(T value, const ResultExpr& result) const;
217 
218   // Cases adds a multiple-value "case" clause to the switch.
219   // See also the SANDBOX_BPF_DSL_CASES macro below for a more idiomatic way
220   // of using this function.
221   template <typename... Values>
222   Caser<T> CasesImpl(const ResultExpr& result, const Values&... values) const;
223 
224   // Terminate the switch with a "default" clause.
225   ResultExpr Default(const ResultExpr& result) const;
226 
227  private:
Caser(const Arg<T> & arg,Elser elser)228   Caser(const Arg<T>& arg, Elser elser) : arg_(arg), elser_(elser) {}
229 
230   Arg<T> arg_;
231   Elser elser_;
232 
233   template <typename U>
234   friend Caser<U> Switch(const Arg<U>&);
235   DISALLOW_ASSIGN(Caser);
236 };
237 
238 // Recommended usage is to put
239 //    #define CASES SANDBOX_BPF_DSL_CASES
240 // near the top of the .cc file (e.g., nearby any "using" statements), then
241 // use like:
242 //    Switch(arg).CASES((3, 5, 7), result)...;
243 #define SANDBOX_BPF_DSL_CASES(values, result) \
244   CasesImpl(result, SANDBOX_BPF_DSL_CASES_HELPER values)
245 
246 // Helper macro to strip parentheses.
247 #define SANDBOX_BPF_DSL_CASES_HELPER(...) __VA_ARGS__
248 
249 // =====================================================================
250 // Official API ends here.
251 // =====================================================================
252 
253 namespace internal {
254 
255 // Make argument-dependent lookup work.  This is necessary because although
256 // BoolExpr is defined in bpf_dsl, since it's merely a typedef for
257 // scoped_refptr<const internal::BoolExplImpl>, argument-dependent lookup only
258 // searches the "internal" nested namespace.
259 using bpf_dsl::Not;
260 using bpf_dsl::AllOf;
261 using bpf_dsl::AnyOf;
262 
263 // Returns a boolean expression that represents whether system call
264 // argument |num| of size |size| is equal to |val|, when masked
265 // according to |mask|.  Users should use the Arg template class below
266 // instead of using this API directly.
267 SANDBOX_EXPORT BoolExpr
268     ArgEq(int num, size_t size, uint64_t mask, uint64_t val);
269 
270 // Returns the default mask for a system call argument of the specified size.
271 SANDBOX_EXPORT uint64_t DefaultMask(size_t size);
272 
273 }  // namespace internal
274 
275 template <typename T>
Arg(int num)276 Arg<T>::Arg(int num)
277     : num_(num), mask_(internal::DefaultMask(sizeof(T))) {
278 }
279 
280 // Definition requires ArgEq to have been declared.  Moved out-of-line
281 // to minimize how much internal clutter users have to ignore while
282 // reading the header documentation.
283 //
284 // Additionally, we use this helper member function to avoid linker errors
285 // caused by defining operator== out-of-line.  For a more detailed explanation,
286 // see http://www.parashift.com/c++-faq-lite/template-friends.html.
287 template <typename T>
EqualTo(T val)288 BoolExpr Arg<T>::EqualTo(T val) const {
289   if (sizeof(T) == 4) {
290     // Prevent sign-extension of negative int32_t values.
291     return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint32_t>(val));
292   }
293   return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint64_t>(val));
294 }
295 
296 template <typename T>
Switch(const Arg<T> & arg)297 SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg) {
298   return Caser<T>(arg, Elser(nullptr));
299 }
300 
301 template <typename T>
Case(T value,const ResultExpr & result)302 Caser<T> Caser<T>::Case(T value, const ResultExpr& result) const {
303   return SANDBOX_BPF_DSL_CASES((value), result);
304 }
305 
306 template <typename T>
307 template <typename... Values>
CasesImpl(const ResultExpr & result,const Values &...values)308 Caser<T> Caser<T>::CasesImpl(const ResultExpr& result,
309                              const Values&... values) const {
310   // Theoretically we could evaluate arg_ just once and emit a more efficient
311   // dispatch table, but for now we simply translate into an equivalent
312   // If/ElseIf/Else chain.
313 
314   return Caser<T>(arg_, elser_.ElseIf(AnyOf((arg_ == values)...), result));
315 }
316 
317 template <typename T>
Default(const ResultExpr & result)318 ResultExpr Caser<T>::Default(const ResultExpr& result) const {
319   return elser_.Else(result);
320 }
321 
322 template <typename... Rest>
AllOf(const BoolExpr & first,const Rest &...rest)323 BoolExpr AllOf(const BoolExpr& first, const Rest&... rest) {
324   return AllOf(first, AllOf(rest...));
325 }
326 
327 template <typename... Rest>
AnyOf(const BoolExpr & first,const Rest &...rest)328 BoolExpr AnyOf(const BoolExpr& first, const Rest&... rest) {
329   return AnyOf(first, AnyOf(rest...));
330 }
331 
332 }  // namespace bpf_dsl
333 }  // namespace sandbox
334 
335 #endif  // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_
336