| // Copyright 2016 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/vdso.h> |
| #include <lib/vdso-constants.h> |
| |
| #include <kernel/cmdline.h> |
| #include <kernel/vm.h> |
| #include <vm/pmm.h> |
| #include <vm/vm_aspace.h> |
| #include <vm/vm_object.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/type_support.h> |
| #include <object/handles.h> |
| #include <platform.h> |
| |
| #include "vdso-code.h" |
| |
| // This is defined in assembly by vdso-image.S; vdso-code.h |
| // gives details about the image's size and layout. |
| extern "C" const char vdso_image[]; |
| |
| namespace { |
| |
| // Each KernelVmoWindow object represents a mapping in the kernel address |
| // space of a T object found inside a VM object. The kernel mapping exists |
| // for the lifetime of the KernelVmoWindow object. |
| template<typename T> |
| class KernelVmoWindow { |
| public: |
| static_assert(fbl::is_pod<T>::value, |
| "this is for C-compatible types only!"); |
| |
| KernelVmoWindow(const char* name, |
| fbl::RefPtr<VmObject> vmo, uint64_t offset) |
| : mapping_(nullptr) { |
| uint64_t page_offset = ROUNDDOWN(offset, PAGE_SIZE); |
| size_t offset_in_page = static_cast<size_t>(offset % PAGE_SIZE); |
| ASSERT(offset % alignof(T) == 0); |
| |
| const size_t size = offset_in_page + sizeof(T); |
| const uint arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE; |
| zx_status_t status = VmAspace::kernel_aspace()->RootVmar()->CreateVmMapping( |
| 0 /* ignored */, size, 0 /* align pow2 */, 0 /* vmar flags */, |
| fbl::move(vmo), page_offset, arch_mmu_flags, name, &mapping_); |
| ASSERT(status == ZX_OK); |
| data_ = reinterpret_cast<T*>(mapping_->base() + offset_in_page); |
| } |
| |
| ~KernelVmoWindow() { |
| if (mapping_) { |
| zx_status_t status = mapping_->Destroy(); |
| ASSERT(status == ZX_OK); |
| } |
| } |
| |
| T* data() const { return data_; } |
| |
| private: |
| fbl::RefPtr<VmMapping> mapping_; |
| T* data_; |
| }; |
| |
| // The .dynsym section of the vDSO, an array of ELF symbol table entries. |
| struct VDsoDynSym { |
| struct { |
| uintptr_t info, value, size; |
| } table[VDSO_DYNSYM_COUNT]; |
| }; |
| |
| #define PASTE(a, b, c) PASTE_1(a, b, c) |
| #define PASTE_1(a, b, c) a##b##c |
| |
| class VDsoDynSymWindow { |
| public: |
| DISALLOW_COPY_ASSIGN_AND_MOVE(VDsoDynSymWindow); |
| |
| static_assert(sizeof(VDsoDynSym) == |
| VDSO_DATA_END_dynsym - VDSO_DATA_START_dynsym, |
| "either VDsoDynsym or gen-rodso-code.sh is suspect"); |
| |
| explicit VDsoDynSymWindow(fbl::RefPtr<VmObject> vmo) : |
| window_("vDSO .dynsym", fbl::move(vmo), VDSO_DATA_START_dynsym) {} |
| |
| void get_symbol_entry(size_t i, uintptr_t* value, size_t* size) { |
| *value = window_.data()->table[i].value; |
| *size = window_.data()->table[i].size; |
| } |
| |
| void set_symbol_entry(size_t i, uintptr_t value, size_t size) { |
| window_.data()->table[i].value = value; |
| window_.data()->table[i].size = size; |
| } |
| |
| void localize_symbol_entry(size_t i) { |
| // The high nybble is the STB_* bits; STB_LOCAL is 0. |
| window_.data()->table[i].info &= 0xf; |
| } |
| |
| #define get_symbol(symbol, value, size) \ |
| get_symbol_entry(PASTE(VDSO_DYNSYM_, symbol,), value, size) |
| |
| #define set_symbol(symbol, target) \ |
| set_symbol_entry(PASTE(VDSO_DYNSYM_, symbol,), \ |
| PASTE(VDSO_CODE_, target,), \ |
| PASTE(VDSO_CODE_, target, _SIZE)) |
| |
| #define localize_symbol(symbol) \ |
| localize_symbol_entry(PASTE(VDSO_DYNSYM_, symbol,)) |
| |
| private: |
| KernelVmoWindow<VDsoDynSym> window_; |
| }; |
| |
| class VDsoCodeWindow { |
| public: |
| DISALLOW_COPY_ASSIGN_AND_MOVE(VDsoCodeWindow); |
| |
| using CodeBuffer = uint8_t[VDSO_CODE_END - VDSO_CODE_START]; |
| |
| explicit VDsoCodeWindow(fbl::RefPtr<VmObject> vmo) : |
| window_("vDSO code segment", fbl::move(vmo), VDSO_CODE_START) {} |
| |
| // Fill the given code region (a whole function) with safely invalid code. |
| // This code should never be run, and any attempt to use it should crash. |
| void blacklist(uintptr_t address, size_t size) { |
| ASSERT(address >= VDSO_CODE_START); |
| ASSERT(address + size < VDSO_CODE_END); |
| address -= VDSO_CODE_START; |
| |
| #if ARCH_X86 |
| |
| // Fill with the single-byte HLT instruction, so any place |
| // user-mode jumps into this code, it gets a trap. |
| memset(&Code()[address], 0xf4, size); |
| |
| #elif ARCH_ARM64 |
| |
| // Fixed-size instructions. |
| ASSERT(address % 4 == 0); |
| ASSERT(size % 4 == 0); |
| uint32_t* code = reinterpret_cast<uint32_t*>(&Code()[address]); |
| for (size_t i = 0; i < size / 4; ++i) |
| code[i] = 0xd4200020; // 'brk #1' (what __builtin_trap() emits) |
| |
| #else |
| #error what architecture? |
| #endif |
| } |
| |
| private: |
| CodeBuffer& Code() { |
| return *window_.data(); |
| } |
| |
| KernelVmoWindow<CodeBuffer> window_; |
| }; |
| |
| #define REDIRECT_SYSCALL(dynsym_window, symbol, target) \ |
| do { \ |
| dynsym_window.set_symbol(symbol, target); \ |
| dynsym_window.set_symbol(_ ## symbol, target); \ |
| } while (0) |
| |
| // Blacklist the named zx_* function. The symbol table entry will |
| // become invisible to runtime symbol resolution, and the code of |
| // the function will be clobbered with trapping instructions. |
| #define BLACKLIST_SYSCALL(dynsym_window, code_window, symbol) \ |
| do { \ |
| dynsym_window.localize_symbol(symbol); \ |
| dynsym_window.localize_symbol(_ ## symbol); \ |
| uintptr_t address, _address; \ |
| size_t size, _size; \ |
| dynsym_window.get_symbol(symbol, &address, &size); \ |
| dynsym_window.get_symbol(_ ## symbol, &_address, &_size); \ |
| ASSERT(address == _address); \ |
| ASSERT(size == _size); \ |
| code_window.blacklist(address, size); \ |
| } while (0) |
| |
| // Random attributes in syscalls.sysgen become "categories" of syscalls. |
| // For each category, define a function blacklist_<category> to blacklist |
| // all the syscalls in that category. These functions can be used in |
| // VDso::CreateVariant (below) to blacklist a category of syscalls for |
| // a particular variant vDSO. |
| #define SYSCALL_CATEGORY_BEGIN(category) \ |
| void blacklist_##category##_syscalls(VDsoDynSymWindow& dynsym_window, \ |
| VDsoCodeWindow& code_window) { |
| #define SYSCALL_IN_CATEGORY(syscall) \ |
| BLACKLIST_SYSCALL(dynsym_window, code_window, zx_##syscall); |
| #define SYSCALL_CATEGORY_END(category) \ |
| } |
| #include <zircon/syscall-category.inc> |
| #undef SYSCALL_CATEGORY_BEGIN |
| #undef SYSCALL_IN_CATEGORY_END |
| #undef SYSCALL_CATEGORY_END |
| |
| } // anonymous namespace |
| |
| const VDso* VDso::instance_ = NULL; |
| |
| // Private constructor, can only be called by Create (below). |
| VDso::VDso() : RoDso("vdso/full", vdso_image, |
| VDSO_CODE_END, VDSO_CODE_START) {} |
| |
| // This is called exactly once, at boot time. |
| const VDso* VDso::Create() { |
| ASSERT(!instance_); |
| |
| fbl::AllocChecker ac; |
| VDso* vdso = new(&ac) VDso(); |
| ASSERT(ac.check()); |
| |
| // Map a window into the VMO to write the vdso_constants struct. |
| static_assert(sizeof(vdso_constants) == VDSO_DATA_CONSTANTS_SIZE, |
| "gen-rodso-code.sh is suspect"); |
| KernelVmoWindow<vdso_constants> constants_window( |
| "vDSO constants", vdso->vmo()->vmo(), VDSO_DATA_CONSTANTS); |
| uint64_t per_second = ticks_per_second(); |
| |
| // Initialize the constants that should be visible to the vDSO. |
| // Rather than assigning each member individually, do this with |
| // struct assignment and a compound literal so that the compiler |
| // can warn if the initializer list omits any member. |
| *constants_window.data() = (vdso_constants) { |
| arch_max_num_cpus(), |
| arch_dcache_line_size(), |
| arch_icache_line_size(), |
| per_second, |
| pmm_count_total_bytes(), |
| }; |
| |
| // If ticks_per_second has not been calibrated, it will return 0. In this |
| // case, use soft_ticks instead. |
| if (per_second == 0 || cmdline_get_bool("vdso.soft_ticks", false)) { |
| // Make zx_ticks_per_second return nanoseconds per second. |
| constants_window.data()->ticks_per_second = ZX_SEC(1); |
| |
| // Adjust the zx_ticks_get entry point to be soft_ticks_get. |
| VDsoDynSymWindow dynsym_window(vdso->vmo()->vmo()); |
| REDIRECT_SYSCALL(dynsym_window, zx_ticks_get, soft_ticks_get); |
| } |
| |
| for (size_t v = static_cast<size_t>(Variant::FULL) + 1; |
| v < static_cast<size_t>(Variant::COUNT); |
| ++v) |
| vdso->CreateVariant(static_cast<Variant>(v)); |
| |
| instance_ = vdso; |
| return instance_; |
| } |
| |
| uintptr_t VDso::base_address(const fbl::RefPtr<VmMapping>& code_mapping) { |
| return code_mapping ? code_mapping->base() - VDSO_CODE_START : 0; |
| } |
| |
| HandleOwner VDso::vmo_handle(Variant variant) const { |
| ASSERT(variant < Variant::COUNT); |
| |
| if (variant == Variant::FULL) |
| return RoDso::vmo_handle(); |
| |
| DEBUG_ASSERT(!(vmo_rights() & ZX_RIGHT_WRITE)); |
| return HandleOwner(MakeHandle(variant_vmo_[variant_index(variant)], |
| vmo_rights())); |
| } |
| |
| // Each vDSO variant VMO is made via a COW clone of the main/default vDSO |
| // VMO. A variant can blacklist some system calls, by syscall category. |
| // This works by modifying the symbol table entries to make the symbols |
| // invisible to dynamic linking (STB_LOCAL) and then clobbering the code |
| // with trapping instructions. In this way, all the code locations are the |
| // same across variants and the syscall entry enforcement doesn't have to |
| // care which variant is in use. The places where the blacklisted |
| // syscalls' syscall entry instructions would be no longer have the syscall |
| // instructions, so a process using the variant can never get into syscall |
| // entry with that PC value and hence can never pass the vDSO enforcement |
| // test. |
| void VDso::CreateVariant(Variant variant) { |
| DEBUG_ASSERT(variant > Variant::FULL); |
| DEBUG_ASSERT(variant < Variant::COUNT); |
| DEBUG_ASSERT(!variant_vmo_[variant_index(variant)]); |
| |
| fbl::RefPtr<VmObject> new_vmo; |
| zx_status_t status = vmo()->Clone(ZX_VMO_CLONE_COPY_ON_WRITE, 0, size(), |
| false, &new_vmo); |
| ASSERT(status == ZX_OK); |
| |
| VDsoDynSymWindow dynsym_window(new_vmo); |
| VDsoCodeWindow code_window(new_vmo); |
| |
| const char* name = nullptr; |
| switch (variant) { |
| case Variant::TEST1: |
| name = "vdso/test1"; |
| blacklist_test_category1_syscalls(dynsym_window, code_window); |
| break; |
| |
| case Variant::TEST2: |
| name = "vdso/test2"; |
| blacklist_test_category2_syscalls(dynsym_window, code_window); |
| break; |
| |
| // No default case so the compiler will warn about new enum entries. |
| case Variant::FULL: |
| case Variant::COUNT: |
| PANIC("VDso::CreateVariant called with bad variant"); |
| } |
| |
| fbl::RefPtr<Dispatcher> dispatcher; |
| zx_rights_t rights; |
| status = VmObjectDispatcher::Create(fbl::move(new_vmo), |
| &dispatcher, &rights); |
| ASSERT(status == ZX_OK); |
| |
| status = dispatcher->set_name(name, strlen(name)); |
| ASSERT(status == ZX_OK); |
| |
| variant_vmo_[variant_index(variant)] = |
| DownCastDispatcher<VmObjectDispatcher>(&dispatcher); |
| } |