Index: sandbox/linux/bpf_dsl/bpf_dsl.h |
diff --git a/sandbox/linux/bpf_dsl/bpf_dsl.h b/sandbox/linux/bpf_dsl/bpf_dsl.h |
new file mode 100644 |
index 0000000000000000000000000000000000000000..25892b7eff51f82583c890c0bcac06970d772917 |
--- /dev/null |
+++ b/sandbox/linux/bpf_dsl/bpf_dsl.h |
@@ -0,0 +1,360 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#ifndef SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ |
+#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ |
+ |
+#include <stdint.h> |
+ |
+#include <utility> |
+#include <vector> |
+ |
+#include "base/macros.h" |
+#include "base/memory/ref_counted.h" |
+#include "sandbox/linux/bpf_dsl/cons.h" |
+#include "sandbox/linux/bpf_dsl/trap_registry.h" |
+#include "sandbox/sandbox_export.h" |
+ |
+// The sandbox::bpf_dsl namespace provides a domain-specific language |
+// to make writing BPF policies more expressive. In general, the |
+// object types all have value semantics (i.e., they can be copied |
+// around, returned from or passed to function calls, etc. without any |
+// surprising side effects), though not all support assignment. |
+// |
+// An idiomatic and demonstrative (albeit silly) example of this API |
+// would be: |
+// |
+// #include "sandbox/linux/bpf_dsl/bpf_dsl.h" |
+// |
+// using namespace sandbox::bpf_dsl; |
+// |
+// class SillyPolicy : public SandboxBPFDSLPolicy { |
+// public: |
+// SillyPolicy() {} |
+// virtual ~SillyPolicy() {} |
+// virtual ResultExpr EvaluateSyscall(int sysno) const override { |
+// if (sysno == __NR_fcntl) { |
+// Arg<int> fd(0), cmd(1); |
+// Arg<unsigned long> flags(2); |
+// const uint64_t kGoodFlags = O_ACCMODE | O_NONBLOCK; |
+// return If(fd == 0 && cmd == F_SETFL && (flags & ~kGoodFlags) == 0, |
+// Allow()) |
+// .ElseIf(cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC, |
+// Error(EMFILE)) |
+// .Else(Trap(SetFlagHandler, NULL)); |
+// } else { |
+// return Allow(); |
+// } |
+// } |
+// |
+// private: |
+// DISALLOW_COPY_AND_ASSIGN(SillyPolicy); |
+// }; |
+// |
+// More generally, the DSL currently supports the following grammar: |
+// |
+// result = Allow() | Error(errno) | Kill(msg) | Trace(aux) |
+// | Trap(trap_func, aux) | UnsafeTrap(trap_func, aux) |
+// | If(bool, result)[.ElseIf(bool, result)].Else(result) |
+// | Switch(arg)[.Case(val, result)].Default(result) |
+// bool = BoolConst(boolean) | !bool | bool && bool | bool || bool |
+// | arg == val | arg != val |
+// arg = Arg<T>(num) | arg & mask |
+// |
+// The semantics of each function and operator are intended to be |
+// intuitive, but are described in more detail below. |
+// |
+// (Credit to Sean Parent's "Inheritance is the Base Class of Evil" |
+// talk at Going Native 2013 for promoting value semantics via shared |
+// pointers to immutable state.) |
+ |
+namespace sandbox { |
+namespace bpf_dsl { |
+ |
+// Forward declarations of classes; see below for proper documentation. |
+class Elser; |
+template <typename T> |
+class Caser; |
+namespace internal { |
+class ResultExprImpl; |
+class BoolExprImpl; |
+} |
+ |
+} // namespace bpf_dsl |
+} // namespace sandbox |
+ |
+extern template class SANDBOX_EXPORT |
+ scoped_refptr<const sandbox::bpf_dsl::internal::BoolExprImpl>; |
+extern template class SANDBOX_EXPORT |
+ scoped_refptr<const sandbox::bpf_dsl::internal::ResultExprImpl>; |
+ |
+namespace sandbox { |
+namespace bpf_dsl { |
+ |
+// ResultExpr is an opaque reference to an immutable result expression tree. |
+typedef scoped_refptr<const internal::ResultExprImpl> ResultExpr; |
+ |
+// BoolExpr is an opaque reference to an immutable boolean expression tree. |
+typedef scoped_refptr<const internal::BoolExprImpl> BoolExpr; |
+ |
+// Interface to implement to define a BPF sandbox policy. |
+// TODO(mdempsky): "sandbox::bpf_dsl::SandboxBPFDSLPolicy" is |
+// tediously repetitive; rename to just "Policy". |
+class SANDBOX_EXPORT SandboxBPFDSLPolicy { |
+ public: |
+ SandboxBPFDSLPolicy() {} |
+ virtual ~SandboxBPFDSLPolicy() {} |
+ |
+ // User extension point for writing custom sandbox policies. |
+ // The returned ResultExpr will control how the kernel responds to the |
+ // specified system call number. |
+ virtual ResultExpr EvaluateSyscall(int sysno) const = 0; |
+ |
+ // Optional overload for specifying alternate behavior for invalid |
+ // system calls. The default is to return ENOSYS. |
+ virtual ResultExpr InvalidSyscall() const; |
+ |
+ // Helper method so policies can just write Trap(func, aux). |
+ static ResultExpr Trap(TrapRegistry::TrapFnc trap_func, const void* aux); |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(SandboxBPFDSLPolicy); |
+}; |
+ |
+// Allow specifies a result that the system call should be allowed to |
+// execute normally. |
+SANDBOX_EXPORT ResultExpr Allow(); |
+ |
+// Error specifies a result that the system call should fail with |
+// error number |err|. As a special case, Error(0) will result in the |
+// system call appearing to have succeeded, but without having any |
+// side effects. |
+SANDBOX_EXPORT ResultExpr Error(int err); |
+ |
+// Kill specifies a result to kill the program and print an error message. |
+SANDBOX_EXPORT ResultExpr Kill(const char* msg); |
+ |
+// Trace specifies a result to notify a tracing process via the |
+// PTRACE_EVENT_SECCOMP event and allow it to change or skip the system call. |
+// The value of |aux| will be available to the tracer via PTRACE_GETEVENTMSG. |
+SANDBOX_EXPORT ResultExpr Trace(uint16_t aux); |
+ |
+// Trap specifies a result that the system call should be handled by |
+// trapping back into userspace and invoking |trap_func|, passing |
+// |aux| as the second parameter. |
+SANDBOX_EXPORT ResultExpr |
+ Trap(TrapRegistry::TrapFnc trap_func, const void* aux); |
+ |
+// UnsafeTrap is like Trap, except the policy is marked as "unsafe" |
+// and allowed to use SandboxSyscall to invoke any system call. |
+// |
+// NOTE: This feature, by definition, disables all security features of |
+// the sandbox. It should never be used in production, but it can be |
+// very useful to diagnose code that is incompatible with the sandbox. |
+// If even a single system call returns "UnsafeTrap", the security of |
+// entire sandbox should be considered compromised. |
+SANDBOX_EXPORT ResultExpr |
+ UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux); |
+ |
+// BoolConst converts a bool value into a BoolExpr. |
+SANDBOX_EXPORT BoolExpr BoolConst(bool value); |
+ |
+// Various ways to combine boolean expressions into more complex expressions. |
+// They follow standard boolean algebra laws. |
+SANDBOX_EXPORT BoolExpr operator!(const BoolExpr& cond); |
+SANDBOX_EXPORT BoolExpr operator&&(const BoolExpr& lhs, const BoolExpr& rhs); |
+SANDBOX_EXPORT BoolExpr operator||(const BoolExpr& lhs, const BoolExpr& rhs); |
+ |
+template <typename T> |
+class SANDBOX_EXPORT Arg { |
+ public: |
+ // Initializes the Arg to represent the |num|th system call |
+ // argument (indexed from 0), which is of type |T|. |
+ explicit Arg(int num); |
+ |
+ Arg(const Arg& arg) : num_(arg.num_), mask_(arg.mask_) {} |
+ |
+ // Returns an Arg representing the current argument, but after |
+ // bitwise-and'ing it with |rhs|. |
+ friend Arg operator&(const Arg& lhs, uint64_t rhs) { |
+ return Arg(lhs.num_, lhs.mask_ & rhs); |
+ } |
+ |
+ // Returns a boolean expression comparing whether the system call argument |
+ // (after applying any bitmasks, if appropriate) equals |rhs|. |
+ friend BoolExpr operator==(const Arg& lhs, T rhs) { return lhs.EqualTo(rhs); } |
+ |
+ // Returns a boolean expression comparing whether the system call argument |
+ // (after applying any bitmasks, if appropriate) does not equal |rhs|. |
+ friend BoolExpr operator!=(const Arg& lhs, T rhs) { return !(lhs == rhs); } |
+ |
+ private: |
+ Arg(int num, uint64_t mask) : num_(num), mask_(mask) {} |
+ |
+ BoolExpr EqualTo(T val) const; |
+ |
+ int num_; |
+ uint64_t mask_; |
+ |
+ DISALLOW_ASSIGN(Arg); |
+}; |
+ |
+// If begins a conditional result expression predicated on the |
+// specified boolean expression. |
+SANDBOX_EXPORT Elser If(const BoolExpr& cond, const ResultExpr& then_result); |
+ |
+class SANDBOX_EXPORT Elser { |
+ public: |
+ Elser(const Elser& elser); |
+ ~Elser(); |
+ |
+ // ElseIf extends the conditional result expression with another |
+ // "if then" clause, predicated on the specified boolean expression. |
+ Elser ElseIf(const BoolExpr& cond, const ResultExpr& then_result) const; |
+ |
+ // Else terminates a conditional result expression using |else_result| as |
+ // the default fallback result expression. |
+ ResultExpr Else(const ResultExpr& else_result) const; |
+ |
+ private: |
+ typedef std::pair<BoolExpr, ResultExpr> Clause; |
+ |
+ explicit Elser(cons::List<Clause> clause_list); |
+ |
+ cons::List<Clause> clause_list_; |
+ |
+ friend Elser If(const BoolExpr&, const ResultExpr&); |
+ template <typename T> |
+ friend Caser<T> Switch(const Arg<T>&); |
+ DISALLOW_ASSIGN(Elser); |
+}; |
+ |
+// Switch begins a switch expression dispatched according to the |
+// specified argument value. |
+template <typename T> |
+SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg); |
+ |
+template <typename T> |
+class SANDBOX_EXPORT Caser { |
+ public: |
+ Caser(const Caser<T>& caser) : arg_(caser.arg_), elser_(caser.elser_) {} |
+ ~Caser() {} |
+ |
+ // Case adds a single-value "case" clause to the switch. |
+ Caser<T> Case(T value, ResultExpr result) const; |
+ |
+ // Cases adds a multiple-value "case" clause to the switch. |
+ // See also the SANDBOX_BPF_DSL_CASES macro below for a more idiomatic way |
+ // of using this function. |
+ Caser<T> Cases(const std::vector<T>& values, ResultExpr result) const; |
+ |
+ // Terminate the switch with a "default" clause. |
+ ResultExpr Default(ResultExpr result) const; |
+ |
+ private: |
+ Caser(const Arg<T>& arg, Elser elser) : arg_(arg), elser_(elser) {} |
+ |
+ Arg<T> arg_; |
+ Elser elser_; |
+ |
+ template <typename U> |
+ friend Caser<U> Switch(const Arg<U>&); |
+ DISALLOW_ASSIGN(Caser); |
+}; |
+ |
+// Recommended usage is to put |
+// #define CASES SANDBOX_BPF_DSL_CASES |
+// near the top of the .cc file (e.g., nearby any "using" statements), then |
+// use like: |
+// Switch(arg).CASES((3, 5, 7), result)...; |
+#define SANDBOX_BPF_DSL_CASES(values, result) \ |
+ Cases(SANDBOX_BPF_DSL_CASES_HELPER values, result) |
+ |
+// Helper macro to construct a std::vector from an initializer list. |
+// TODO(mdempsky): Convert to use C++11 initializer lists instead. |
+#define SANDBOX_BPF_DSL_CASES_HELPER(value, ...) \ |
+ ({ \ |
+ const __typeof__(value) bpf_dsl_cases_values[] = {value, __VA_ARGS__}; \ |
+ std::vector<__typeof__(value)>( \ |
+ bpf_dsl_cases_values, \ |
+ bpf_dsl_cases_values + arraysize(bpf_dsl_cases_values)); \ |
+ }) |
+ |
+// ===================================================================== |
+// Official API ends here. |
+// ===================================================================== |
+ |
+namespace internal { |
+ |
+// Make argument-dependent lookup work. This is necessary because although |
+// BoolExpr is defined in bpf_dsl, since it's merely a typedef for |
+// scoped_refptr<const internal::BoolExplImpl>, argument-dependent lookup only |
+// searches the "internal" nested namespace. |
+using bpf_dsl::operator!; |
+using bpf_dsl::operator||; |
+using bpf_dsl::operator&&; |
+ |
+// Returns a boolean expression that represents whether system call |
+// argument |num| of size |size| is equal to |val|, when masked |
+// according to |mask|. Users should use the Arg template class below |
+// instead of using this API directly. |
+SANDBOX_EXPORT BoolExpr |
+ ArgEq(int num, size_t size, uint64_t mask, uint64_t val); |
+ |
+// Returns the default mask for a system call argument of the specified size. |
+SANDBOX_EXPORT uint64_t DefaultMask(size_t size); |
+ |
+} // namespace internal |
+ |
+template <typename T> |
+Arg<T>::Arg(int num) |
+ : num_(num), mask_(internal::DefaultMask(sizeof(T))) { |
+} |
+ |
+// Definition requires ArgEq to have been declared. Moved out-of-line |
+// to minimize how much internal clutter users have to ignore while |
+// reading the header documentation. |
+// |
+// Additionally, we use this helper member function to avoid linker errors |
+// caused by defining operator== out-of-line. For a more detailed explanation, |
+// see http://www.parashift.com/c++-faq-lite/template-friends.html. |
+template <typename T> |
+BoolExpr Arg<T>::EqualTo(T val) const { |
+ return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint64_t>(val)); |
+} |
+ |
+template <typename T> |
+SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg) { |
+ return Caser<T>(arg, Elser(nullptr)); |
+} |
+ |
+template <typename T> |
+Caser<T> Caser<T>::Case(T value, ResultExpr result) const { |
+ return SANDBOX_BPF_DSL_CASES((value), result); |
+} |
+ |
+template <typename T> |
+Caser<T> Caser<T>::Cases(const std::vector<T>& values, |
+ ResultExpr result) const { |
+ // Theoretically we could evaluate arg_ just once and emit a more efficient |
+ // dispatch table, but for now we simply translate into an equivalent |
+ // If/ElseIf/Else chain. |
+ |
+ typedef typename std::vector<T>::const_iterator Iter; |
+ BoolExpr test = BoolConst(false); |
+ for (Iter i = values.begin(), end = values.end(); i != end; ++i) { |
+ test = test || (arg_ == *i); |
+ } |
+ return Caser<T>(arg_, elser_.ElseIf(test, result)); |
+} |
+ |
+template <typename T> |
+ResultExpr Caser<T>::Default(ResultExpr result) const { |
+ return elser_.Else(result); |
+} |
+ |
+} // namespace bpf_dsl |
+} // namespace sandbox |
+ |
+#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ |