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
20namespace sandbox {
21class ErrorCode;
22class 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
78namespace sandbox {
79namespace bpf_dsl {
80
81// Forward declarations of classes; see below for proper documentation.
82class Elser;
83template <typename T>
84class Caser;
85namespace internal {
86class ResultExprImpl;
87class BoolExprImpl;
88}
89
90// ResultExpr is an opaque reference to an immutable result expression tree.
91typedef scoped_refptr<const internal::ResultExprImpl> ResultExpr;
92
93// BoolExpr is an opaque reference to an immutable boolean expression tree.
94typedef scoped_refptr<const internal::BoolExprImpl> BoolExpr;
95
96// Helper class to make writing policies easier.
97class 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.
125SANDBOX_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.
131SANDBOX_EXPORT ResultExpr Error(int err);
132
133// Kill specifies a result to kill the program and print an error message.
134SANDBOX_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.
139SANDBOX_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.
144SANDBOX_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.
154SANDBOX_EXPORT ResultExpr UnsafeTrap(Trap::TrapFnc trap_func, const void* aux);
155
156// BoolConst converts a bool value into a BoolExpr.
157SANDBOX_EXPORT BoolExpr BoolConst(bool value);
158
159// Various ways to combine boolean expressions into more complex expressions.
160// They follow standard boolean algebra laws.
161SANDBOX_EXPORT BoolExpr operator!(const BoolExpr& cond);
162SANDBOX_EXPORT BoolExpr operator&&(const BoolExpr& lhs, const BoolExpr& rhs);
163SANDBOX_EXPORT BoolExpr operator||(const BoolExpr& lhs, const BoolExpr& rhs);
164
165template <typename T>
166class 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.
201SANDBOX_EXPORT Elser If(const BoolExpr& cond, const ResultExpr& then_result);
202
203class 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.
231template <typename T>
232SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg);
233
234template <typename T>
235class 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.
287namespace 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.
293using bpf_dsl::operator!;
294using bpf_dsl::operator||;
295using 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.
301SANDBOX_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.
305SANDBOX_EXPORT uint64_t DefaultMask(size_t size);
306
307// Internal interface implemented by BoolExpr implementations.
308class 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.
324class 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
339template <typename T>
340Arg<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.
351template <typename T>
352BoolExpr Arg<T>::EqualTo(T val) const {
353  return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint64_t>(val));
354}
355
356template <typename T>
357SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg) {
358  return Caser<T>(arg, Elser(Cons<Elser::Clause>::List()));
359}
360
361template <typename T>
362Caser<T> Caser<T>::Case(T value, ResultExpr result) const {
363  return SANDBOX_BPF_DSL_CASES((value), result);
364}
365
366template <typename T>
367Caser<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
381template <typename T>
382ResultExpr 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