// 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/syscall.h" #include <asm/unistd.h> #include <errno.h> #include <fcntl.h> #include <stddef.h> #include <stdint.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> #include <vector> #include "base/macros.h" #include "base/posix/eintr_wrapper.h" #include "build/build_config.h" #include "sandbox/linux/bpf_dsl/bpf_dsl.h" #include "sandbox/linux/bpf_dsl/policy.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" using sandbox::bpf_dsl::Allow; using sandbox::bpf_dsl::ResultExpr; using sandbox::bpf_dsl::Trap; namespace sandbox { namespace { // Different platforms use different symbols for the six-argument version // of the mmap() system call. Test for the correct symbol at compile time. #ifdef __NR_mmap2 const int kMMapNr = __NR_mmap2; #else const int kMMapNr = __NR_mmap; #endif TEST(Syscall, InvalidCallReturnsENOSYS) { EXPECT_EQ(-ENOSYS, Syscall::InvalidCall()); } TEST(Syscall, WellKnownEntryPoint) { // Test that Syscall::Call(-1) is handled specially. Don't do this on ARM, // where syscall(-1) crashes with SIGILL. Not running the test is fine, as we // are still testing ARM code in the next set of tests. #if !defined(__arm__) && !defined(__aarch64__) EXPECT_NE(Syscall::Call(-1), syscall(-1)); #endif // If possible, test that Syscall::Call(-1) returns the address right // after // a kernel entry point. #if defined(__i386__) EXPECT_EQ(0x80CDu, ((uint16_t*)Syscall::Call(-1))[-1]); // INT 0x80 #elif defined(__x86_64__) EXPECT_EQ(0x050Fu, ((uint16_t*)Syscall::Call(-1))[-1]); // SYSCALL #elif defined(__arm__) #if defined(__thumb__) EXPECT_EQ(0xDF00u, ((uint16_t*)Syscall::Call(-1))[-1]); // SWI 0 #else EXPECT_EQ(0xEF000000u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 #endif #elif defined(__mips__) // Opcode for MIPS sycall is in the lower 16-bits EXPECT_EQ(0x0cu, (((uint32_t*)Syscall::Call(-1))[-1]) & 0x0000FFFF); #elif defined(__aarch64__) EXPECT_EQ(0xD4000001u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 #else #warning Incomplete test case; need port for target platform #endif } TEST(Syscall, TrivialSyscallNoArgs) { // Test that we can do basic system calls EXPECT_EQ(Syscall::Call(__NR_getpid), syscall(__NR_getpid)); } TEST(Syscall, TrivialSyscallOneArg) { int new_fd; // Duplicate standard error and close it. ASSERT_GE(new_fd = Syscall::Call(__NR_dup, 2), 0); int close_return_value = IGNORE_EINTR(Syscall::Call(__NR_close, new_fd)); ASSERT_EQ(close_return_value, 0); } TEST(Syscall, TrivialFailingSyscall) { errno = -42; int ret = Syscall::Call(__NR_dup, -1); ASSERT_EQ(-EBADF, ret); // Verify that Syscall::Call does not touch errno. ASSERT_EQ(-42, errno); } // SIGSYS trap handler that will be called on __NR_uname. intptr_t CopySyscallArgsToAux(const struct arch_seccomp_data& args, void* aux) { // |aux| is our BPF_AUX pointer. std::vector<uint64_t>* const seen_syscall_args = static_cast<std::vector<uint64_t>*>(aux); BPF_ASSERT(arraysize(args.args) == 6); seen_syscall_args->assign(args.args, args.args + arraysize(args.args)); return -ENOMEM; } class CopyAllArgsOnUnamePolicy : public bpf_dsl::Policy { public: explicit CopyAllArgsOnUnamePolicy(std::vector<uint64_t>* aux) : aux_(aux) {} ~CopyAllArgsOnUnamePolicy() override {} ResultExpr EvaluateSyscall(int sysno) const override { DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); if (sysno == __NR_uname) { return Trap(CopySyscallArgsToAux, aux_); } else { return Allow(); } } private: std::vector<uint64_t>* aux_; DISALLOW_COPY_AND_ASSIGN(CopyAllArgsOnUnamePolicy); }; // We are testing Syscall::Call() by making use of a BPF filter that // allows us // to inspect the system call arguments that the kernel saw. BPF_TEST(Syscall, SyntheticSixArgs, CopyAllArgsOnUnamePolicy, std::vector<uint64_t> /* (*BPF_AUX) */) { const int kExpectedValue = 42; // In this test we only pass integers to the kernel. We might want to make // additional tests to try other types. What we will see depends on // implementation details of kernel BPF filters and we will need to document // the expected behavior very clearly. int syscall_args[6]; for (size_t i = 0; i < arraysize(syscall_args); ++i) { syscall_args[i] = kExpectedValue + i; } // We could use pretty much any system call we don't need here. uname() is // nice because it doesn't have any dangerous side effects. BPF_ASSERT(Syscall::Call(__NR_uname, syscall_args[0], syscall_args[1], syscall_args[2], syscall_args[3], syscall_args[4], syscall_args[5]) == -ENOMEM); // We expect the trap handler to have copied the 6 arguments. BPF_ASSERT(BPF_AUX->size() == 6); // Don't loop here so that we can see which argument does cause the failure // easily from the failing line. // uint64_t is the type passed to our SIGSYS handler. BPF_ASSERT((*BPF_AUX)[0] == static_cast<uint64_t>(syscall_args[0])); BPF_ASSERT((*BPF_AUX)[1] == static_cast<uint64_t>(syscall_args[1])); BPF_ASSERT((*BPF_AUX)[2] == static_cast<uint64_t>(syscall_args[2])); BPF_ASSERT((*BPF_AUX)[3] == static_cast<uint64_t>(syscall_args[3])); BPF_ASSERT((*BPF_AUX)[4] == static_cast<uint64_t>(syscall_args[4])); BPF_ASSERT((*BPF_AUX)[5] == static_cast<uint64_t>(syscall_args[5])); } TEST(Syscall, ComplexSyscallSixArgs) { int fd; ASSERT_LE(0, fd = Syscall::Call(__NR_openat, AT_FDCWD, "/dev/null", O_RDWR, 0L)); // Use mmap() to allocate some read-only memory char* addr0; ASSERT_NE( (char*)NULL, addr0 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, (void*)NULL, 4096, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, fd, 0L))); // Try to replace the existing mapping with a read-write mapping char* addr1; ASSERT_EQ(addr0, addr1 = reinterpret_cast<char*>( Syscall::Call(kMMapNr, addr0, 4096L, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, fd, 0L))); ++*addr1; // This should not seg fault // Clean up EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr1, 4096L)); EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); // Check that the offset argument (i.e. the sixth argument) is processed // correctly. ASSERT_GE( fd = Syscall::Call(__NR_openat, AT_FDCWD, "/proc/self/exe", O_RDONLY, 0L), 0); char* addr2, *addr3; ASSERT_NE((char*)NULL, addr2 = reinterpret_cast<char*>(Syscall::Call( kMMapNr, (void*)NULL, 8192L, PROT_READ, MAP_PRIVATE, fd, 0L))); ASSERT_NE((char*)NULL, addr3 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, (void*)NULL, 4096L, PROT_READ, MAP_PRIVATE, fd, #if defined(__NR_mmap2) 1L #else 4096L #endif ))); EXPECT_EQ(0, memcmp(addr2 + 4096, addr3, 4096)); // Just to be absolutely on the safe side, also verify that the file // contents matches what we are getting from a read() operation. char buf[8192]; EXPECT_EQ(8192, Syscall::Call(__NR_read, fd, buf, 8192L)); EXPECT_EQ(0, memcmp(addr2, buf, 8192)); // Clean up EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr2, 8192L)); EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr3, 4096L)); EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); } } // namespace } // namespace sandbox