Commit 279b9aad authored by Matthew Dempsky's avatar Matthew Dempsky

bpf_dsl: support Switch/Case expressions

This adds support for expressions like

    Switch(arg)
      .Case(1, result1)
      .CASES((2, 3), result2)
      .Default(result3)

Currently these expressions are compiled simply as short-hand for

    If(arg == 1, result1)
      .ElseIf(arg == 2 || arg == 3, result2)
      .Else(result3)

but in the future we should be able to optimize it better.

The CASES macro ugliness is unfortunately necessary until we're
allowed to use C++11.  Later we'll be able to change it to
real C++ code like "Cases({2, 3}, result2)" (which clang-format
will then format a bit more nicely too).

BUG=408845
R=jln@chromium.org, jorgelo@chromium.org

Review URL: https://codereview.chromium.org/438683004

Cr-Commit-Position: refs/heads/master@{#293599}
parent 9d972ceb
......@@ -90,6 +90,24 @@ class IfThenResultExprImpl : public internal::ResultExprImpl {
DISALLOW_COPY_AND_ASSIGN(IfThenResultExprImpl);
};
class ConstBoolExprImpl : public internal::BoolExprImpl {
public:
ConstBoolExprImpl(bool value) : value_(value) {}
virtual ErrorCode Compile(SandboxBPF* sb,
ErrorCode true_ec,
ErrorCode false_ec) const OVERRIDE {
return value_ ? true_ec : false_ec;
}
private:
virtual ~ConstBoolExprImpl() {}
bool value_;
DISALLOW_COPY_AND_ASSIGN(ConstBoolExprImpl);
};
class PrimitiveBoolExprImpl : public internal::BoolExprImpl {
public:
PrimitiveBoolExprImpl(int argno,
......@@ -214,6 +232,10 @@ ResultExpr Trap(Trap::TrapFnc trap_func, void* aux) {
return ResultExpr(new const TrapResultExprImpl(trap_func, aux));
}
BoolExpr BoolConst(bool value) {
return BoolExpr(new const ConstBoolExprImpl(value));
}
BoolExpr operator!(const BoolExpr& cond) {
return BoolExpr(new const NegateBoolExprImpl(cond));
}
......
......@@ -8,6 +8,7 @@
#include <stdint.h>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
......@@ -59,10 +60,12 @@ class SandboxBPF;
//
// More generally, the DSL currently supports the following grammar:
//
// result = Allow() | Error(errno) | Trap(trap_func, arg)
// result = Allow() | Error(errno) | Trap(trap_func, aux)
// | If(bool, result)[.ElseIf(bool, result)].Else(result)
// bool = arg == val | (arg & mask) == val
// | !bool | bool && bool | bool || bool
// | Switch(arg)[.Case(val, result)].Default(result)
// bool = BoolConst(boolean) | !bool | bool && bool | bool || bool
// | 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.
......@@ -76,6 +79,8 @@ 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;
......@@ -159,6 +164,9 @@ class SANDBOX_EXPORT Arg {
DISALLOW_ASSIGN(Arg);
};
// Convert 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);
......@@ -190,9 +198,62 @@ class SANDBOX_EXPORT Elser {
Cons<Clause>::List 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.
// =====================================================================
......@@ -261,6 +322,36 @@ 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(Cons<Elser::Clause>::List()));
}
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
......
......@@ -9,6 +9,7 @@
#include <sys/socket.h>
#include <sys/utsname.h>
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "build/build_config.h"
#include "sandbox/linux/seccomp-bpf/bpf_tests.h"
......@@ -16,6 +17,8 @@
#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
#include "sandbox/linux/seccomp-bpf/syscall.h"
#define CASES SANDBOX_BPF_DSL_CASES
// Helper macro to assert that invoking system call |sys| directly via
// Syscall::Call with arguments |...| returns |res|.
// Errors can be asserted by specifying a value like "-EINVAL".
......@@ -36,6 +39,10 @@ class Stubs {
return Syscall::Call(__NR_setpgid, pid, pgid);
}
static int fcntl(int fd, int cmd, unsigned long arg = 0) {
return Syscall::Call(__NR_fcntl, fd, cmd, arg);
}
static int uname(struct utsname* buf) {
return Syscall::Call(__NR_uname, buf);
}
......@@ -275,6 +282,42 @@ BPF_TEST_C(BPFDSL, ElseIfTest, ElseIfPolicy) {
ASSERT_SYSCALL_RESULT(-EACCES, setuid, 0x0222);
}
class SwitchPolicy : public SandboxBPFDSLPolicy {
public:
SwitchPolicy() {}
virtual ~SwitchPolicy() {}
virtual ResultExpr EvaluateSyscall(int sysno) const OVERRIDE {
if (sysno == __NR_fcntl) {
const Arg<int> cmd(1);
const Arg<unsigned long> long_arg(2);
return Switch(cmd)
.CASES((F_GETFL, F_GETFD), Error(ENOENT))
.Case(F_SETFD, If(long_arg == O_CLOEXEC, Allow()).Else(Error(EINVAL)))
.Case(F_SETFL, Error(EPERM))
.Default(Error(EACCES));
}
return Allow();
}
private:
DISALLOW_COPY_AND_ASSIGN(SwitchPolicy);
};
BPF_TEST_C(BPFDSL, SwitchTest, SwitchPolicy) {
base::ScopedFD sock_fd(socket(AF_UNIX, SOCK_STREAM, 0));
BPF_ASSERT(sock_fd.is_valid());
ASSERT_SYSCALL_RESULT(-ENOENT, fcntl, sock_fd.get(), F_GETFD);
ASSERT_SYSCALL_RESULT(-ENOENT, fcntl, sock_fd.get(), F_GETFL);
ASSERT_SYSCALL_RESULT(0, fcntl, sock_fd.get(), F_SETFD, O_CLOEXEC);
ASSERT_SYSCALL_RESULT(-EINVAL, fcntl, sock_fd.get(), F_SETFD, 0);
ASSERT_SYSCALL_RESULT(-EPERM, fcntl, sock_fd.get(), F_SETFL, O_RDONLY);
ASSERT_SYSCALL_RESULT(-EACCES, fcntl, sock_fd.get(), F_DUPFD, 0);
}
} // namespace
} // namespace bpf_dsl
} // namespace sandbox
......@@ -79,6 +79,8 @@ inline bool IsArchitectureMips() {
} // namespace.
#define CASES SANDBOX_BPF_DSL_CASES
using sandbox::bpf_dsl::Allow;
using sandbox::bpf_dsl::Arg;
using sandbox::bpf_dsl::BoolExpr;
......@@ -123,15 +125,16 @@ ResultExpr RestrictPrctl() {
// Will need to add seccomp compositing in the future. PR_SET_PTRACER is
// used by breakpad but not needed anymore.
const Arg<int> option(0);
return If(option == PR_GET_NAME || option == PR_SET_NAME ||
option == PR_GET_DUMPABLE || option == PR_SET_DUMPABLE,
Allow()).Else(CrashSIGSYSPrctl());
return Switch(option)
.CASES((PR_GET_NAME, PR_SET_NAME, PR_GET_DUMPABLE, PR_SET_DUMPABLE),
Allow())
.Default(CrashSIGSYSPrctl());
}
ResultExpr RestrictIoctl() {
const Arg<int> request(1);
return If(request == TCGETS || request == FIONREAD, Allow())
.Else(CrashSIGSYSIoctl());
return Switch(request).CASES((TCGETS, FIONREAD), Allow()).Default(
CrashSIGSYSIoctl());
}
ResultExpr RestrictMmapFlags() {
......@@ -172,11 +175,19 @@ ResultExpr RestrictFcntlCommands() {
unsigned long denied_mask = ~(O_ACCMODE | O_APPEND | O_NONBLOCK | O_SYNC |
kOLargeFileFlag | O_CLOEXEC | O_NOATIME);
return If(cmd == F_GETFL || cmd == F_GETFD || cmd == F_SETFD ||
cmd == F_SETLK || cmd == F_SETLKW || cmd == F_GETLK ||
cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC ||
(cmd == F_SETFL && (long_arg & denied_mask) == 0),
Allow()).Else(CrashSIGSYS());
return Switch(cmd)
.CASES((F_GETFL,
F_GETFD,
F_SETFD,
F_SETLK,
F_SETLKW,
F_GETLK,
F_DUPFD,
F_DUPFD_CLOEXEC),
Allow())
.Case(F_SETFL,
If((long_arg & denied_mask) == 0, Allow()).Else(CrashSIGSYS()))
.Default(CrashSIGSYS());
}
#if defined(__i386__) || defined(__mips__)
......@@ -186,11 +197,17 @@ ResultExpr RestrictSocketcallCommand() {
// few protocols actually support socketpair(2). The scary call that we're
// worried about, socket(2), remains blocked.
const Arg<int> call(0);
return If(call == SYS_SOCKETPAIR || call == SYS_SHUTDOWN ||
call == SYS_RECV || call == SYS_SEND ||
call == SYS_RECVFROM || call == SYS_SENDTO ||
call == SYS_RECVMSG || call == SYS_SENDMSG,
Allow()).Else(Error(EPERM));
return Switch(call)
.CASES((SYS_SOCKETPAIR,
SYS_SHUTDOWN,
SYS_RECV,
SYS_SEND,
SYS_RECVFROM,
SYS_SENDTO,
SYS_RECVMSG,
SYS_SENDMSG),
Allow())
.Default(Error(EPERM));
}
#endif
......@@ -212,18 +229,19 @@ ResultExpr RestrictKillTarget(pid_t target_pid, int sysno) {
ResultExpr RestrictFutex() {
const int kAllowedFutexFlags = FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME;
const int kOperationMask = ~kAllowedFutexFlags;
const int kAllowedFutexOperations[] = {
FUTEX_WAIT, FUTEX_WAKE, FUTEX_FD, FUTEX_REQUEUE,
FUTEX_CMP_REQUEUE, FUTEX_WAKE_OP, FUTEX_WAIT_BITSET, FUTEX_WAKE_BITSET};
const Arg<int> op(1);
BoolExpr IsAllowedOp = (op & kOperationMask) == kAllowedFutexOperations[0];
for (size_t i = 1; i < arraysize(kAllowedFutexOperations); ++i) {
IsAllowedOp =
IsAllowedOp || ((op & kOperationMask) == kAllowedFutexOperations[i]);
}
return If(IsAllowedOp, Allow()).Else(CrashSIGSYSFutex());
return Switch(op & kOperationMask)
.CASES((FUTEX_WAIT,
FUTEX_WAKE,
FUTEX_FD,
FUTEX_REQUEUE,
FUTEX_CMP_REQUEUE,
FUTEX_WAKE_OP,
FUTEX_WAIT_BITSET,
FUTEX_WAKE_BITSET),
Allow())
.Default(CrashSIGSYSFutex());
}
} // namespace sandbox.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment