blob: f05431ec782d9e73104d3e7dc68cc7e3c291bfea [file] [log] [blame]
// Copyright 2017 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 <elfload/elfload.h>
#include <fbl/array.h>
#include <lib/zx/process.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <limits.h>
#include <new>
#include <string.h>
#include <unittest/unittest.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
static const zx::vmo vdso_vmo{zx_take_startup_handle(PA_HND(PA_VMO_VDSO, 0))};
class ScratchPad {
public:
ScratchPad() = delete;
ScratchPad(const char* name) {
EXPECT_EQ(zx_process_create(
zx_job_default(),
name, static_cast<uint32_t>(strlen(name)), 0,
process_.reset_and_get_address(),
root_vmar_.reset_and_get_address()),
ZX_OK, "zx_process_create");
}
const zx::vmar& root_vmar() const { return root_vmar_; }
uintptr_t vdso_base() const { return vdso_base_; }
uintptr_t vdso_code_offset() const { return vdso_code_offset_; }
uintptr_t vdso_code_size() const { return vdso_code_size_; }
uintptr_t vdso_code_address() const {
return vdso_base_ + vdso_code_offset_;
}
uintptr_t vdso_total_size() const {
return vdso_code_offset_ + vdso_code_size_;
}
zx_status_t load_vdso(zx::vmar* segments_vmar = nullptr,
bool really_load = true) {
elf_load_header_t header;
uintptr_t phoff;
zx_status_t status = elf_load_prepare(vdso_vmo.get(), nullptr, 0,
&header, &phoff);
if (status == ZX_OK) {
fbl::Array<elf_phdr_t> phdrs(new elf_phdr_t[header.e_phnum],
header.e_phnum);
status = elf_load_read_phdrs(vdso_vmo.get(), phdrs.get(), phoff,
header.e_phnum);
if (status == ZX_OK) {
for (const auto& ph : phdrs) {
if (ph.p_type == PT_LOAD && (ph.p_type & PF_X)) {
vdso_code_offset_ = ph.p_vaddr;
vdso_code_size_ = ph.p_memsz;
}
}
if (really_load) {
status = elf_load_map_segments(
root_vmar_.get(), &header, phdrs.get(), vdso_vmo.get(),
segments_vmar ? segments_vmar->reset_and_get_address() : nullptr,
&vdso_base_, nullptr);
}
}
}
return status;
}
zx_status_t compute_vdso_sizes() {
return load_vdso(nullptr, false);
}
private:
zx::process process_;
zx::vmar root_vmar_;
uintptr_t vdso_base_ = 0;
uintptr_t vdso_code_offset_ = 0;
uintptr_t vdso_code_size_ = 0;
};
bool vdso_map_twice_test() {
BEGIN_TEST;
ScratchPad scratch(__func__);
// Load the vDSO once. That's on me.
EXPECT_EQ(scratch.load_vdso(), ZX_OK, "load vDSO into empty process");
// Load the vDSO twice. Can't get loaded again.
EXPECT_EQ(scratch.load_vdso(), ZX_ERR_ACCESS_DENIED, "load vDSO second time");
END_TEST;
}
bool vdso_map_change_test() {
BEGIN_TEST;
ScratchPad scratch(__func__);
// Load the vDSO and hold onto the sub-VMAR.
zx::vmar vdso_vmar;
EXPECT_EQ(scratch.load_vdso(&vdso_vmar), ZX_OK, "load vDSO");
// Changing protections on the code pages is forbidden.
EXPECT_EQ(vdso_vmar.protect(scratch.vdso_code_address(),
scratch.vdso_code_size(),
ZX_VM_PERM_READ),
ZX_ERR_ACCESS_DENIED, "zx_vmar_protect on vDSO code");
zx::vmo vmo;
ASSERT_EQ(zx::vmo::create(scratch.vdso_total_size(), 0, &vmo),
ZX_OK, "zx_vmo_create");
// Implicit unmapping by overwriting the mapping is forbidden.
uintptr_t addr = 0;
EXPECT_EQ(vdso_vmar.map(0, vmo, 0, scratch.vdso_total_size(),
ZX_VM_PERM_READ |
ZX_VM_SPECIFIC_OVERWRITE,
&addr),
ZX_ERR_ACCESS_DENIED, "zx_vmar_map to overmap vDSO");
EXPECT_EQ(addr, 0, "zx_vmar_map to overmap vDSO");
// Also forbidden if done from a parent VMAR.
zx_info_vmar_t root_vmar_info;
ASSERT_EQ(scratch.root_vmar().get_info(ZX_INFO_VMAR, &root_vmar_info,
sizeof(root_vmar_info),
nullptr, nullptr),
ZX_OK, "zx_object_get_info on root VMAR");
EXPECT_EQ(scratch.root_vmar().
map(scratch.vdso_base() - root_vmar_info.base, vmo,
0, scratch.vdso_total_size(),
ZX_VM_PERM_READ | ZX_VM_SPECIFIC_OVERWRITE,
&addr),
ZX_ERR_ACCESS_DENIED, "zx_vmar_map to overmap vDSO from root");
EXPECT_EQ(addr, 0, "zx_vmar_map to overmap vDSO from root");
// Explicit unmapping covering the vDSO code region is forbidden.
EXPECT_EQ(scratch.root_vmar().unmap(scratch.vdso_base(),
scratch.vdso_total_size()),
ZX_ERR_ACCESS_DENIED, "zx_vmar_unmap to unmap vDSO");
// Implicit unmapping by destroying a containing VMAR is forbidden.
EXPECT_EQ(vdso_vmar.destroy(),
ZX_ERR_ACCESS_DENIED, "zx_vmar_destroy to unmap vDSO");
EXPECT_EQ(scratch.root_vmar().destroy(),
ZX_ERR_ACCESS_DENIED, "zx_vmar_destroy on root to unmap vDSO");
END_TEST;
}
bool vdso_map_code_wrong_test() {
BEGIN_TEST;
ScratchPad scratch(__func__);
ASSERT_EQ(scratch.compute_vdso_sizes(), ZX_OK,
"cannot read vDSO program headers");
// Try to map the first page, which is not the code, as executable.
uintptr_t addr;
EXPECT_EQ(scratch.root_vmar().
map(0, vdso_vmo, 0, PAGE_SIZE,
ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE, &addr),
ZX_ERR_ACCESS_DENIED, "executable mapping of wrong part of vDSO");
// Try to map only part of the code, not the whole code segment.
ASSERT_GE(scratch.vdso_code_size(), PAGE_SIZE, "vDSO code < page??");
if (scratch.vdso_code_size() > PAGE_SIZE) {
ASSERT_EQ(scratch.vdso_code_size() % PAGE_SIZE, 0);
EXPECT_EQ(scratch.root_vmar().
map(0, vdso_vmo, scratch.vdso_code_offset(), PAGE_SIZE,
ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE, &addr),
ZX_ERR_ACCESS_DENIED,
"executable mapping of subset of vDSO code");
}
END_TEST;
}
BEGIN_TEST_CASE(vdso_tests)
RUN_TEST(vdso_map_twice_test);
RUN_TEST(vdso_map_code_wrong_test);
RUN_TEST(vdso_map_change_test);
END_TEST_CASE(vdso_tests)
int main(int argc, char** argv) {
return unittest_run_all_tests(argc, argv) ? 0 : -1;
}