| // 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 <lib/elfldltl/container.h> |
| #include <lib/elfldltl/diagnostics.h> |
| #include <lib/elfldltl/load.h> |
| #include <lib/elfldltl/vmar-loader.h> |
| #include <lib/elfldltl/vmo.h> |
| #include <lib/elfldltl/zircon.h> |
| #include <lib/zx/process.h> |
| #include <lib/zx/vmar.h> |
| #include <lib/zx/vmo.h> |
| #include <limits.h> |
| #include <string.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| |
| #include <new> |
| #include <vector> |
| |
| #include <fbl/array.h> |
| #include <zxtest/zxtest.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) { |
| using LoadInfo = |
| elfldltl::LoadInfo<elfldltl::Elf<>, elfldltl::StdContainer<std::vector>::Container>; |
| |
| zx_status_t status = ZX_OK; |
| auto report = [&status](auto&&... args) -> bool { |
| auto check = [&status](auto arg) { |
| if constexpr (std::is_same_v<decltype(arg), elfldltl::ZirconError>) { |
| status = arg.status; |
| } |
| }; |
| (check(args), ...); |
| return false; |
| }; |
| auto diag = elfldltl::Diagnostics(report, elfldltl::DiagnosticsPanicFlags()); |
| elfldltl::UnownedVmoFile file(vdso_vmo.borrow(), diag); |
| LoadInfo load_info; |
| elfldltl::RemoteVmarLoader loader{root_vmar()}; |
| auto headers = elfldltl::LoadHeadersFromFile<elfldltl::Elf<>>( |
| diag, file, elfldltl::NewArrayFromFile<elfldltl::Elf<>::Phdr>()); |
| ZX_ASSERT(headers); |
| auto& [ehdr, phdrs_result] = *headers; |
| cpp20::span<const elfldltl::Elf<>::Phdr> phdrs = phdrs_result.get(); |
| ZX_ASSERT(elfldltl::DecodePhdrs(diag, phdrs, load_info.GetPhdrObserver(loader.page_size()))); |
| load_info.VisitSegments([this](const auto& segment) { |
| if (segment.executable()) { |
| vdso_code_offset_ = segment.vaddr(); |
| vdso_code_size_ = segment.memsz(); |
| return false; |
| } |
| return true; |
| }); |
| if (really_load) { |
| if (loader.Load(diag, load_info, vdso_vmo.borrow())) { |
| ZX_ASSERT(status == ZX_OK); |
| vdso_base_ = load_info.vaddr_start() + loader.load_bias(); |
| zx::vmar vmar = std::move(loader).Commit(LoadInfo::Region{}).TakeVmar(); |
| if (segments_vmar) { |
| *segments_vmar = std::move(vmar); |
| } |
| } else { |
| ZX_ASSERT(status != ZX_OK); |
| } |
| } |
| 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; |
| }; |
| |
| TEST(VdsoTests, vdso_map_twice_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"); |
| } |
| |
| TEST(VdsoTests, vdso_map_change_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(ZX_VM_PERM_READ, scratch.vdso_code_address(), scratch.vdso_code_size()), |
| 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(ZX_VM_PERM_READ | ZX_VM_SPECIFIC_OVERWRITE, 0, vmo, 0, |
| scratch.vdso_total_size(), &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(ZX_VM_PERM_READ | ZX_VM_SPECIFIC_OVERWRITE, |
| scratch.vdso_base() - root_vmar_info.base, vmo, 0, |
| scratch.vdso_total_size(), &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"); |
| } |
| |
| TEST(VdsoTests, vdso_map_code_wrong_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(ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE, 0, vdso_vmo, 0, PAGE_SIZE, |
| &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(ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE, 0, vdso_vmo, |
| scratch.vdso_code_offset(), PAGE_SIZE, &addr), |
| ZX_ERR_ACCESS_DENIED, "executable mapping of subset of vDSO code"); |
| } |
| } |