Files
visor/test/syscalls/linux/exec_binary.cc
T
Michael PrattandgVisor bot c288236ee4 Support new interpreter requirements in test
Refactoring in 0036d1f7eb95bcc52977f15507f00dd07018e7e2 (v4.10) caused Linux to
start unconditionally zeroing the remainder of the last page in the
interpreter. Previously it did not due so if filesz == memsz, and *still* does
not do so when filesz == memsz for loading binaries, only interpreter.

This inconsistency is not worth replicating in gVisor, as it is arguably a bug,
but our tests must ensure we create interpreter ELFs compatible with this new
requirement.

PiperOrigin-RevId: 272266401
2019-10-01 12:25:11 -07:00

1521 lines
50 KiB
C++

// 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, &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, ""},
})));
}
// 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, &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(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, &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, 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, &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, 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, &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