diff options
Diffstat (limited to 'test/syscalls/linux/exec_binary.cc')
-rw-r--r-- | test/syscalls/linux/exec_binary.cc | 1520 |
1 files changed, 0 insertions, 1520 deletions
diff --git a/test/syscalls/linux/exec_binary.cc b/test/syscalls/linux/exec_binary.cc deleted file mode 100644 index 0a3931e5a..000000000 --- a/test/syscalls/linux/exec_binary.cc +++ /dev/null @@ -1,1520 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <elf.h> -#include <errno.h> -#include <signal.h> -#include <sys/ptrace.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/user.h> -#include <unistd.h> -#include <algorithm> -#include <functional> -#include <iterator> -#include <tuple> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/proc_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using ::testing::AnyOf; -using ::testing::Eq; - -#ifndef __x86_64__ -// The assembly stub and ELF internal details must be ported to other arches. -#error "Test only supported on x86-64" -#endif // __x86_64__ - -// amd64 stub that calls PTRACE_TRACEME and sends itself SIGSTOP. -const char kPtraceCode[] = { - // movq $101, %rax /* ptrace */ - '\x48', - '\xc7', - '\xc0', - '\x65', - '\x00', - '\x00', - '\x00', - // movq $0, %rsi /* PTRACE_TRACEME */ - '\x48', - '\xc7', - '\xc6', - '\x00', - '\x00', - '\x00', - '\x00', - // movq $0, %rdi - '\x48', - '\xc7', - '\xc7', - '\x00', - '\x00', - '\x00', - '\x00', - // movq $0, %rdx - '\x48', - '\xc7', - '\xc2', - '\x00', - '\x00', - '\x00', - '\x00', - // movq $0, %r10 - '\x49', - '\xc7', - '\xc2', - '\x00', - '\x00', - '\x00', - '\x00', - // syscall - '\x0f', - '\x05', - - // movq $39, %rax /* getpid */ - '\x48', - '\xc7', - '\xc0', - '\x27', - '\x00', - '\x00', - '\x00', - // syscall - '\x0f', - '\x05', - - // movq %rax, %rdi /* pid */ - '\x48', - '\x89', - '\xc7', - // movq $62, %rax /* kill */ - '\x48', - '\xc7', - '\xc0', - '\x3e', - '\x00', - '\x00', - '\x00', - // movq $19, %rsi /* SIGSTOP */ - '\x48', - '\xc7', - '\xc6', - '\x13', - '\x00', - '\x00', - '\x00', - // syscall - '\x0f', - '\x05', -}; - -// Size of a syscall instruction. -constexpr int kSyscallSize = 2; - -// This test suite tests executable loading in the kernel (ELF and interpreter -// scripts). - -// Parameterized ELF types for 64 and 32 bit. -template <int Size> -struct ElfTypes; - -template <> -struct ElfTypes<64> { - typedef Elf64_Ehdr ElfEhdr; - typedef Elf64_Phdr ElfPhdr; -}; - -template <> -struct ElfTypes<32> { - typedef Elf32_Ehdr ElfEhdr; - typedef Elf32_Phdr ElfPhdr; -}; - -template <int Size> -struct ElfBinary { - using ElfEhdr = typename ElfTypes<Size>::ElfEhdr; - using ElfPhdr = typename ElfTypes<Size>::ElfPhdr; - - ElfEhdr header = {}; - std::vector<ElfPhdr> phdrs; - std::vector<char> data; - - // UpdateOffsets updates p_offset, p_vaddr in all phdrs to account for the - // space taken by the header and phdrs. - // - // It also updates header.e_phnum and adds the offset to header.e_entry to - // account for the headers residing in the first PT_LOAD segment. - // - // Before calling UpdateOffsets each of those fields should be the appropriate - // offset into data. - void UpdateOffsets() { - size_t offset = sizeof(header) + phdrs.size() * sizeof(ElfPhdr); - header.e_entry += offset; - header.e_phnum = phdrs.size(); - for (auto& p : phdrs) { - p.p_offset += offset; - p.p_vaddr += offset; - } - } - - // AddInterpreter adds a PT_INTERP segment with the passed contents. - // - // A later call to UpdateOffsets is required to make the new phdr valid. - void AddInterpreter(std::vector<char> contents) { - const int start = data.size(); - data.insert(data.end(), contents.begin(), contents.end()); - const int size = data.size() - start; - - ElfPhdr phdr = {}; - phdr.p_type = PT_INTERP; - phdr.p_offset = start; - phdr.p_filesz = size; - phdr.p_memsz = size; - // "If [PT_INTERP] is present, it must precede any loadable segment entry." - phdrs.insert(phdrs.begin(), phdr); - } - - // Writes the header, phdrs, and data to fd. - PosixError Write(int fd) const { - int ret = WriteFd(fd, &header, sizeof(header)); - if (ret < 0) { - return PosixError(errno, "failed to write header"); - } else if (ret != sizeof(header)) { - return PosixError(EIO, absl::StrCat("short write of header: ", ret)); - } - - for (auto const& p : phdrs) { - ret = WriteFd(fd, &p, sizeof(p)); - if (ret < 0) { - return PosixError(errno, "failed to write phdr"); - } else if (ret != sizeof(p)) { - return PosixError(EIO, absl::StrCat("short write of phdr: ", ret)); - } - } - - ret = WriteFd(fd, data.data(), data.size()); - if (ret < 0) { - return PosixError(errno, "failed to write data"); - } else if (ret != static_cast<int>(data.size())) { - return PosixError(EIO, absl::StrCat("short write of data: ", ret)); - } - - return NoError(); - } -}; - -// Creates a new temporary executable ELF file in parent with elf as the -// contents. -template <int Size> -PosixErrorOr<TempPath> CreateElfWith(absl::string_view parent, - ElfBinary<Size> const& elf) { - ASSIGN_OR_RETURN_ERRNO( - auto file, TempPath::CreateFileWith(parent, absl::string_view(), 0755)); - ASSIGN_OR_RETURN_ERRNO(auto fd, Open(file.path(), O_RDWR)); - RETURN_IF_ERRNO(elf.Write(fd.get())); - return std::move(file); -} - -// Creates a new temporary executable ELF file with elf as the contents. -template <int Size> -PosixErrorOr<TempPath> CreateElfWith(ElfBinary<Size> const& elf) { - return CreateElfWith(GetAbsoluteTestTmpdir(), elf); -} - -// Wait for pid to stop, and assert that it stopped via SIGSTOP. -PosixError WaitStopped(pid_t pid) { - int status; - int ret = RetryEINTR(waitpid)(pid, &status, 0); - MaybeSave(); - if (ret < 0) { - return PosixError(errno, "wait failed"); - } else if (ret != pid) { - return PosixError(ESRCH, absl::StrCat("wait got ", ret, " want ", pid)); - } - - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { - return PosixError(EINVAL, - absl::StrCat("pid did not SIGSTOP; status = ", status)); - } - - return NoError(); -} - -// Returns a valid ELF that PTRACE_TRACEME and SIGSTOPs itself. -// -// UpdateOffsets must be called before writing this ELF. -ElfBinary<64> StandardElf() { - ElfBinary<64> elf; - elf.header.e_ident[EI_MAG0] = ELFMAG0; - elf.header.e_ident[EI_MAG1] = ELFMAG1; - elf.header.e_ident[EI_MAG2] = ELFMAG2; - elf.header.e_ident[EI_MAG3] = ELFMAG3; - elf.header.e_ident[EI_CLASS] = ELFCLASS64; - elf.header.e_ident[EI_DATA] = ELFDATA2LSB; - elf.header.e_ident[EI_VERSION] = EV_CURRENT; - elf.header.e_type = ET_EXEC; - elf.header.e_machine = EM_X86_64; - elf.header.e_version = EV_CURRENT; - elf.header.e_phoff = sizeof(elf.header); - elf.header.e_phentsize = sizeof(decltype(elf)::ElfPhdr); - - // TODO(gvisor.dev/issue/153): Always include a PT_GNU_STACK segment to - // disable executable stacks. With this omitted the stack (and all PROT_READ) - // mappings should be executable, but gVisor doesn't support that. - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_GNU_STACK; - phdr.p_flags = PF_R | PF_W; - elf.phdrs.push_back(phdr); - - phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_X; - phdr.p_offset = 0; - phdr.p_vaddr = 0x40000; - phdr.p_filesz = sizeof(kPtraceCode); - phdr.p_memsz = phdr.p_filesz; - elf.phdrs.push_back(phdr); - - elf.header.e_entry = phdr.p_vaddr; - - elf.data.assign(kPtraceCode, kPtraceCode + sizeof(kPtraceCode)); - - return elf; -} - -// Test that a trivial binary executes. -TEST(ElfTest, Execute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - // Ensure it made it to SIGSTOP. - ASSERT_NO_ERRNO(WaitStopped(child)); - - struct user_regs_struct regs; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, ®s), SyscallSucceeds()); - // RIP is just beyond the final syscall instruction. - EXPECT_EQ(regs.rip, elf.header.e_entry + sizeof(kPtraceCode)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - }))); -} - -// StandardElf without data completes execve, but faults once running. -TEST(ElfTest, MissingText) { - ElfBinary<64> elf = StandardElf(); - elf.data.clear(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - // It runs off the end of the zeroes filling the end of the page. - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status; -} - -// Typical ELF with a data + bss segment -TEST(ElfTest, DataSegment) { - ElfBinary<64> elf = StandardElf(); - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - phdr.p_vaddr = 0x41000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT( - child, ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - // data + bss page from file. - {0x41000, 0x42000, true, true, false, true, kPageSize, 0, 0, 0, - file.path().c_str()}, - // bss page from anon. - {0x42000, 0x43000, true, true, false, true, 0, 0, 0, 0, ""}, - }))); -} - -// Additonal pages beyond filesz honor (only) execute protections. -// -// N.B. Linux changed this in 4.11 (16e72e9b30986 "powerpc: do not make the -// entire heap executable"). Previously, extra pages were always RW. -TEST(ElfTest, ExtraMemPages) { - // gVisor has the newer behavior. - if (!IsRunningOnGvisor()) { - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); - SKIP_IF(version.major < 4 || (version.major == 4 && version.minor < 11)); - } - - ElfBinary<64> elf = StandardElf(); - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - // RWX segment. The extra anon page will also be RWX. - // - // N.B. Linux uses clear_user to clear the end of the file-mapped page, which - // respects the mapping protections. Thus if we map this RO with memsz > - // (unaligned) filesz, then execve will fail with EFAULT. See padzero(elf_bss) - // in fs/binfmt_elf.c:load_elf_binary. - // - // N.N.B.B. The above only applies to the last segment. For earlier segments, - // the clear_user error is ignored. - phdr.p_flags = PF_R | PF_W | PF_X; - phdr.p_offset = kPageSize; - phdr.p_vaddr = 0x41000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, - ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - // data + bss page from file. - {0x41000, 0x42000, true, true, true, true, kPageSize, 0, 0, 0, - file.path().c_str()}, - // extra page from anon. - {0x42000, 0x43000, true, true, true, true, 0, 0, 0, 0, ""}, - }))); -} - -// An aligned segment with filesz == 0, memsz > 0 is anon-only. -TEST(ElfTest, AnonOnlySegment) { - ElfBinary<64> elf = StandardElf(); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - // RO segment. The extra anon page will be RW anyways. - phdr.p_flags = PF_R; - phdr.p_offset = 0; - phdr.p_vaddr = 0x41000; - phdr.p_filesz = 0; - phdr.p_memsz = kPageSize; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - // UpdateOffsets adjusts p_vaddr and p_offset by the header size, but we need - // a page-aligned p_vaddr to get a truly anon-only page. - elf.phdrs[2].p_vaddr = 0x41000; - // N.B. p_offset is now unaligned, but Linux doesn't care since this is - // anon-only. - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, - ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - // anon page. - {0x41000, 0x42000, true, true, false, true, 0, 0, 0, 0, ""}, - }))); -} - -// p_offset must have the same alignment as p_vaddr. -TEST(ElfTest, UnalignedOffset) { - ElfBinary<64> elf = StandardElf(); - - // Unaligned offset. - elf.phdrs[1].p_offset += 1; - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - - // execve(2) return EINVAL, but behavior varies between Linux and gVisor. - // - // On Linux, the new mm is committed before attempting to map into it. By the - // time we hit EINVAL in the segment mmap, the old mm is gone. Linux returns - // to an empty mm, which immediately segfaults. - // - // OTOH, gVisor maps into the new mm before committing it. Thus when it hits - // failure, the caller is still intact to receive the error. - if (IsRunningOnGvisor()) { - ASSERT_EQ(execve_errno, EINVAL); - } else { - ASSERT_EQ(execve_errno, 0); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status; - } -} - -// Linux will allow PT_LOAD segments to overlap. -TEST(ElfTest, DirectlyOverlappingSegments) { - // NOTE(b/37289926): see PIEOutOfOrderSegments. - SKIP_IF(IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - - // Same as the StandardElf mapping. - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - // Add PF_W so we can differentiate this mapping from the first. - phdr.p_flags = PF_R | PF_W | PF_X; - phdr.p_offset = 0; - phdr.p_vaddr = 0x40000; - phdr.p_filesz = sizeof(kPtraceCode); - phdr.p_memsz = phdr.p_filesz; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - {0x40000, 0x41000, true, true, true, true, 0, 0, 0, 0, - file.path().c_str()}, - }))); -} - -// Linux allows out-of-order PT_LOAD segments. -TEST(ElfTest, OutOfOrderSegments) { - // NOTE(b/37289926): see PIEOutOfOrderSegments. - SKIP_IF(IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_X; - phdr.p_offset = 0; - phdr.p_vaddr = 0x20000; - phdr.p_filesz = sizeof(kPtraceCode); - phdr.p_memsz = phdr.p_filesz; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - {0x20000, 0x21000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - }))); -} - -// header.e_phoff is bound the end of the file. -TEST(ElfTest, OutOfBoundsPhdrs) { - ElfBinary<64> elf = StandardElf(); - elf.header.e_phoff = 0x100000; - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - // On Linux 3.11, this caused EIO. On newer Linux, it causes ENOEXEC. - EXPECT_THAT(execve_errno, AnyOf(Eq(ENOEXEC), Eq(EIO))); -} - -// Claim there is a phdr beyond the end of the file, but don't include it. -TEST(ElfTest, MissingPhdr) { - ElfBinary<64> elf = StandardElf(); - - // Clear data so the file ends immediately after the phdrs. - // N.B. Per ElfTest.MissingData, StandardElf without data completes execve - // without error. - elf.data.clear(); - elf.UpdateOffsets(); - - // Claim that there is another phdr just beyond the end of the file. Of - // course, it isn't accessible. - elf.header.e_phnum++; - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - // On Linux 3.11, this caused EIO. On newer Linux, it causes ENOEXEC. - EXPECT_THAT(execve_errno, AnyOf(Eq(ENOEXEC), Eq(EIO))); -} - -// No headers at all, just the ELF magic. -TEST(ElfTest, MissingHeader) { - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0755)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - const char kElfMagic[] = {0x7f, 'E', 'L', 'F'}; - - ASSERT_THAT(WriteFd(fd.get(), &kElfMagic, sizeof(kElfMagic)), - SyscallSucceeds()); - fd.reset(); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ENOEXEC); -} - -// Load a PIE ELF with a data + bss segment. -TEST(ElfTest, PIE) { - ElfBinary<64> elf = StandardElf(); - - elf.header.e_type = ET_DYN; - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - elf.header.e_entry = 0x0; - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - // Put the data segment at a bit of an offset. - phdr.p_vaddr = 0x20000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - const uint64_t offset = elf.phdrs[1].p_offset; - elf.phdrs[1].p_offset = 0x0; - elf.phdrs[1].p_vaddr = 0x0; - elf.phdrs[1].p_filesz += offset; - elf.phdrs[1].p_memsz += offset; - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment was loaded into. - struct user_regs_struct regs; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, ®s), SyscallSucceeds()); - - const uint64_t load_addr = regs.rip & ~(kPageSize - 1); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {load_addr, load_addr + 0x1000, true, false, true, - true, 0, 0, 0, 0, file.path().c_str()}, - // data + bss page from file. - {load_addr + 0x20000, load_addr + 0x21000, true, true, - false, true, kPageSize, 0, 0, 0, file.path().c_str()}, - // bss page from anon. - {load_addr + 0x21000, load_addr + 0x22000, true, true, - false, true, 0, 0, 0, 0, ""}, - }))); -} - -// PIE binary with a non-zero start address. -// -// This is non-standard for a PIE binary, but valid. The binary is still loaded -// at an arbitrary address, not the first PT_LOAD vaddr. -// -// N.B. Linux changed this behavior in d1fd836dcf00d2028c700c7e44d2c23404062c90. -// Previously, with "randomization" enabled, PIE binaries with a non-zero start -// address would be be loaded at the address they specified because mmap was -// passed the load address, which wasn't 0 as expected. -// -// This change is present in kernel v4.1+. -TEST(ElfTest, PIENonZeroStart) { - // gVisor has the newer behavior. - if (!IsRunningOnGvisor()) { - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); - SKIP_IF(version.major < 4 || (version.major == 4 && version.minor < 1)); - } - - ElfBinary<64> elf = StandardElf(); - - elf.header.e_type = ET_DYN; - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - // Put the data segment at a bit of an offset. - phdr.p_vaddr = 0x60000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment was loaded into. - struct user_regs_struct regs; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, ®s), SyscallSucceeds()); - - const uint64_t load_addr = regs.rip & ~(kPageSize - 1); - - // The ELF is loaded at an arbitrary address, not the first PT_LOAD vaddr. - // - // N.B. this is technically flaky, but Linux is *extremely* unlikely to pick - // this as the start address, as it searches from the top down. - EXPECT_NE(load_addr, 0x40000); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {load_addr, load_addr + 0x1000, true, false, true, - true, 0, 0, 0, 0, file.path().c_str()}, - // data + bss page from file. - {load_addr + 0x20000, load_addr + 0x21000, true, true, - false, true, kPageSize, 0, 0, 0, file.path().c_str()}, - // bss page from anon. - {load_addr + 0x21000, load_addr + 0x22000, true, true, - false, true, 0, 0, 0, 0, ""}, - }))); -} - -TEST(ElfTest, PIEOutOfOrderSegments) { - // TODO(b/37289926): This triggers a bug in Linux where it computes the size - // of the binary as 0x20000 - 0x40000 = 0xfffffffffffe0000, which obviously - // fails to map. - // - // We test gVisor's behavior (of rejecting the binary) because I assert that - // Linux is wrong and needs to be fixed. - SKIP_IF(!IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - - elf.header.e_type = ET_DYN; - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - // Put the data segment *before* the first segment. - phdr.p_vaddr = 0x20000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ENOEXEC); -} - -// Standard dynamically linked binary with an ELF interpreter. -TEST(ElfTest, ELFInterpreter) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // N.B. Since Linux 4.10 (0036d1f7eb95b "binfmt_elf: fix calculations for bss - // padding"), Linux unconditionally zeroes the remainder of the highest mapped - // page in an interpreter, failing if the protections don't allow write. Thus - // we must mark this writeable. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - - // Append the interpreter path. - int const interp_data_start = binary.data.size(); - for (char const c : interpreter_file.path()) { - binary.data.push_back(c); - } - // NUL-terminate. - binary.data.push_back(0); - int const interp_data_size = binary.data.size() - interp_data_start; - - decltype(binary)::ElfPhdr phdr = {}; - phdr.p_type = PT_INTERP; - phdr.p_offset = interp_data_start; - phdr.p_filesz = interp_data_size; - phdr.p_memsz = interp_data_size; - // "If [PT_INTERP] is present, it must precede any loadable segment entry." - // - // However, Linux allows it anywhere, so we just stick it at the end to make - // sure out-of-order PT_INTERP is OK. - binary.phdrs.push_back(phdr); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment of the interpreter was loaded - // into. - struct user_regs_struct regs; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, ®s), SyscallSucceeds()); - - const uint64_t interp_load_addr = regs.rip & ~(kPageSize - 1); - - EXPECT_THAT( - child, ContainsMappings(std::vector<ProcMapsEntry>({ - // Main binary - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - binary_file.path().c_str()}, - // Interpreter - {interp_load_addr, interp_load_addr + 0x1000, true, true, true, - true, 0, 0, 0, 0, interpreter_file.path().c_str()}, - }))); -} - -// Test parameter to ElfInterpterStaticTest cases. The first item is a suffix to -// add to the end of the interpreter path in the PT_INTERP segment and the -// second is the expected execve(2) errno. -using ElfInterpreterStaticParam = std::tuple<std::vector<char>, int>; - -class ElfInterpreterStaticTest - : public ::testing::TestWithParam<ElfInterpreterStaticParam> {}; - -// Statically linked ELF with a statically linked ELF interpreter. -TEST_P(ElfInterpreterStaticTest, Test) { - const std::vector<char> segment_suffix = std::get<0>(GetParam()); - const int expected_errno = std::get<1>(GetParam()); - - ElfBinary<64> interpreter = StandardElf(); - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.UpdateOffsets(); - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - // The PT_LOAD segment conflicts with the interpreter's PT_LOAD segment. The - // interpreter's will be mapped directly over the binary's. - - // Interpreter path plus the parameterized suffix in the PT_INTERP segment. - const std::string path = interpreter_file.path(); - std::vector<char> segment(path.begin(), path.end()); - segment.insert(segment.end(), segment_suffix.begin(), segment_suffix.end()); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, expected_errno); - - if (expected_errno == 0) { - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - // Interpreter. - {0x40000, 0x41000, true, true, true, true, 0, 0, 0, - 0, interpreter_file.path().c_str()}, - }))); - } -} - -INSTANTIATE_TEST_SUITE_P( - Cases, ElfInterpreterStaticTest, - ::testing::ValuesIn({ - // Simple NUL-terminator to run the interpreter as normal. - std::make_tuple(std::vector<char>({'\0'}), 0), - // Add some garbage to the segment followed by a NUL-terminator. This is - // ignored. - std::make_tuple(std::vector<char>({'\0', 'b', '\0'}), 0), - // Add some garbage to the segment without a NUL-terminator. Linux will - // reject - // this. - std::make_tuple(std::vector<char>({'\0', 'b'}), ENOEXEC), - })); - -// Test parameter to ElfInterpterBadPathTest cases. The first item is the -// contents of the PT_INTERP segment and the second is the expected execve(2) -// errno. -using ElfInterpreterBadPathParam = std::tuple<std::vector<char>, int>; - -class ElfInterpreterBadPathTest - : public ::testing::TestWithParam<ElfInterpreterBadPathParam> {}; - -TEST_P(ElfInterpreterBadPathTest, Test) { - const std::vector<char> segment = std::get<0>(GetParam()); - const int expected_errno = std::get<1>(GetParam()); - - ElfBinary<64> binary = StandardElf(); - binary.AddInterpreter(segment); - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, expected_errno); -} - -INSTANTIATE_TEST_SUITE_P( - Cases, ElfInterpreterBadPathTest, - ::testing::ValuesIn({ - // NUL-terminated fake path in the PT_INTERP segment. - std::make_tuple(std::vector<char>({'/', 'f', '/', 'b', '\0'}), ENOENT), - // ELF interpreter not NUL-terminated. - std::make_tuple(std::vector<char>({'/', 'f', '/', 'b'}), ENOEXEC), - // ELF interpreter path omitted entirely. - // - // fs/binfmt_elf.c:load_elf_binary returns ENOEXEC if p_filesz is < 2 - // bytes. - std::make_tuple(std::vector<char>({'\0'}), ENOEXEC), - // ELF interpreter path = "\0". - // - // fs/binfmt_elf.c:load_elf_binary returns ENOEXEC if p_filesz is < 2 - // bytes, so add an extra byte to pass that check. - // - // load_elf_binary -> open_exec -> do_open_execat fails to check that - // name != '\0' before calling do_filp_open, which thus opens the - // working directory. do_open_execat returns EACCES because the - // directory is not a regular file. - std::make_tuple(std::vector<char>({'\0', '\0'}), EACCES), - })); - -// Relative path to ELF interpreter. -TEST(ElfTest, ELFInterpreterRelative) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); - auto interpreter_relative = - ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, interpreter_file.path())); - - ElfBinary<64> binary = StandardElf(); - - // NUL-terminated path in the PT_INTERP segment. - std::vector<char> segment(interpreter_relative.begin(), - interpreter_relative.end()); - segment.push_back(0); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment of the interpreter was loaded - // into. - struct user_regs_struct regs; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, ®s), SyscallSucceeds()); - - const uint64_t interp_load_addr = regs.rip & ~(kPageSize - 1); - - EXPECT_THAT( - child, ContainsMappings(std::vector<ProcMapsEntry>({ - // Main binary - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - binary_file.path().c_str()}, - // Interpreter - {interp_load_addr, interp_load_addr + 0x1000, true, true, true, - true, 0, 0, 0, 0, interpreter_file.path().c_str()}, - }))); -} - -// ELF interpreter architecture doesn't match the binary. -TEST(ElfTest, ELFInterpreterWrongArch) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_machine = EM_PPC64; - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - - // NUL-terminated path in the PT_INTERP segment. - const std::string path = interpreter_file.path(); - std::vector<char> segment(path.begin(), path.end()); - segment.push_back(0); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, ELIBBAD); -} - -// No execute permissions on the binary. -TEST(ElfTest, NoExecute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - ASSERT_THAT(chmod(file.path().c_str(), 0644), SyscallSucceeds()); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -// Execute, but no read permissions on the binary works just fine. -TEST(ElfTest, NoRead) { - // TODO(gvisor.dev/issue/160): gVisor's backing filesystem may prevent the - // sentry from reading the executable. - SKIP_IF(IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - ASSERT_THAT(chmod(file.path().c_str(), 0111), SyscallSucceeds()); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // TODO(gvisor.dev/issue/160): A task with a non-readable executable is marked - // non-dumpable, preventing access to proc files. gVisor does not implement - // this behavior. -} - -// No execute permissions on the ELF interpreter. -TEST(ElfTest, ElfInterpreterNoExecute) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - - // NUL-terminated path in the PT_INTERP segment. - const std::string path = interpreter_file.path(); - std::vector<char> segment(path.begin(), path.end()); - segment.push_back(0); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - ASSERT_THAT(chmod(interpreter_file.path().c_str(), 0644), SyscallSucceeds()); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(interpreter_file.path(), {interpreter_file.path()}, {}, - &child, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -// Execute a basic interpreter script. -TEST(InterpreterScriptTest, Execute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Whitespace after #!. -TEST(InterpreterScriptTest, Whitespace) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#! \t \t", binary.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script is missing execute permission. -TEST(InterpreterScriptTest, InterpreterScriptNoExecute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0644)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, EACCES); -} - -// Binary interpreter script refers to is missing execute permission. -TEST(InterpreterScriptTest, BinaryNoExecute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - ASSERT_THAT(chmod(binary.path().c_str(), 0644), SyscallSucceeds()); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, EACCES); -} - -// Linux will load interpreter scripts five levels deep, but no more. -TEST(InterpreterScriptTest, MaxRecursion) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", binary.path()), 0755)); - TempPath script2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script1.path()), 0755)); - TempPath script3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script2.path()), 0755)); - TempPath script4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script3.path()), 0755)); - TempPath script5 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script4.path()), 0755)); - TempPath script6 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script5.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script6.path(), {script6.path()}, {}, &child, &execve_errno)); - // Too many levels of recursion. - EXPECT_EQ(execve_errno, ELOOP); - - // The next level up is OK. - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script5.path(), {script5.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script with a relative path. -TEST(InterpreterScriptTest, RelativePath) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); - auto binary_relative = - ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, binary.path())); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary_relative), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script with .. in a path component. -TEST(InterpreterScriptTest, UncleanPath) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!/tmp/../", binary.path()), - 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Passed interpreter script is a symlink. -TEST(InterpreterScriptTest, Symlink) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755)); - - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), script.path())); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(link.path(), {link.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script points to a symlink loop. -TEST(InterpreterScriptTest, SymlinkLoop) { - std::string const link1 = NewTempAbsPathInDir("/tmp"); - std::string const link2 = NewTempAbsPathInDir("/tmp"); - - ASSERT_THAT(symlink(link2.c_str(), link1.c_str()), SyscallSucceeds()); - auto remove_link1 = Cleanup( - [&link1] { EXPECT_THAT(unlink(link1.c_str()), SyscallSucceeds()); }); - - ASSERT_THAT(symlink(link1.c_str(), link2.c_str()), SyscallSucceeds()); - auto remove_link2 = Cleanup( - [&link2] { EXPECT_THAT(unlink(link2.c_str()), SyscallSucceeds()); }); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link1), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ELOOP); -} - -// Binary is a symlink loop. -TEST(ExecveTest, SymlinkLoop) { - std::string const link1 = NewTempAbsPathInDir("/tmp"); - std::string const link2 = NewTempAbsPathInDir("/tmp"); - - ASSERT_THAT(symlink(link2.c_str(), link1.c_str()), SyscallSucceeds()); - auto remove_link = Cleanup( - [&link1] { EXPECT_THAT(unlink(link1.c_str()), SyscallSucceeds()); }); - - ASSERT_THAT(symlink(link1.c_str(), link2.c_str()), SyscallSucceeds()); - auto remove_link2 = Cleanup( - [&link2] { EXPECT_THAT(unlink(link2.c_str()), SyscallSucceeds()); }); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(link1, {link1}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ELOOP); -} - -// Binary is a directory. -TEST(ExecveTest, Directory) { - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/tmp", {"/tmp"}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -// Pass a valid binary as a directory (extra / on the end). -TEST(ExecveTest, BinaryAsDirectory) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - std::string const path = absl::StrCat(file.path(), "/"); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(path, {path}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ENOTDIR); -} - -// The initial brk value is after the page at the end of the binary. -TEST(ExecveTest, BrkAfterBinary) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - // Ensure it made it to SIGSTOP. - ASSERT_NO_ERRNO(WaitStopped(child)); - - struct user_regs_struct regs; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, ®s), SyscallSucceeds()); - - // RIP is just beyond the final syscall instruction. Rewind to execute a brk - // syscall. - regs.rip -= kSyscallSize; - regs.rax = __NR_brk; - regs.rdi = 0; - ASSERT_THAT(ptrace(PTRACE_SETREGS, child, 0, ®s), SyscallSucceeds()); - - // Resume the child, waiting for syscall entry. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child, 0, 0), SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << "status = " << status; - - // Execute the syscall. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child, 0, 0), SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << "status = " << status; - - ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, ®s), SyscallSucceeds()); - - // brk is after the text page. - // - // The kernel does brk randomization, so we can't be sure what the exact - // address will be, but it is always beyond the final page in the binary. - // i.e., it does not start immediately after memsz in the middle of a page. - // Userspace may expect to use that space. - EXPECT_GE(regs.rax, 0x41000); -} - -} // namespace - -} // namespace testing -} // namespace gvisor |