| // 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 <dlfcn.h> |
| #include <link.h> |
| #include <zircon/dlfcn.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/directory.h> |
| #include <stdio.h> |
| #include <sys/param.h> |
| #include <string.h> |
| |
| #include <unittest/unittest.h> |
| |
| bool vdso_base_test(void) { |
| BEGIN_TEST; |
| |
| char msg[128]; |
| |
| struct link_map* lm = dlopen("libzircon.so", RTLD_NOLOAD); |
| snprintf(msg, sizeof(msg), "dlopen(\"libzircon.so\") failed: %s", |
| dlerror()); |
| EXPECT_NONNULL(lm, msg); |
| uintptr_t rtld_vdso_base = lm->l_addr; |
| int ok = dlclose(lm); |
| snprintf(msg, sizeof(msg), "dlclose failed: %s", dlerror()); |
| EXPECT_EQ(ok, 0, msg); |
| |
| uintptr_t prop_vdso_base; |
| zx_status_t status = |
| zx_object_get_property(zx_process_self(), |
| ZX_PROP_PROCESS_VDSO_BASE_ADDRESS, |
| &prop_vdso_base, sizeof(prop_vdso_base)); |
| snprintf(msg, sizeof(msg), "zx_object_get_property failed: %d", status); |
| EXPECT_EQ(status, 0, msg); |
| |
| EXPECT_EQ(rtld_vdso_base, prop_vdso_base, |
| "rtld reported address != process property reported address"); |
| |
| END_TEST; |
| } |
| |
| static int phdr_info_callback(struct dl_phdr_info* info, size_t size, |
| void* data) { |
| struct dl_phdr_info* key = data; |
| if (info->dlpi_addr == key->dlpi_addr) { |
| *key = *info; |
| return 1; |
| } |
| return 0; |
| } |
| |
| bool vdso_unmap_test(void) { |
| BEGIN_TEST; |
| |
| char msg[128]; |
| |
| uintptr_t prop_vdso_base; |
| zx_status_t status = |
| zx_object_get_property(zx_process_self(), |
| ZX_PROP_PROCESS_VDSO_BASE_ADDRESS, |
| &prop_vdso_base, sizeof(prop_vdso_base)); |
| snprintf(msg, sizeof(msg), "zx_object_get_property failed: %d", status); |
| ASSERT_EQ(status, 0, msg); |
| |
| struct dl_phdr_info info = { .dlpi_addr = prop_vdso_base }; |
| int ret = dl_iterate_phdr(&phdr_info_callback, &info); |
| EXPECT_EQ(ret, 1, "dl_iterate_phdr didn't see vDSO?"); |
| |
| uintptr_t vdso_code_start = 0; |
| size_t vdso_code_len = 0; |
| for (uint_fast16_t i = 0; i < info.dlpi_phnum; ++i) { |
| if (info.dlpi_phdr[i].p_type == PT_LOAD && |
| (info.dlpi_phdr[i].p_flags & PF_X)) { |
| vdso_code_start = info.dlpi_addr + info.dlpi_phdr[i].p_vaddr; |
| vdso_code_len = info.dlpi_phdr[i].p_memsz; |
| break; |
| } |
| } |
| ASSERT_NE(vdso_code_start, 0u, "vDSO has no code segment?"); |
| ASSERT_NE(vdso_code_len, 0u, "vDSO has no code segment?"); |
| |
| // Removing the vDSO code mapping is not allowed. |
| status = zx_vmar_unmap(zx_vmar_root_self(), |
| vdso_code_start, vdso_code_len); |
| EXPECT_EQ(status, ZX_ERR_ACCESS_DENIED, "unmap vDSO code"); |
| |
| // Nor is removing a whole range overlapping the vDSO code. |
| status = zx_vmar_unmap(zx_vmar_root_self(), |
| vdso_code_start - PAGE_SIZE, |
| PAGE_SIZE * 2); |
| EXPECT_EQ(status, ZX_ERR_ACCESS_DENIED, "unmap range overlapping vDSO code"); |
| |
| END_TEST; |
| } |
| |
| bool vdso_map_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t vmo = zx_take_startup_handle(PA_HND(PA_VMO_VDSO, 0)); |
| ASSERT_NE(vmo, ZX_HANDLE_INVALID, "zx_take_startup_handle(PA_HND(PA_VMO_VDSO, 0))"); |
| |
| // Since we already have a vDSO mapping, loading it again should fail. |
| void* h = dlopen_vmo(vmo, RTLD_LOCAL); |
| EXPECT_NULL(h, "dlopen_vmo on vDSO VMO succeeded"); |
| |
| // Create a fresh process that doesn't already have a vDSO mapping. |
| // We can't meaningfully test the other constraints on our own |
| // process, because the "there can be only one" constraint trumps them. |
| const char* name = "vdso_map_test"; |
| zx_handle_t proc, vmar; |
| ASSERT_EQ(zx_process_create(zx_job_default(), |
| name, strlen(name), 0, &proc, &vmar), |
| ZX_OK, "zx_process_create failed"); |
| |
| // This should fail because it's an executable mapping of |
| // the wrong portion of the vDSO image (the first page is |
| // rodata including the ELF headers). Only the actual code |
| // segment can be mapped executable. |
| uintptr_t addr; |
| zx_status_t status = zx_vmar_map( |
| vmar, ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE, 0, vmo, 0, |
| PAGE_SIZE, &addr); |
| EXPECT_EQ(status, ZX_ERR_ACCESS_DENIED, "map vDSO data as executable"); |
| |
| zx_handle_close(proc); |
| zx_handle_close(vmar); |
| |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(vdso_base_tests) |
| RUN_TEST(vdso_base_test); |
| RUN_TEST(vdso_unmap_test); |
| RUN_TEST(vdso_map_test); |
| END_TEST_CASE(vdso_base_tests) |