summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/exec_binary.cc
diff options
context:
space:
mode:
authorBrian Geffon <bgeffon@google.com>2018-12-10 14:41:40 -0800
committerShentubot <shentubot@google.com>2018-12-10 14:42:34 -0800
commitd3bc79bc8438206ac6a14fde4eaa288fc07eee82 (patch)
treee820398591bfd1503456e877fa0c2bdd0f994959 /test/syscalls/linux/exec_binary.cc
parent833edbd10b49db1f934dcb2495dcb41c1310eea4 (diff)
Open source system call tests.
PiperOrigin-RevId: 224886231 Change-Id: I0fccb4d994601739d8b16b1d4e6b31f40297fb22
Diffstat (limited to 'test/syscalls/linux/exec_binary.cc')
-rw-r--r--test/syscalls/linux/exec_binary.cc1367
1 files changed, 1367 insertions, 0 deletions
diff --git a/test/syscalls/linux/exec_binary.cc b/test/syscalls/linux/exec_binary.cc
new file mode 100644
index 000000000..cfc898699
--- /dev/null
+++ b/test/syscalls/linux/exec_binary.cc
@@ -0,0 +1,1367 @@
+// Copyright 2018 Google LLC
+//
+// 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: 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, &regs), 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, ""},
+ })));
+}
+
+// Linux will allow PT_LOAD segments to overlap.
+TEST(ElfTest, DirectlyOverlappingSegments) {
+ // NOTE: 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: 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, &regs), 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, &regs), 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: 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;
+ 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, &regs), 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, false,
+ 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();
+ 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, false, true, true, 0, 0, 0,
+ 0, interpreter_file.path().c_str()},
+ })));
+ }
+}
+
+INSTANTIATE_TEST_CASE_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_CASE_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;
+ 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, &regs), 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, false,
+ 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;
+ 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'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: 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;
+ 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, &regs), 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, &regs), 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, &regs), 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