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 <memory>
12 #include <utility>
13 #include <vector>
14
15 #include "base/macros.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 using ResultExpr = std::shared_ptr<const internal::ResultExprImpl>;
81
82 // BoolExpr is an opaque reference to an immutable boolean expression tree.
83 using BoolExpr = std::shared_ptr<const internal::BoolExprImpl>;
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(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(BoolExpr lhs, BoolExpr rhs);
130 template <typename... Rest>
131 SANDBOX_EXPORT BoolExpr AllOf(BoolExpr first, 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(BoolExpr lhs, BoolExpr rhs);
137 template <typename... Rest>
138 SANDBOX_EXPORT BoolExpr AnyOf(BoolExpr first, 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(BoolExpr cond, 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(BoolExpr cond, 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(ResultExpr else_result) const;
190
191 private:
192 using Clause = std::pair<BoolExpr, ResultExpr>;
193
194 explicit Elser(cons::List<Clause> clause_list);
195
196 cons::List<Clause> clause_list_;
197
198 friend Elser If(BoolExpr, 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, 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(ResultExpr result, const Values&... values) const;
223
224 // Terminate the switch with a "default" clause.
225 ResultExpr Default(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,ResultExpr result)302 Caser<T> Caser<T>::Case(T value, ResultExpr result) const {
303 return SANDBOX_BPF_DSL_CASES((value), std::move(result));
304 }
305
306 template <typename T>
307 template <typename... Values>
CasesImpl(ResultExpr result,const Values &...values)308 Caser<T> Caser<T>::CasesImpl(ResultExpr result, const Values&... values) const {
309 // Theoretically we could evaluate arg_ just once and emit a more efficient
310 // dispatch table, but for now we simply translate into an equivalent
311 // If/ElseIf/Else chain.
312
313 return Caser<T>(arg_,
314 elser_.ElseIf(AnyOf((arg_ == values)...), std::move(result)));
315 }
316
317 template <typename T>
Default(ResultExpr result)318 ResultExpr Caser<T>::Default(ResultExpr result) const {
319 return elser_.Else(std::move(result));
320 }
321
322 template <typename... Rest>
AllOf(BoolExpr first,Rest &&...rest)323 BoolExpr AllOf(BoolExpr first, Rest&&... rest) {
324 return AllOf(std::move(first), AllOf(std::forward<Rest>(rest)...));
325 }
326
327 template <typename... Rest>
AnyOf(BoolExpr first,Rest &&...rest)328 BoolExpr AnyOf(BoolExpr first, Rest&&... rest) {
329 return AnyOf(std::move(first), AnyOf(std::forward<Rest>(rest)...));
330 }
331
332 } // namespace bpf_dsl
333 } // namespace sandbox
334
335 #endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_
336