diff options
author | jorgelo@chromium.org <jorgelo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-15 20:33:16 +0000 |
---|---|---|
committer | jorgelo@chromium.org <jorgelo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-15 20:33:16 +0000 |
commit | db30283ec8bbf3f36adf072c4e5827693a9fde9f (patch) | |
tree | 1faaa19d22425b3a6bf87d89e73a98db99c512c2 /sandbox/linux | |
parent | ff297228aab73ef4748c650d4ad77b84aeac4425 (diff) | |
download | chromium_src-db30283ec8bbf3f36adf072c4e5827693a9fde9f.zip chromium_src-db30283ec8bbf3f36adf072c4e5827693a9fde9f.tar.gz chromium_src-db30283ec8bbf3f36adf072c4e5827693a9fde9f.tar.bz2 |
Add a platform-specific syscall number iterator.
Avoid needlessly expensive scanning of system call ranges.
This CL improves how we deal with discontiguous ranges of system call numbers.
(Original CL by markus@chromium.org)
TEST=sandbox_linux_unittests on x86_64 and ARM
BUG=148856
Review URL: https://chromiumcodereview.appspot.com/11096012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@161943 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox/linux')
-rw-r--r-- | sandbox/linux/sandbox_linux.gypi | 3 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/Makefile | 2 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.cc | 85 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.h | 43 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc | 74 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/syscall_iterator.cc | 91 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/syscall_iterator.h | 58 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/syscall_iterator_unittest.cc | 135 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/verifier.cc | 17 |
9 files changed, 401 insertions, 107 deletions
diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi index 64b8b91..0508f70 100644 --- a/sandbox/linux/sandbox_linux.gypi +++ b/sandbox/linux/sandbox_linux.gypi @@ -57,6 +57,7 @@ 'seccomp-bpf/bpf_tests.h', 'seccomp-bpf/errorcode_unittest.cc', 'seccomp-bpf/sandbox_bpf_unittest.cc', + 'seccomp-bpf/syscall_iterator_unittest.cc', ], }], ], @@ -71,6 +72,8 @@ 'seccomp-bpf/errorcode.h', 'seccomp-bpf/sandbox_bpf.cc', 'seccomp-bpf/sandbox_bpf.h', + 'seccomp-bpf/syscall_iterator.cc', + 'seccomp-bpf/syscall_iterator.h', 'seccomp-bpf/verifier.cc', 'seccomp-bpf/verifier.h', ], diff --git a/sandbox/linux/seccomp-bpf/Makefile b/sandbox/linux/seccomp-bpf/Makefile index a8a2ca3..920b754 100644 --- a/sandbox/linux/seccomp-bpf/Makefile +++ b/sandbox/linux/seccomp-bpf/Makefile @@ -2,7 +2,7 @@ DEF_CFLAGS = -g -O3 -Wall -Werror -Wextra -Wno-missing-field-initializers -fPIC DEF_CPPFLAGS = -D_GNU_SOURCE -DSECCOMP_BPF_STANDALONE -DSECCOMP_BPF_VALGRIND_HACKS -include valgrind/valgrind.h -iquote ../../.. DEF_LDFLAGS = -g -lpthread DEPFLAGS = -MMD -MF .$@.d -MODS := demo sandbox_bpf die errorcode util verifier +MODS := demo sandbox_bpf die errorcode syscall_iterator util verifier OBJS64 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o64/') OBJS32 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o32/') ALL_OBJS = $(OBJS32) $(OBJS64) diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index bc7e682..643eacb 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -5,6 +5,7 @@ #include <time.h> #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall_iterator.h" #include "sandbox/linux/seccomp-bpf/verifier.h" // The kernel gives us a sandbox, we turn it into a playground :-) @@ -34,9 +35,12 @@ void Sandbox::probeProcess(void) { } } -ErrorCode Sandbox::allowAllEvaluator(int signo) { - if (signo < static_cast<int>(MIN_SYSCALL) || - signo > static_cast<int>(MAX_SYSCALL)) { +bool Sandbox::isValidSyscallNumber(int sysnum) { + return SyscallIterator::IsValid(sysnum); +} + +ErrorCode Sandbox::allowAllEvaluator(int sysnum) { + if (!isValidSyscallNumber(sysnum)) { return ErrorCode(ENOSYS); } return ErrorCode(ErrorCode::ERR_ALLOWED); @@ -144,7 +148,6 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), } return rc; - } bool Sandbox::kernelSupportSeccompBPF(int proc_fd) { @@ -277,46 +280,13 @@ bool Sandbox::isDenied(const ErrorCode& code) { void Sandbox::policySanityChecks(EvaluateSyscall syscallEvaluator, EvaluateArguments) { - // Do some sanity checks on the policy. This will warn users if they do - // things that are likely unsafe and unintended. - // We also have similar checks later, when we actually compile the BPF - // program. That catches problems with incorrectly stacked evaluators. - if (!isDenied(syscallEvaluator(-1))) { - SANDBOX_DIE("Negative system calls should always be disallowed by policy"); - } -#ifndef NDEBUG -#if defined(__i386__) || defined(__x86_64__) -#if defined(__x86_64__) && defined(__ILP32__) - for (unsigned int sysnum = MIN_SYSCALL & ~0x40000000u; - sysnum <= (MAX_SYSCALL & ~0x40000000u); - ++sysnum) { + for (SyscallIterator iter(true); !iter.Done(); ) { + uint32_t sysnum = iter.Next(); if (!isDenied(syscallEvaluator(sysnum))) { - SANDBOX_DIE("In x32 mode, you should not allow any non-x32 " - "system calls"); + SANDBOX_DIE("Policies should deny system calls that are outside the " + "expected range (typically MIN_SYSCALL..MAX_SYSCALL)"); } } -#else - for (unsigned int sysnum = MIN_SYSCALL | 0x40000000u; - sysnum <= (MAX_SYSCALL | 0x40000000u); - ++sysnum) { - if (!isDenied(syscallEvaluator(sysnum))) { - SANDBOX_DIE("x32 system calls should be explicitly disallowed"); - } - } -#endif -#endif -#endif - // Check interesting boundary values just outside of the valid system call - // range: 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF, MIN_SYSCALL-1, MAX_SYSCALL+1. - // They all should be denied. - if (!isDenied(syscallEvaluator(std::numeric_limits<int>::max())) || - !isDenied(syscallEvaluator(std::numeric_limits<int>::min())) || - !isDenied(syscallEvaluator(-1)) || - !isDenied(syscallEvaluator(static_cast<int>(MIN_SYSCALL) - 1)) || - !isDenied(syscallEvaluator(static_cast<int>(MAX_SYSCALL) + 1))) { - SANDBOX_DIE("Even for default-allow policies, you must never allow system " - "calls outside of the standard system call range"); - } return; } @@ -469,32 +439,23 @@ void Sandbox::findRanges(Ranges *ranges) { EvaluateSyscall evaluateSyscall = evaluators_.begin()->first; uint32_t oldSysnum = 0; ErrorCode oldErr = evaluateSyscall(oldSysnum); - for (uint32_t sysnum = std::max(1u, MIN_SYSCALL); - sysnum <= MAX_SYSCALL + 1; - ++sysnum) { + ErrorCode invalidErr = evaluateSyscall(MIN_SYSCALL - 1); + for (SyscallIterator iter(false); !iter.Done(); ) { + uint32_t sysnum = iter.Next(); ErrorCode err = evaluateSyscall(static_cast<int>(sysnum)); - if (!err.Equals(oldErr)) { - ranges->push_back(Range(oldSysnum, sysnum-1, oldErr)); + if (!iter.IsValid(sysnum) && !invalidErr.Equals(err)) { + // A proper sandbox policy should always treat system calls outside of + // the range MIN_SYSCALL..MAX_SYSCALL (i.e. anything that returns + // "false" for SyscallIterator::IsValid()) identically. Typically, all + // of these system calls would be denied with the same ErrorCode. + SANDBOX_DIE("Invalid seccomp policy"); + } + if (!err.Equals(oldErr) || iter.Done()) { + ranges->push_back(Range(oldSysnum, sysnum - 1, oldErr)); oldSysnum = sysnum; oldErr = err; } } - - // As we looped all the way past the valid system calls (i.e. MAX_SYSCALL+1), - // "oldErr" should at this point be the "default" policy for all system call - // numbers that don't have an explicit handler in the system call evaluator. - // But as we are quite paranoid, we perform some more sanity checks to verify - // that there actually is a consistent "default" policy in the first place. - // We don't actually iterate over all possible 2^32 values, though. We just - // perform spot checks at the boundaries. - // The cases that we test are: 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF. - if (!oldErr.Equals(evaluateSyscall(std::numeric_limits<int>::max())) || - !oldErr.Equals(evaluateSyscall(std::numeric_limits<int>::min())) || - !oldErr.Equals(evaluateSyscall(-1))) { - SANDBOX_DIE("Invalid seccomp policy"); - } - ranges->push_back( - Range(oldSysnum, std::numeric_limits<unsigned>::max(), oldErr)); } void Sandbox::emitJumpStatements(Program *program, RetInsns *rets, diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h index 5a177ad..8cc3b7b 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -87,8 +87,9 @@ #define SECCOMP_MAX_PROGRAM_SIZE (1<<30) #if defined(__i386__) -#define MIN_SYSCALL 0u -#define MAX_SYSCALL 1024u +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 1024u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL #define SECCOMP_ARCH AUDIT_ARCH_I386 #define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) @@ -103,8 +104,9 @@ #define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, REG_EBP) #elif defined(__x86_64__) -#define MIN_SYSCALL 0u -#define MAX_SYSCALL 1024u +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 1024u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL #define SECCOMP_ARCH AUDIT_ARCH_X86_64 #define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) @@ -123,8 +125,12 @@ // and a "ghost syscall private to the kernel", cmpxchg, // at |__ARM_NR_BASE+0x00fff0|. // See </arch/arm/include/asm/unistd.h> in the Linux kernel. -#define MIN_SYSCALL ((unsigned int)__NR_SYSCALL_BASE) -#define MAX_SYSCALL ((unsigned int)__ARM_NR_BASE + 0x00ffffu) +#define MIN_SYSCALL ((unsigned int)__NR_SYSCALL_BASE) +#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + 1024u) +#define MIN_PRIVATE_SYSCALL ((unsigned int)__ARM_NR_BASE) +#define MAX_PRIVATE_SYSCALL (MIN_PRIVATE_SYSCALL + 16u) +#define MIN_GHOST_SYSCALL ((unsigned int)__ARM_NR_BASE + 0xfff0u) +#define MAX_SYSCALL (MIN_GHOST_SYSCALL + 4u) // <linux/audit.h> includes <linux/elf-em.h>, which does not define EM_ARM. // <linux/elf.h> only includes <asm/elf.h> if we're in the kernel. # if !defined(EM_ARM) @@ -151,6 +157,15 @@ #endif +#if defined(SECCOMP_BPF_STANDALONE) +#define arraysize(x) (sizeof(x)/sizeof(*(x))) +#define HANDLE_EINTR TEMP_FAILURE_RETRY +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) +#endif + #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/errorcode.h" @@ -169,15 +184,6 @@ struct arch_sigsys { unsigned int arch; }; -#if defined(SECCOMP_BPF_STANDALONE) -#define arraysize(x) sizeof(x)/sizeof(*(x))) -#define HANDLE_EINTR TEMP_FAILURE_RETRY -#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName(); \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) -#endif - class Sandbox { public: enum SandboxStatus { @@ -217,6 +223,11 @@ class Sandbox { Constraint *constraint); typedef std::vector<std::pair<EvaluateSyscall,EvaluateArguments> >Evaluators; + // Checks whether a particular system call number is valid on the current + // architecture. E.g. on ARM there's a non-contiguous range of private + // system calls. + static bool isValidSyscallNumber(int sysnum); + // There are a lot of reasons why the Seccomp sandbox might not be available. // This could be because the kernel does not support Seccomp mode, or it // could be because another sandbox is already active. @@ -291,7 +302,7 @@ class Sandbox { static ErrorCode probeEvaluator(int signo) __attribute__((const)); static void probeProcess(void); - static ErrorCode allowAllEvaluator(int signo); + static ErrorCode allowAllEvaluator(int sysnum); static void tryVsyscallProcess(void); static bool kernelSupportSeccompBPF(int proc_fd); static bool RunFunctionInPolicy(void (*function)(), diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc index 1422b59..60554b3 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -13,9 +13,6 @@ using namespace playground2; namespace { const int kExpectedReturnValue = 42; -#if defined(__arm__) -const int kArmPublicSysnoCeiling = __NR_SYSCALL_BASE + 1024; -#endif // This test should execute no matter whether we have kernel support. So, // we make it a TEST() instead of a BPF_TEST(). @@ -41,11 +38,11 @@ SANDBOX_TEST(SandboxBpf, CallSupportsTwice) { // A simple blacklist test ErrorCode BlacklistNanosleepPolicy(int sysno) { - if (sysno < static_cast<int>(MIN_SYSCALL) || - sysno > static_cast<int>(MAX_SYSCALL)) { + if (!Sandbox::isValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } + switch (sysno) { case __NR_nanosleep: return ErrorCode(EACCES); @@ -100,11 +97,11 @@ intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) { } ErrorCode BlacklistNanosleepPolicySigsys(int sysno) { - if (sysno < static_cast<int>(MIN_SYSCALL) || - sysno > static_cast<int>(MAX_SYSCALL)) { + if (!Sandbox::isValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } + switch (sysno) { case __NR_nanosleep: return Sandbox::Trap(EnomemHandler, @@ -148,16 +145,16 @@ int SysnoToRandomErrno(int sysno) { } ErrorCode SyntheticPolicy(int sysno) { - if (sysno < static_cast<int>(MIN_SYSCALL) || - sysno > static_cast<int>(MAX_SYSCALL)) { - // FIXME: we should really not have to do that in a trivial policy. + if (!Sandbox::isValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } - // TODO(jorgelo): remove this restriction once crbug.com/141694 is fixed. +// TODO(jorgelo): remove this once the new code generator lands. #if defined(__arm__) - if (sysno > kArmPublicSysnoCeiling) + if (sysno > static_cast<int>(MAX_PUBLIC_SYSCALL)) { return ErrorCode(ENOSYS); + } #endif // TODO(markus): allow calls to write(). This should start working as soon @@ -177,17 +174,10 @@ BPF_TEST(SandboxBpf, SyntheticPolicy, SyntheticPolicy) { // overflow. BPF_ASSERT( std::numeric_limits<int>::max() - kExpectedReturnValue - 1 >= - static_cast<int>(MAX_SYSCALL)); - - // TODO(jorgelo): remove this limit once crbug.com/141694 is fixed. -#if defined(__arm__) - const int sysno_ceiling = kArmPublicSysnoCeiling; -#else - const int sysno_ceiling = static_cast<int>(MAX_SYSCALL); -#endif + static_cast<int>(MAX_PUBLIC_SYSCALL)); for (int syscall_number = static_cast<int>(MIN_SYSCALL); - syscall_number <= sysno_ceiling; + syscall_number <= static_cast<int>(MAX_PUBLIC_SYSCALL); ++syscall_number) { if (syscall_number == __NR_exit_group || syscall_number == __NR_write) { @@ -200,4 +190,46 @@ BPF_TEST(SandboxBpf, SyntheticPolicy, SyntheticPolicy) { } } +#if defined(__arm__) +// A simple policy that tests whether ARM private system calls are supported +// by our BPF compiler and by the BPF interpreter in the kernel. + +// For ARM private system calls, return an errno equal to their offset from +// MIN_PRIVATE_SYSCALL plus 1 (to avoid NUL errno). +int ArmPrivateSysnoToErrno(int sysno) { + if (sysno >= static_cast<int>(MIN_PRIVATE_SYSCALL) && + sysno <= static_cast<int>(MAX_PRIVATE_SYSCALL)) { + return (sysno - MIN_PRIVATE_SYSCALL) + 1; + } else { + return ENOSYS; + } +} + +ErrorCode ArmPrivatePolicy(int sysno) { + if (!Sandbox::isValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy. + return ErrorCode(ENOSYS); + } + + // Start from |__ARM_NR_set_tls + 1| so as not to mess with actual + // ARM private system calls. + if (sysno >= static_cast<int>(__ARM_NR_set_tls + 1) && + sysno <= static_cast<int>(MAX_PRIVATE_SYSCALL)) { + return ErrorCode(ArmPrivateSysnoToErrno(sysno)); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(SandboxBpf, ArmPrivatePolicy, ArmPrivatePolicy) { + for (int syscall_number = static_cast<int>(__ARM_NR_set_tls + 1); + syscall_number <= static_cast<int>(MAX_PRIVATE_SYSCALL); + ++syscall_number) { + errno = 0; + BPF_ASSERT(syscall(syscall_number) == -1); + BPF_ASSERT(errno == ArmPrivateSysnoToErrno(syscall_number)); + } +} +#endif // defined(__arm__) + } // namespace diff --git a/sandbox/linux/seccomp-bpf/syscall_iterator.cc b/sandbox/linux/seccomp-bpf/syscall_iterator.cc new file mode 100644 index 0000000..583dcf6 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall_iterator.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2012 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/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall_iterator.h" + +namespace playground2 { + +uint32_t SyscallIterator::Next() { + if (done_) { + return num_; + } + + uint32_t val; + do { + // |num_| has been initialized to 0, which we assume is also MIN_SYSCALL. + // This true for supported architectures (Intel and ARM EABI). + CHECK_EQ(MIN_SYSCALL, 0u); + val = num_; + + // First we iterate up to MAX_PUBLIC_SYSCALL, which is equal to MAX_SYSCALL + // on Intel architectures, but leaves room for private syscalls on ARM. + if (num_ <= MAX_PUBLIC_SYSCALL) { + if (invalid_only_ && num_ < MAX_PUBLIC_SYSCALL) { + num_ = MAX_PUBLIC_SYSCALL; + } else { + ++num_; + } +#if defined(__arm__) + // ARM EABI includes "ARM private" system calls starting at + // MIN_PRIVATE_SYSCALL, and a "ghost syscall private to the kernel" at + // MIN_GHOST_SYSCALL. + } else if (num_ < MIN_PRIVATE_SYSCALL - 1) { + num_ = MIN_PRIVATE_SYSCALL - 1; + } else if (num_ <= MAX_PRIVATE_SYSCALL) { + if (invalid_only_ && num_ < MAX_PRIVATE_SYSCALL) { + num_ = MAX_PRIVATE_SYSCALL; + } else { + ++num_; + } + } else if (num_ < MIN_GHOST_SYSCALL - 1) { + num_ = MIN_GHOST_SYSCALL - 1; + } else if (num_ <= MAX_SYSCALL) { + if (invalid_only_ && num_ < MAX_SYSCALL) { + num_ = MAX_SYSCALL; + } else { + ++num_; + } +#endif + // BPF programs only ever operate on unsigned quantities. So, that's how + // we iterate; we return values from 0..0xFFFFFFFFu. But there are places, + // where the kernel might interpret system call numbers as signed + // quantities, so the boundaries between signed and unsigned values are + // potential problem cases. We want to explicitly return these values from + // our iterator. + } else if (num_ < 0x7FFFFFFFu) { + num_ = 0x7FFFFFFFu; + } else if (num_ < 0x80000000u) { + num_ = 0x80000000u; + } else if (num_ < 0xFFFFFFFFu) { + num_ = 0xFFFFFFFFu; + } + } while (invalid_only_ && IsValid(val)); + + done_ |= val == 0xFFFFFFFFu; + return val; +} + +bool SyscallIterator::IsValid(uint32_t num) { + uint32_t min_syscall = MIN_SYSCALL; + if (num >= min_syscall && num <= MAX_PUBLIC_SYSCALL) { + return true; + } + if (IsArmPrivate(num)) { + return true; + } + return false; +} + +bool SyscallIterator::IsArmPrivate(uint32_t num) { +#if defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) + return (num >= MIN_PRIVATE_SYSCALL && num <= MAX_PRIVATE_SYSCALL) || + (num >= MIN_GHOST_SYSCALL && num <= MAX_SYSCALL); +#else + return false; +#endif +} + +} // namespace + diff --git a/sandbox/linux/seccomp-bpf/syscall_iterator.h b/sandbox/linux/seccomp-bpf/syscall_iterator.h new file mode 100644 index 0000000..39568d8 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall_iterator.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 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_SECCOMP_BPF_SYSCALL_ITERATOR_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_ITERATOR_H__ + +#include <stdint.h> + +#include <base/logging.h> + +namespace playground2 { + +// Iterates over the entire system call range from 0..0xFFFFFFFFu. This +// iterator is aware of how system calls look like and will skip quickly +// over ranges that can't contain system calls. It iterates more slowly +// whenever it reaches a range that is potentially problematic, returning +// the last invalid value before a valid range of system calls, and the +// first invalid value after a valid range of syscalls. It iterates over +// individual values whenever it is in the normal range for system calls +// (typically MIN_SYSCALL..MAX_SYSCALL). +// If |invalid_only| is true, this iterator will only return invalid +// syscall numbers, but will still skip quickly over invalid ranges, +// returning the first invalid value in the range and then skipping +// to the last invalid value in the range. +// +// Example usage: +// for (SyscallIterator iter(false); !iter.Done(); ) { +// uint32_t sysnum = iter.Next(); +// // Do something with sysnum. +// } +// +// TODO(markus): Make this a classic C++ iterator. +class SyscallIterator { + public: + explicit SyscallIterator(bool invalid_only) + : invalid_only_(invalid_only), + done_(false), + num_(0) {} + + bool Done() const { return done_; } + uint32_t Next(); + static bool IsValid(uint32_t num); + + private: + static bool IsArmPrivate(uint32_t num); + + bool invalid_only_; + bool done_; + uint32_t num_; + + DISALLOW_COPY_AND_ASSIGN(SyscallIterator); +}; + +} // namespace playground2 + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_ITERATOR_H__ + diff --git a/sandbox/linux/seccomp-bpf/syscall_iterator_unittest.cc b/sandbox/linux/seccomp-bpf/syscall_iterator_unittest.cc new file mode 100644 index 0000000..26f11ce --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall_iterator_unittest.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2012 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/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall_iterator.h" +#include "sandbox/linux/tests/unit_tests.h" + +using namespace playground2; + +namespace { + +SANDBOX_TEST(SyscallIterator, Monotonous) { + for (int i = 0; i < 2; ++i) { + bool invalid_only = !i; // Testing both |invalid_only| cases. + SyscallIterator iter(invalid_only); + uint32_t next = iter.Next(); + + if (!invalid_only) { + // The iterator should start at 0. + SANDBOX_ASSERT(next == 0); + } + for (uint32_t last = next; !iter.Done(); last = next) { + next = iter.Next(); + SANDBOX_ASSERT(last < next); + } + // The iterator should always return 0xFFFFFFFFu as the last value. + SANDBOX_ASSERT(next == 0xFFFFFFFFu); + } +} + +SANDBOX_TEST(SyscallIterator, PublicSyscallRange) { + SyscallIterator iter(false); + uint32_t next = iter.Next(); + + // The iterator should cover the public syscall range + // MIN_SYSCALL..MAX_PUBLIC_SYSCALL, without skipping syscalls. + // We're assuming MIN_SYSCALL == 0 for all architectures, + // this is currently valid for Intel and ARM EABI. + SANDBOX_ASSERT(MIN_SYSCALL == 0); + SANDBOX_ASSERT(next == MIN_SYSCALL); + for (uint32_t last = next; next < MAX_PUBLIC_SYSCALL + 1; last = next) { + SANDBOX_ASSERT((next = iter.Next()) == last + 1); + } + SANDBOX_ASSERT(next == MAX_PUBLIC_SYSCALL + 1); +} + +#if defined(__arm__) +SANDBOX_TEST(SyscallIterator, ARMPrivateSyscallRange) { + SyscallIterator iter(false); + uint32_t next = iter.Next(); + while (next < MIN_PRIVATE_SYSCALL - 1) { + next = iter.Next(); + } + // The iterator should cover the ARM private syscall range + // without skipping syscalls. + SANDBOX_ASSERT(next == MIN_PRIVATE_SYSCALL - 1); + for (uint32_t last = next; next < MAX_PRIVATE_SYSCALL + 1; last = next) { + SANDBOX_ASSERT((next = iter.Next()) == last + 1); + } + SANDBOX_ASSERT(next == MAX_PRIVATE_SYSCALL + 1); +} + +SANDBOX_TEST(SyscallIterator, ARMHiddenSyscallRange) { + SyscallIterator iter(false); + uint32_t next = iter.Next(); + while (next < MIN_GHOST_SYSCALL - 1) { + next = iter.Next(); + } + // The iterator should cover the ARM hidden syscall range + // without skipping syscalls. + SANDBOX_ASSERT(next == MIN_GHOST_SYSCALL - 1); + for (uint32_t last = next; next < MAX_SYSCALL + 1; last = next) { + SANDBOX_ASSERT((next = iter.Next()) == last + 1); + } + SANDBOX_ASSERT(next == MAX_SYSCALL + 1); +} +#endif + +SANDBOX_TEST(SyscallIterator, Invalid) { + for (int i = 0; i < 2; ++i) { + bool invalid_only = !i; // Testing both |invalid_only| cases. + SyscallIterator iter(invalid_only); + uint32_t next = iter.Next(); + + while (next < MAX_SYSCALL + 1) { + next = iter.Next(); + } + + SANDBOX_ASSERT(next == MAX_SYSCALL + 1); + while (next < 0x7FFFFFFFu) { + next = iter.Next(); + } + + // The iterator should return the signed/unsigned corner cases. + SANDBOX_ASSERT(next == 0x7FFFFFFFu); + next = iter.Next(); + SANDBOX_ASSERT(next == 0x80000000u); + SANDBOX_ASSERT(!iter.Done()); + next = iter.Next(); + SANDBOX_ASSERT(iter.Done()); + SANDBOX_ASSERT(next == 0xFFFFFFFFu); + } +} + +SANDBOX_TEST(SyscallIterator, InvalidOnly) { + bool invalid_only = true; + SyscallIterator iter(invalid_only); + uint32_t next = iter.Next(); + // We're assuming MIN_SYSCALL == 0 for all architectures, + // this is currently valid for Intel and ARM EABI. + // First invalid syscall should then be |MAX_PUBLIC_SYSCALL + 1|. + SANDBOX_ASSERT(MIN_SYSCALL == 0); + SANDBOX_ASSERT(next == MAX_PUBLIC_SYSCALL + 1); + +#if defined(__arm__) + next = iter.Next(); + // The iterator should skip until the last invalid syscall in this range. + SANDBOX_ASSERT(next == MIN_PRIVATE_SYSCALL - 1); + while (next <= MAX_PRIVATE_SYSCALL) { + next = iter.Next(); + } + + next = iter.Next(); + // The iterator should skip until the last invalid syscall in this range. + SANDBOX_ASSERT(next == MIN_GHOST_SYSCALL - 1); + while (next <= MAX_SYSCALL) { + next = iter.Next(); + } + SANDBOX_ASSERT(next == MAX_SYSCALL + 1); +#endif +} + +} // namespace + diff --git a/sandbox/linux/seccomp-bpf/verifier.cc b/sandbox/linux/seccomp-bpf/verifier.cc index 882e96f..343a6b4 100644 --- a/sandbox/linux/seccomp-bpf/verifier.cc +++ b/sandbox/linux/seccomp-bpf/verifier.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall_iterator.h" #include "sandbox/linux/seccomp-bpf/verifier.h" @@ -17,7 +18,8 @@ bool Verifier::verifyBPF(const std::vector<struct sock_filter>& program, return false; } Sandbox::EvaluateSyscall evaluateSyscall = evaluators.begin()->first; - for (int nr = MIN_SYSCALL-1; nr <= static_cast<int>(MAX_SYSCALL)+1; ++nr) { + for (SyscallIterator iter(false); !iter.Done(); ) { + uint32_t sysnum = iter.Next(); // We ideally want to iterate over the full system call range and values // just above and just below this range. This gives us the full result set // of the "evaluators". @@ -25,17 +27,18 @@ bool Verifier::verifyBPF(const std::vector<struct sock_filter>& program, // indicates either i386 or x86-64; and a set bit 30 indicates x32. And // unless we pay attention to setting this bit correctly, an early check in // our BPF program will make us fail with a misleading error code. + struct arch_seccomp_data data = { sysnum, SECCOMP_ARCH }; #if defined(__i386__) || defined(__x86_64__) #if defined(__x86_64__) && defined(__ILP32__) - int sysnum = nr | 0x40000000; + if (!(sysnum & 0x40000000u)) { + continue; + } #else - int sysnum = nr & ~0x40000000; + if (sysnum & 0x40000000u) { + continue; + } #endif -#else - int sysnum = nr; #endif - - struct arch_seccomp_data data = { sysnum, SECCOMP_ARCH }; ErrorCode code = evaluateSyscall(sysnum); uint32_t computedRet = evaluateBPF(program, data, err); if (*err) { |