diff options
Diffstat (limited to 'sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc')
-rw-r--r-- | sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc b/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc new file mode 100644 index 0000000000..398ec59ef1 --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc @@ -0,0 +1,486 @@ +// 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. + +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" + +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include <map> +#include <utility> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/bpf_dsl/verifier.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define CASES SANDBOX_BPF_DSL_CASES + +namespace sandbox { +namespace bpf_dsl { +namespace { + +// Helper function to construct fake arch_seccomp_data objects. +struct arch_seccomp_data FakeSyscall(int nr, + uint64_t p0 = 0, + uint64_t p1 = 0, + uint64_t p2 = 0, + uint64_t p3 = 0, + uint64_t p4 = 0, + uint64_t p5 = 0) { + // Made up program counter for syscall address. + const uint64_t kFakePC = 0x543210; + + struct arch_seccomp_data data = { + nr, + SECCOMP_ARCH, + kFakePC, + { + p0, p1, p2, p3, p4, p5, + }, + }; + + return data; +} + +class FakeTrapRegistry : public TrapRegistry { + public: + FakeTrapRegistry() : map_() {} + virtual ~FakeTrapRegistry() {} + + uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override { + EXPECT_TRUE(safe); + + const uint16_t next_id = map_.size() + 1; + return map_.insert(std::make_pair(Key(fnc, aux), next_id)).first->second; + } + + bool EnableUnsafeTraps() override { + ADD_FAILURE() << "Unimplemented"; + return false; + } + + private: + using Key = std::pair<TrapFnc, const void*>; + + std::map<Key, uint16_t> map_; + + DISALLOW_COPY_AND_ASSIGN(FakeTrapRegistry); +}; + +intptr_t FakeTrapFuncOne(const arch_seccomp_data& data, void* aux) { return 1; } +intptr_t FakeTrapFuncTwo(const arch_seccomp_data& data, void* aux) { return 2; } + +// Test that FakeTrapRegistry correctly assigns trap IDs to trap handlers. +TEST(FakeTrapRegistry, TrapIDs) { + struct { + TrapRegistry::TrapFnc fnc; + const void* aux; + } funcs[] = { + {FakeTrapFuncOne, nullptr}, + {FakeTrapFuncTwo, nullptr}, + {FakeTrapFuncOne, funcs}, + {FakeTrapFuncTwo, funcs}, + }; + + FakeTrapRegistry traps; + + // Add traps twice to test that IDs are reused correctly. + for (int i = 0; i < 2; ++i) { + for (size_t j = 0; j < arraysize(funcs); ++j) { + // Trap IDs start at 1. + EXPECT_EQ(j + 1, traps.Add(funcs[j].fnc, funcs[j].aux, true)); + } + } +} + +class PolicyEmulator { + public: + explicit PolicyEmulator(const Policy* policy) : program_(), traps_() { + program_ = *PolicyCompiler(policy, &traps_).Compile(true /* verify */); + } + ~PolicyEmulator() {} + + uint32_t Emulate(const struct arch_seccomp_data& data) const { + const char* err = nullptr; + uint32_t res = Verifier::EvaluateBPF(program_, data, &err); + if (err) { + ADD_FAILURE() << err; + return 0; + } + return res; + } + + void ExpectAllow(const struct arch_seccomp_data& data) const { + EXPECT_EQ(SECCOMP_RET_ALLOW, Emulate(data)); + } + + void ExpectErrno(uint16_t err, const struct arch_seccomp_data& data) const { + EXPECT_EQ(SECCOMP_RET_ERRNO | err, Emulate(data)); + } + + private: + CodeGen::Program program_; + FakeTrapRegistry traps_; + + DISALLOW_COPY_AND_ASSIGN(PolicyEmulator); +}; + +class BasicPolicy : public Policy { + public: + BasicPolicy() {} + ~BasicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_getpgid) { + const Arg<pid_t> pid(0); + return If(pid == 0, Error(EPERM)).Else(Error(EINVAL)); + } + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If(uid != 42, Error(ESRCH)).Else(Error(ENOMEM)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicPolicy); +}; + +TEST(BPFDSL, Basic) { + BasicPolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_getpgid, 0)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_getpgid, 1)); + + emulator.ExpectErrno(ENOMEM, FakeSyscall(__NR_setuid, 42)); + emulator.ExpectErrno(ESRCH, FakeSyscall(__NR_setuid, 43)); +} + +/* On IA-32, socketpair() is implemented via socketcall(). :-( */ +#if !defined(ARCH_CPU_X86) +class BooleanLogicPolicy : public Policy { + public: + BooleanLogicPolicy() {} + ~BooleanLogicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_socketpair) { + const Arg<int> domain(0), type(1), protocol(2); + return If(domain == AF_UNIX && + (type == SOCK_STREAM || type == SOCK_DGRAM) && + protocol == 0, + Error(EPERM)).Else(Error(EINVAL)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BooleanLogicPolicy); +}; + +TEST(BPFDSL, BooleanLogic) { + BooleanLogicPolicy policy; + PolicyEmulator emulator(&policy); + + const intptr_t kFakeSV = 0x12345; + + // Acceptable combinations that should return EPERM. + emulator.ExpectErrno( + EPERM, FakeSyscall(__NR_socketpair, AF_UNIX, SOCK_STREAM, 0, kFakeSV)); + emulator.ExpectErrno( + EPERM, FakeSyscall(__NR_socketpair, AF_UNIX, SOCK_DGRAM, 0, kFakeSV)); + + // Combinations that are invalid for only one reason; should return EINVAL. + emulator.ExpectErrno( + EINVAL, FakeSyscall(__NR_socketpair, AF_INET, SOCK_STREAM, 0, kFakeSV)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_socketpair, AF_UNIX, + SOCK_SEQPACKET, 0, kFakeSV)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_socketpair, AF_UNIX, + SOCK_STREAM, IPPROTO_TCP, kFakeSV)); + + // Completely unacceptable combination; should also return EINVAL. + emulator.ExpectErrno( + EINVAL, FakeSyscall(__NR_socketpair, AF_INET, SOCK_SEQPACKET, IPPROTO_UDP, + kFakeSV)); +} +#endif // !ARCH_CPU_X86 + +class MoreBooleanLogicPolicy : public Policy { + public: + MoreBooleanLogicPolicy() {} + ~MoreBooleanLogicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setresuid) { + const Arg<uid_t> ruid(0), euid(1), suid(2); + return If(ruid == 0 || euid == 0 || suid == 0, Error(EPERM)) + .ElseIf(ruid == 1 && euid == 1 && suid == 1, Error(EAGAIN)) + .Else(Error(EINVAL)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MoreBooleanLogicPolicy); +}; + +TEST(BPFDSL, MoreBooleanLogic) { + MoreBooleanLogicPolicy policy; + PolicyEmulator emulator(&policy); + + // Expect EPERM if any set to 0. + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 0, 5, 5)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 5, 0, 5)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 5, 5, 0)); + + // Expect EAGAIN if all set to 1. + emulator.ExpectErrno(EAGAIN, FakeSyscall(__NR_setresuid, 1, 1, 1)); + + // Expect EINVAL for anything else. + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 5, 1, 1)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 1, 5, 1)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 1, 1, 5)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 3, 4, 5)); +} + +static const uintptr_t kDeadBeefAddr = + static_cast<uintptr_t>(0xdeadbeefdeadbeefULL); + +class ArgSizePolicy : public Policy { + public: + ArgSizePolicy() {} + ~ArgSizePolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_uname) { + const Arg<uintptr_t> addr(0); + return If(addr == kDeadBeefAddr, Error(EPERM)).Else(Allow()); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ArgSizePolicy); +}; + +TEST(BPFDSL, ArgSizeTest) { + ArgSizePolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectAllow(FakeSyscall(__NR_uname, 0)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_uname, kDeadBeefAddr)); +} + +#if 0 +// TODO(mdempsky): This is really an integration test. + +class TrappingPolicy : public Policy { + public: + TrappingPolicy() {} + ~TrappingPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_uname) { + return Trap(UnameTrap, &count_); + } + return Allow(); + } + + private: + static intptr_t count_; + + static intptr_t UnameTrap(const struct arch_seccomp_data& data, void* aux) { + BPF_ASSERT_EQ(&count_, aux); + return ++count_; + } + + DISALLOW_COPY_AND_ASSIGN(TrappingPolicy); +}; + +intptr_t TrappingPolicy::count_; + +BPF_TEST_C(BPFDSL, TrapTest, TrappingPolicy) { + ASSERT_SYSCALL_RESULT(1, uname, NULL); + ASSERT_SYSCALL_RESULT(2, uname, NULL); + ASSERT_SYSCALL_RESULT(3, uname, NULL); +} +#endif + +class MaskingPolicy : public Policy { + public: + MaskingPolicy() {} + ~MaskingPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If((uid & 0xf) == 0, Error(EINVAL)).Else(Error(EACCES)); + } + if (sysno == __NR_setgid) { + const Arg<gid_t> gid(0); + return If((gid & 0xf0) == 0xf0, Error(EINVAL)).Else(Error(EACCES)); + } + if (sysno == __NR_setpgid) { + const Arg<pid_t> pid(0); + return If((pid & 0xa5) == 0xa0, Error(EINVAL)).Else(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MaskingPolicy); +}; + +TEST(BPFDSL, MaskTest) { + MaskingPolicy policy; + PolicyEmulator emulator(&policy); + + for (uid_t uid = 0; uid < 0x100; ++uid) { + const int expect_errno = (uid & 0xf) == 0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setuid, uid)); + } + + for (gid_t gid = 0; gid < 0x100; ++gid) { + const int expect_errno = (gid & 0xf0) == 0xf0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setgid, gid)); + } + + for (pid_t pid = 0; pid < 0x100; ++pid) { + const int expect_errno = (pid & 0xa5) == 0xa0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setpgid, pid, 0)); + } +} + +class ElseIfPolicy : public Policy { + public: + ElseIfPolicy() {} + ~ElseIfPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If((uid & 0xfff) == 0, Error(0)) + .ElseIf((uid & 0xff0) == 0, Error(EINVAL)) + .ElseIf((uid & 0xf00) == 0, Error(EEXIST)) + .Else(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ElseIfPolicy); +}; + +TEST(BPFDSL, ElseIfTest) { + ElseIfPolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectErrno(0, FakeSyscall(__NR_setuid, 0)); + + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setuid, 0x0001)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setuid, 0x0002)); + + emulator.ExpectErrno(EEXIST, FakeSyscall(__NR_setuid, 0x0011)); + emulator.ExpectErrno(EEXIST, FakeSyscall(__NR_setuid, 0x0022)); + + emulator.ExpectErrno(EACCES, FakeSyscall(__NR_setuid, 0x0111)); + emulator.ExpectErrno(EACCES, FakeSyscall(__NR_setuid, 0x0222)); +} + +class SwitchPolicy : public Policy { + public: + SwitchPolicy() {} + ~SwitchPolicy() override {} + 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); +}; + +TEST(BPFDSL, SwitchTest) { + SwitchPolicy policy; + PolicyEmulator emulator(&policy); + + const int kFakeSockFD = 42; + + emulator.ExpectErrno(ENOENT, FakeSyscall(__NR_fcntl, kFakeSockFD, F_GETFD)); + emulator.ExpectErrno(ENOENT, FakeSyscall(__NR_fcntl, kFakeSockFD, F_GETFL)); + + emulator.ExpectAllow( + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFD, O_CLOEXEC)); + emulator.ExpectErrno(EINVAL, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFD, 0)); + + emulator.ExpectErrno(EPERM, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFL, O_RDONLY)); + + emulator.ExpectErrno(EACCES, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_DUPFD, 0)); +} + +static intptr_t DummyTrap(const struct arch_seccomp_data& data, void* aux) { + return 0; +} + +TEST(BPFDSL, IsAllowDeny) { + ResultExpr allow = Allow(); + EXPECT_TRUE(allow->IsAllow()); + EXPECT_FALSE(allow->IsDeny()); + + ResultExpr error = Error(ENOENT); + EXPECT_FALSE(error->IsAllow()); + EXPECT_TRUE(error->IsDeny()); + + ResultExpr trace = Trace(42); + EXPECT_FALSE(trace->IsAllow()); + EXPECT_FALSE(trace->IsDeny()); + + ResultExpr trap = Trap(DummyTrap, nullptr); + EXPECT_FALSE(trap->IsAllow()); + EXPECT_TRUE(trap->IsDeny()); + + const Arg<int> arg(0); + ResultExpr maybe = If(arg == 0, Allow()).Else(Error(EPERM)); + EXPECT_FALSE(maybe->IsAllow()); + EXPECT_FALSE(maybe->IsDeny()); +} + +TEST(BPFDSL, HasUnsafeTraps) { + ResultExpr allow = Allow(); + EXPECT_FALSE(allow->HasUnsafeTraps()); + + ResultExpr safe = Trap(DummyTrap, nullptr); + EXPECT_FALSE(safe->HasUnsafeTraps()); + + ResultExpr unsafe = UnsafeTrap(DummyTrap, nullptr); + EXPECT_TRUE(unsafe->HasUnsafeTraps()); + + const Arg<int> arg(0); + ResultExpr maybe = If(arg == 0, allow).Else(unsafe); + EXPECT_TRUE(maybe->HasUnsafeTraps()); +} + +} // namespace +} // namespace bpf_dsl +} // namespace sandbox |