blob: 961790f15e3965722c7fcb91ae21786c49cecb1c [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <gtest/gtest.h>
#include "src/lib/files/file.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
// VdsoModificationsDontAffectOtherPrograms invokes the test suite
// to run VdsoHasElfHeader, using gtest_filter. We need
// to keep them in sync.
#define ELF_HEADER_TEST_NAME "VdsoTest.VdsoHasElfHeader"
namespace {
// The ELF header has some bytes of padding that should be ignored by programs.
// We can modify those bytes and that shouldn't affect any vdso elf parsing logic.
constexpr size_t kEhdrEIPadOffset = 0x9;
bool IsEIPadFirstByteZero(void* addr) {
return static_cast<uint8_t*>(addr)[kEhdrEIPadOffset] == 0x0;
}
void SetEIPadFirstByte(void* addr, uint8_t val) {
static_cast<uint8_t*>(addr)[kEhdrEIPadOffset] = val;
}
bool IsElfMagic(void* addr) {
uint8_t elf_magic[] = {'\x7f', 'E', 'L', 'F'};
return memcmp(addr, elf_magic, sizeof(elf_magic)) == 0;
}
class VdsoProcTest : public ::testing::Test {
protected:
void SetUp() override {
vdso_base_ = reinterpret_cast<void*>(getauxval(AT_SYSINFO_EHDR));
// To get the vdso size, we need to either parse the vdso elf or look up the
// mapping size in /proc/self/maps. The latter is easier.
std::string maps;
ASSERT_TRUE(files::ReadFileToString("/proc/self/maps", &maps));
auto vdso_mapping = test_helper::find_memory_mapping(
[](const test_helper::MemoryMapping& mapping) { return mapping.pathname == "[vdso]"; },
maps);
ASSERT_NE(vdso_mapping, std::nullopt);
ASSERT_EQ(vdso_mapping->start, reinterpret_cast<uintptr_t>(vdso_base_));
vdso_size_ = static_cast<size_t>(vdso_mapping->end - vdso_mapping->start);
auto vvar_mapping = test_helper::find_memory_mapping(
[](const test_helper::MemoryMapping& mapping) { return mapping.pathname == "[vvar]"; },
maps);
ASSERT_NE(vvar_mapping, std::nullopt);
vvar_base_ = reinterpret_cast<void*>(vvar_mapping->start);
vvar_size_ = static_cast<size_t>(vvar_mapping->end - vvar_mapping->start);
}
void* vdso_base_;
size_t vdso_size_;
void* vvar_base_;
size_t vvar_size_;
};
} // namespace
TEST(VdsoTest, AtSysinfoEhdrPresent) {
uintptr_t addr = (uintptr_t)getauxval(AT_SYSINFO_EHDR);
EXPECT_NE(addr, 0ul);
}
TEST_F(VdsoProcTest, VdsoMappingCannotBeSplit) {
if (!test_helper::IsStarnix()) {
constexpr unsigned kMinMajorDisallowingSplitVdsoMapping = 5;
constexpr unsigned kMinMinorDisallowingSplitVdsoMapping = 11;
utsname u;
ASSERT_EQ(uname(&u), 0) << strerror(errno);
unsigned major = 0, minor = 0;
ASSERT_EQ(sscanf(u.release, "%u.%u", &major, &minor), 2) << u.release;
if ((major < kMinMajorDisallowingSplitVdsoMapping) ||
(major == kMinMajorDisallowingSplitVdsoMapping &&
minor < kMinMinorDisallowingSplitVdsoMapping)) {
GTEST_SKIP() << "Linux only disallows splitting a VDSO mapping as of v"
<< kMinMajorDisallowingSplitVdsoMapping << "."
<< kMinMinorDisallowingSplitVdsoMapping << ", we are at " << u.release;
}
}
const size_t page_size = SAFE_SYSCALL(sysconf(_SC_PAGE_SIZE));
// This test will be disabled in Starnix until their vDSO grows to more than one page.
if (vdso_size_ == page_size) {
GTEST_SKIP() << "Need more than one vdso page to split it";
}
test_helper::ForkHelper helper;
// We cannot unmap one page of the vdso.
helper.RunInForkedProcess([&] {
ASSERT_NE(munmap(vdso_base_, page_size), 0)
<< "vdso: base " << vdso_base_ << " size " << vdso_size_ << " page size: " << page_size;
EXPECT_EQ(errno, EINVAL);
});
EXPECT_TRUE(helper.WaitForChildren());
// We cannot mprotect one page of the vdso.
helper.RunInForkedProcess([&] {
ASSERT_NE(mprotect(vdso_base_, page_size, PROT_NONE), 0);
EXPECT_EQ(errno, EINVAL);
});
EXPECT_TRUE(helper.WaitForChildren());
// We cannot map on top of one page of the vdso.
helper.RunInForkedProcess([&] {
ASSERT_EQ(
mmap(vdso_base_, page_size, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
MAP_FAILED);
EXPECT_EQ(errno, ENOMEM);
});
EXPECT_TRUE(helper.WaitForChildren());
void* new_addr = mmap(nullptr, page_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(new_addr, MAP_FAILED);
// We cannot mremap one page of the vdso somewhere else.
helper.RunInForkedProcess([&, new_addr] {
ASSERT_EQ(mremap(vdso_base_, page_size, page_size, MREMAP_MAYMOVE | MREMAP_FIXED, new_addr),
MAP_FAILED);
EXPECT_EQ(errno, EINVAL);
});
EXPECT_TRUE(helper.WaitForChildren());
// We cannot mremap something else on top of one page of the vdso.
helper.RunInForkedProcess([&, new_addr] {
ASSERT_EQ(mremap(new_addr, page_size, page_size, MREMAP_MAYMOVE | MREMAP_FIXED, vdso_base_),
MAP_FAILED);
EXPECT_EQ(errno, EINVAL);
});
EXPECT_TRUE(helper.WaitForChildren());
ASSERT_EQ(munmap(new_addr, page_size), 0);
}
TEST_F(VdsoProcTest, VdsoCanBeUnmapped) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
EXPECT_EQ(munmap(vdso_base_, vdso_size_), 0);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VdsoCanBeMprotected) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
EXPECT_EQ(mprotect(vdso_base_, vdso_size_, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
EXPECT_EQ(mprotect(vdso_base_, vdso_size_, PROT_NONE), 0);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VdsoCanBeMappedInto) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
EXPECT_NE(
mmap(vdso_base_, vdso_size_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0),
MAP_FAILED);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VdsoCanBeRemapped) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
void* new_addr = mmap(nullptr, vdso_size_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(new_addr, MAP_FAILED);
EXPECT_NE(mremap(vdso_base_, vdso_size_, vdso_size_, MREMAP_FIXED | MREMAP_MAYMOVE, new_addr),
MAP_FAILED);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VdsoCanBeRemappedInto) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
void* new_addr = mmap(nullptr, vdso_size_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(new_addr, MAP_FAILED);
EXPECT_NE(mremap(new_addr, vdso_size_, vdso_size_, MREMAP_FIXED | MREMAP_MAYMOVE, vdso_base_),
MAP_FAILED);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VdsoModificationsDontAffectParent) {
ASSERT_TRUE(IsEIPadFirstByteZero(vdso_base_));
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
ASSERT_EQ(mprotect(vdso_base_, vdso_size_, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
SetEIPadFirstByte(vdso_base_, 0x3F);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
EXPECT_TRUE(IsEIPadFirstByteZero(vdso_base_));
}
TEST_F(VdsoProcTest, VdsoModificationsShowUpInFork) {
ASSERT_TRUE(IsEIPadFirstByteZero(vdso_base_));
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
ASSERT_EQ(mprotect(vdso_base_, vdso_size_, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
// We are going to modify the vdso. Try not to use complex code.
SetEIPadFirstByte(vdso_base_, 0x3F);
pid_t child_pid = fork();
if (child_pid == 0) {
// We should not see the ELF magic header on the vDSO.
_exit(IsEIPadFirstByteZero(vdso_base_) ? 1 : 0);
}
int status;
pid_t waited = waitpid(child_pid, &status, 0);
SetEIPadFirstByte(vdso_base_, 0x0);
EXPECT_EQ(waited, child_pid);
EXPECT_TRUE(WIFEXITED(status));
EXPECT_EQ(WEXITSTATUS(status), 0);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VdsoModificationsAferForkDontShowUpInChild) {
ASSERT_TRUE(IsEIPadFirstByteZero(vdso_base_));
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
ASSERT_EQ(mprotect(vdso_base_, vdso_size_, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
test_helper::SignalMaskHelper signal_helper = test_helper::SignalMaskHelper();
signal_helper.blockSignal(SIGUSR1);
test_helper::ForkHelper child_helper;
pid_t child_pid = fork();
if (child_pid == 0) {
signal_helper.waitForSignal(SIGUSR1);
// We should not see the vdso modified by our parent.
_exit(IsEIPadFirstByteZero(vdso_base_) ? 0 : 1);
}
SetEIPadFirstByte(vdso_base_, 0x3F);
ASSERT_EQ(kill(child_pid, SIGUSR1), 0);
int status;
pid_t waited = waitpid(child_pid, &status, 0);
SetEIPadFirstByte(vdso_base_, 0x0);
EXPECT_EQ(waited, child_pid) << errno;
EXPECT_TRUE(WIFEXITED(status));
EXPECT_EQ(WEXITSTATUS(status), 0);
signal_helper.restoreSigmask();
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST(VdsoTest, VdsoCanBeMadvised) {
const size_t page_size = SAFE_SYSCALL(sysconf(_SC_PAGE_SIZE));
void* vdso_addr = reinterpret_cast<void*>(getauxval(AT_SYSINFO_EHDR));
std::vector<uint8_t> vdso_bkp(page_size, 0);
memcpy((void*)vdso_bkp.data(), vdso_addr, page_size);
EXPECT_EQ(0, madvise(vdso_addr, page_size, MADV_DONTNEED));
EXPECT_EQ(0, memcmp(vdso_bkp.data(), vdso_addr, page_size));
}
TEST(VdsoTest, VdsoHasElfHeader) {
// This test is invoked by other tests, so we need to make sure the naming is
// consistent with ELF_HEADER_TEST_NAME.
std::string test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name();
std::string suite_name =
::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name();
ASSERT_EQ(suite_name + "." + test_name, ELF_HEADER_TEST_NAME);
void* vdso_addr = reinterpret_cast<void*>(getauxval(AT_SYSINFO_EHDR));
EXPECT_TRUE(IsElfMagic(vdso_addr));
EXPECT_TRUE(IsEIPadFirstByteZero(vdso_addr));
}
TEST_F(VdsoProcTest, VdsoModificationsDontAffectOtherPrograms) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
const char* argv[] = {"/proc/self/exe", "--gtest_filter=" ELF_HEADER_TEST_NAME, nullptr};
ASSERT_EQ(mprotect(vdso_base_, vdso_size_, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
// Don't print anything on the child.
ASSERT_EQ(fcntl(fileno(stdout), F_SETFD, FD_CLOEXEC), 0);
ASSERT_EQ(fcntl(fileno(stderr), F_SETFD, FD_CLOEXEC), 0);
ASSERT_EQ(fcntl(fileno(stdin), F_SETFD, FD_CLOEXEC), 0);
// We are going to modify the vdso. Try not to use complex code.
SetEIPadFirstByte(vdso_base_, 0x3F);
// From this point on, we can't use any vdso call.
execve(argv[0], const_cast<char**>(&argv[0]), nullptr);
_exit(1);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VdsoModificationsBeforeForkingDontAffectOtherPrograms) {
// Due to eager vmo copies in starnix fork, modifying the vdso before and
// after fork has different effects. This test checks that if we modify the
// vdso before forking, and execve into another binary, that binary will *not*
// see our vdso modifications.
const char* argv[] = {"/proc/self/exe", "--gtest_filter=" ELF_HEADER_TEST_NAME, nullptr};
ASSERT_EQ(mprotect(vdso_base_, vdso_size_, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
ASSERT_TRUE(IsEIPadFirstByteZero(vdso_base_));
// Don't print anything on the child.
ASSERT_EQ(fcntl(fileno(stdout), F_SETFD, FD_CLOEXEC), 0);
ASSERT_EQ(fcntl(fileno(stderr), F_SETFD, FD_CLOEXEC), 0);
ASSERT_EQ(fcntl(fileno(stdin), F_SETFD, FD_CLOEXEC), 0);
// We are going to modify the vdso. Try not to use complex code.
SetEIPadFirstByte(vdso_base_, 0x3F);
// From this point on, we can't use any vdso call.
pid_t child_pid = fork();
if (child_pid == 0) {
execve(argv[0], const_cast<char**>(&argv[0]), nullptr);
_exit(1);
} else {
int status;
pid_t waited = waitpid(child_pid, &status, 0);
// restore the vdso.
SetEIPadFirstByte(vdso_base_, 0x00);
ASSERT_EQ(mprotect(vdso_base_, vdso_size_, PROT_READ | PROT_EXEC), 0);
EXPECT_EQ(waited, child_pid);
EXPECT_TRUE(WIFEXITED(status));
EXPECT_EQ(WEXITSTATUS(status), 0);
}
}
TEST_F(VdsoProcTest, VvarCantWriteDeathTest) {
volatile uint8_t* vvar_addr = reinterpret_cast<volatile uint8_t*>(vvar_base_);
ASSERT_DEATH({ vvar_addr[0] = 3; }, "");
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
// Try writing from a system call.
EXPECT_FALSE(test_helper::TryWrite(reinterpret_cast<uintptr_t>(vvar_base_)));
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VvarCannotBeMadeWritable) {
EXPECT_EQ(mprotect(vvar_base_, vvar_size_, PROT_READ | PROT_WRITE), -1);
EXPECT_EQ(errno, EACCES);
EXPECT_EQ(mprotect(vvar_base_, vvar_size_, PROT_READ | PROT_EXEC), -1);
EXPECT_EQ(errno, EACCES);
EXPECT_EQ(mprotect(vvar_base_, vvar_size_, PROT_READ | PROT_EXEC | PROT_WRITE), -1);
EXPECT_EQ(errno, EACCES);
}
TEST_F(VdsoProcTest, VvarCannotBeMadeWritableAfterFork) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
EXPECT_EQ(mprotect(vvar_base_, vvar_size_, PROT_READ | PROT_WRITE), -1);
EXPECT_EQ(errno, EACCES);
EXPECT_EQ(mprotect(vvar_base_, vvar_size_, PROT_READ | PROT_EXEC), -1);
EXPECT_EQ(errno, EACCES);
EXPECT_EQ(mprotect(vvar_base_, vvar_size_, PROT_READ | PROT_EXEC | PROT_WRITE), -1);
EXPECT_EQ(errno, EACCES);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VvarCanBeUnmapped) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
EXPECT_EQ(munmap(vvar_base_, vvar_size_), 0);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VvarCanBeMadeNonReadable) {
test_helper::ForkHelper helper;
helper.RunInForkedProcess([&] {
EXPECT_EQ(mprotect(vvar_base_, vvar_size_, PROT_NONE), 0);
_exit(0);
});
EXPECT_TRUE(helper.WaitForChildren());
}
TEST_F(VdsoProcTest, VvarAndVdsoHaveCorrectPermissions) {
std::string maps;
ASSERT_TRUE(files::ReadFileToString("/proc/self/maps", &maps));
auto vdso_mapping = test_helper::find_memory_mapping(
[](const test_helper::MemoryMapping& mapping) { return mapping.pathname == "[vdso]"; }, maps);
ASSERT_NE(vdso_mapping, std::nullopt);
EXPECT_EQ(vdso_mapping->perms, "r-xp");
auto vvar_mapping = test_helper::find_memory_mapping(
[](const test_helper::MemoryMapping& mapping) { return mapping.pathname == "[vvar]"; }, maps);
ASSERT_NE(vvar_mapping, std::nullopt);
EXPECT_EQ(vvar_mapping->perms, "r--p");
// TODO(https://fxbug.dev/313689934): Add a similar test that checks the
// flags in /proc/self/smaps
}