From 2702120d7938772f989791263e0be5b6581b7e27 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 18 Apr 2022 01:39:22 -0700 Subject: [PATCH] ro/os: use os primitives for MapProcessCodeMemory --- libstratosphere/include/stratosphere/os.hpp | 1 + .../os/os_process_code_memory_api.hpp | 31 ++++ .../os/impl/os_address_space_allocator.hpp | 5 +- ...ddress_space_allocator_impl.os.horizon.hpp | 2 +- .../os/impl/os_aslr_space_manager_types.hpp | 19 ++- .../os/impl/os_process_code_memory_impl.hpp | 27 ++++ ...os_process_code_memory_impl.os.horizon.cpp | 142 ++++++++++++++++++ .../source/os/os_process_code_memory.cpp | 29 ++++ .../include/vapours/results/os_results.hpp | 1 + 9 files changed, 249 insertions(+), 8 deletions(-) create mode 100644 libstratosphere/include/stratosphere/os/os_process_code_memory_api.hpp create mode 100644 libstratosphere/source/os/impl/os_process_code_memory_impl.hpp create mode 100644 libstratosphere/source/os/impl/os_process_code_memory_impl.os.horizon.cpp create mode 100644 libstratosphere/source/os/os_process_code_memory.cpp diff --git a/libstratosphere/include/stratosphere/os.hpp b/libstratosphere/include/stratosphere/os.hpp index e43a4884..bb0a0e27 100644 --- a/libstratosphere/include/stratosphere/os.hpp +++ b/libstratosphere/include/stratosphere/os.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/libstratosphere/include/stratosphere/os/os_process_code_memory_api.hpp b/libstratosphere/include/stratosphere/os/os_process_code_memory_api.hpp new file mode 100644 index 00000000..0789b373 --- /dev/null +++ b/libstratosphere/include/stratosphere/os/os_process_code_memory_api.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams::os { + + struct ProcessMemoryRegion { + u64 address; + u64 size; + }; + + Result MapProcessCodeMemory(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions); + Result UnmapProcessCodeMemory(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions); + +} diff --git a/libstratosphere/source/os/impl/os_address_space_allocator.hpp b/libstratosphere/source/os/impl/os_address_space_allocator.hpp index f76031bf..d3ff688b 100644 --- a/libstratosphere/source/os/impl/os_address_space_allocator.hpp +++ b/libstratosphere/source/os/impl/os_address_space_allocator.hpp @@ -25,10 +25,13 @@ namespace ams::os::impl { AddressAllocationResult_OutOfSpace, }; - template + template class AddressSpaceAllocatorBase { NON_COPYABLE(AddressSpaceAllocatorBase); NON_MOVEABLE(AddressSpaceAllocatorBase); + public: + using AddressType = AddressType_; + using SizeType = SizeType_; private: static constexpr size_t MaxForbiddenRegions = 2; private: diff --git a/libstratosphere/source/os/impl/os_address_space_allocator_impl.os.horizon.hpp b/libstratosphere/source/os/impl/os_address_space_allocator_impl.os.horizon.hpp index 9ba15af4..d6981188 100644 --- a/libstratosphere/source/os/impl/os_address_space_allocator_impl.os.horizon.hpp +++ b/libstratosphere/source/os/impl/os_address_space_allocator_impl.os.horizon.hpp @@ -24,7 +24,7 @@ namespace ams::os::impl { public: using Base::Base; public: - virtual bool CheckFreeSpace(uintptr_t address, size_t size) override { + virtual bool CheckFreeSpace(AddressType address, SizeType size) override { /* Query the memory. */ svc::MemoryInfo memory_info; svc::PageInfo page_info; diff --git a/libstratosphere/source/os/impl/os_aslr_space_manager_types.hpp b/libstratosphere/source/os/impl/os_aslr_space_manager_types.hpp index dc0517f6..2caaa543 100644 --- a/libstratosphere/source/os/impl/os_aslr_space_manager_types.hpp +++ b/libstratosphere/source/os/impl/os_aslr_space_manager_types.hpp @@ -38,17 +38,21 @@ namespace ams::os::impl { class AslrSpaceManagerTemplate { NON_COPYABLE(AslrSpaceManagerTemplate); NON_MOVEABLE(AslrSpaceManagerTemplate); + private: + using AddressType = typename Allocator::AddressType; + using SizeType = typename Allocator::SizeType; private: Impl m_impl; Allocator m_allocator; public: - AslrSpaceManagerTemplate() : m_impl(), m_allocator(m_impl.GetAslrSpaceBeginAddress(), m_impl.GetAslrSpaceEndAddress(), AslrSpaceGuardSize, m_impl.GetForbiddenRegions(), m_impl.GetForbiddenRegionCount()) { + template + AslrSpaceManagerTemplate(Args &&... args) : m_impl(), m_allocator(m_impl.GetAslrSpaceBeginAddress(), m_impl.GetAslrSpaceEndAddress(), AslrSpaceGuardSize, m_impl.GetForbiddenRegions(), m_impl.GetForbiddenRegionCount(), std::forward(args)...) { /* ... */ } - uintptr_t AllocateSpace(size_t size, size_t align_offset) { + AddressType AllocateSpace(SizeType size, SizeType align_offset) { /* Try to allocate a large-aligned space, if we can. */ - if (align_offset || size >= AslrSpaceLargeAlign) { + if (align_offset || (size / AslrSpaceLargeAlign) != 0) { if (auto large_align = m_allocator.AllocateSpace(size, AslrSpaceLargeAlign, align_offset & (AslrSpaceLargeAlign - 1)); large_align != 0) { return large_align; } @@ -58,14 +62,14 @@ namespace ams::os::impl { return m_allocator.AllocateSpace(size, MemoryPageSize, 0); } - bool CheckGuardSpace(uintptr_t address, size_t size) { + bool CheckGuardSpace(AddressType address, SizeType size) { return m_allocator.CheckGuardSpace(address, size, AslrSpaceGuardSize); } template - Result MapAtRandomAddress(uintptr_t *out, MapFunction map_function, UnmapFunction unmap_function, size_t size, size_t align_offset) { + Result MapAtRandomAddress(AddressType *out, MapFunction map_function, UnmapFunction unmap_function, SizeType size, SizeType align_offset) { /* Try to map up to 64 times. */ - for (int i = 0; i < 64; ++i) { + for (auto i = 0; i < 64; ++i) { /* Reserve space to map the memory. */ const uintptr_t map_address = this->AllocateSpace(size, align_offset); if (map_address == 0) { @@ -82,6 +86,9 @@ namespace ams::os::impl { if (!this->CheckGuardSpace(map_address, size)) { /* We don't have guard space, so unmap. */ unmap_function(map_address, size); + + /* NOTE: Nintendo is missing this continue; this is almost certainly a bug. */ + /* This will cause them to incorrectly return success after unmapping if guard space is not present. */ continue; } diff --git a/libstratosphere/source/os/impl/os_process_code_memory_impl.hpp b/libstratosphere/source/os/impl/os_process_code_memory_impl.hpp new file mode 100644 index 00000000..45fe4e21 --- /dev/null +++ b/libstratosphere/source/os/impl/os_process_code_memory_impl.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::os::impl { + + class ProcessCodeMemoryImpl { + public: + static Result Map(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions); + static Result Unmap(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions); + }; + +} \ No newline at end of file diff --git a/libstratosphere/source/os/impl/os_process_code_memory_impl.os.horizon.cpp b/libstratosphere/source/os/impl/os_process_code_memory_impl.os.horizon.cpp new file mode 100644 index 00000000..5a25538e --- /dev/null +++ b/libstratosphere/source/os/impl/os_process_code_memory_impl.os.horizon.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "os_process_code_memory_impl.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + namespace { + + class ProcessAddressSpaceAllocator final : public AddressSpaceAllocatorBase { + private: + using Base = AddressSpaceAllocatorBase; + private: + NativeHandle m_handle; + public: + ProcessAddressSpaceAllocator(u64 start_address, u64 end_address, SizeType guard_size, const AddressSpaceAllocatorForbiddenRegion *forbidden_regions, size_t num_forbidden_regions, NativeHandle handle) : Base(start_address, end_address, guard_size, forbidden_regions, num_forbidden_regions), m_handle(handle) { + /* ... */ + } + public: + virtual bool CheckFreeSpace(AddressType address, SizeType size) override { + /* Query the memory. */ + svc::MemoryInfo memory_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryProcessMemory(std::addressof(memory_info), std::addressof(page_info), m_handle, address)); + + return memory_info.state == svc::MemoryState_Free && address + size <= memory_info.base_address + memory_info.size; + } + }; + + using ProcessAslrSpaceManager = AslrSpaceManagerTemplate; + + size_t GetTotalProcessMemoryRegionSize(const ProcessMemoryRegion *regions, size_t num_regions) { + size_t total = 0; + + for (size_t i = 0; i < num_regions; ++i) { + total += regions[i].size; + } + + return total; + } + + } + + Result ProcessCodeMemoryImpl::Map(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions) { + /* Get the total process memory region size. */ + const size_t total_size = GetTotalProcessMemoryRegionSize(regions, num_regions); + + /* Create an aslr space manager for the process. */ + auto process_aslr_space_manager = ProcessAslrSpaceManager(handle); + + /* Map at a random address. */ + u64 mapped_address; + R_TRY(process_aslr_space_manager.MapAtRandomAddress(std::addressof(mapped_address), + [handle, regions, num_regions](u64 map_address, u64 map_size) -> Result { + AMS_UNUSED(map_size); + + /* Map the regions in order. */ + u64 mapped_size = 0; + for (size_t i = 0; i < num_regions; ++i) { + /* If we fail, unmap up to where we've mapped. */ + ON_RESULT_FAILURE { R_ABORT_UNLESS(ProcessCodeMemoryImpl::Unmap(handle, map_address, regions, i)); }; + + /* Map the current region. */ + R_TRY_CATCH(svc::MapProcessCodeMemory(handle, map_address + mapped_size, regions[i].address, regions[i].size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CATCH(svc::ResultInvalidCurrentMemory) { + /* Check if the process memory is invalid. */ + const u64 last_address = regions[i].address + regions[i].size - 1; + u64 cur_address = regions[i].address; + while (true) { + svc::MemoryInfo memory_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryProcessMemory(std::addressof(memory_info), std::addressof(page_info), handle, cur_address)); + + R_UNLESS(memory_info.state == svc::MemoryState_Normal, os::ResultInvalidProcessMemory()); + R_UNLESS(memory_info.permission == svc::MemoryPermission_ReadWrite, os::ResultInvalidProcessMemory()); + R_UNLESS(memory_info.attribute == static_cast(0), os::ResultInvalidProcessMemory()); + + cur_address = memory_info.base_address + memory_info.size; + if (cur_address > last_address) { + break; + } + } + + R_THROW(os::ResultInvalidCurrentMemoryState()); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + R_SUCCEED(); + }, + [handle, regions, num_regions](u64 map_address, u64 map_size) -> void { + AMS_UNUSED(map_size); + R_ABORT_UNLESS(ProcessCodeMemoryImpl::Unmap(handle, map_address, regions, num_regions)); + }, + total_size, + regions[0].address /* NOTE: This seems like a Nintendo bug, if the caller passed no regions. */ + )); + + /* Set the output address. */ + *out = mapped_address; + R_SUCCEED(); + } + + Result ProcessCodeMemoryImpl::Unmap(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions) { + /* Get the total process memory region size. */ + const size_t total_size = GetTotalProcessMemoryRegionSize(regions, num_regions); + + /* Unmap each region in order. */ + size_t cur_offset = total_size; + for (size_t i = 0; i < num_regions; ++i) { + /* We want to unmap in reverse order. */ + const auto &cur_region = regions[num_regions - 1 - i]; + + /* Subtract to update the current offset. */ + cur_offset -= cur_region.size; + + /* Unmap. */ + R_TRY_CATCH(svc::UnmapProcessCodeMemory(handle, process_code_address + cur_offset, cur_region.address, cur_region.size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + R_SUCCEED(); + } + +} diff --git a/libstratosphere/source/os/os_process_code_memory.cpp b/libstratosphere/source/os/os_process_code_memory.cpp new file mode 100644 index 00000000..ccd24d15 --- /dev/null +++ b/libstratosphere/source/os/os_process_code_memory.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "impl/os_process_code_memory_impl.hpp" + +namespace ams::os { + + Result MapProcessCodeMemory(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions) { + R_RETURN(::ams::os::impl::ProcessCodeMemoryImpl::Map(out, handle, regions, num_regions)); + } + + Result UnmapProcessCodeMemory(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions) { + R_RETURN(::ams::os::impl::ProcessCodeMemoryImpl::Unmap(handle, process_code_address, regions, num_regions)); + } + +} diff --git a/libvapours/include/vapours/results/os_results.hpp b/libvapours/include/vapours/results/os_results.hpp index b83389e8..680a12db 100644 --- a/libvapours/include/vapours/results/os_results.hpp +++ b/libvapours/include/vapours/results/os_results.hpp @@ -41,6 +41,7 @@ namespace ams::os { R_DEFINE_ERROR_RESULT(SessionClosedForReceive, 510); R_DEFINE_ERROR_RESULT(SessionClosedForReply, 511); R_DEFINE_ERROR_RESULT(ReceiveListBroken, 512); + R_DEFINE_ERROR_RESULT(InvalidProcessMemory, 513); R_DEFINE_ERROR_RESULT(NotImplemented, 1000); R_DEFINE_ERROR_RESULT(NotSupported, 1001);