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