ro/os: use os primitives for MapProcessCodeMemory

This commit is contained in:
Michael Scire 2022-04-18 01:39:22 -07:00
parent f5052b4bca
commit c2c0a2e169
15 changed files with 297 additions and 400 deletions

View file

@ -25,10 +25,13 @@ namespace ams::os::impl {
AddressAllocationResult_OutOfSpace,
};
template<std::unsigned_integral AddressType, std::unsigned_integral SizeType>
template<std::unsigned_integral AddressType_, std::unsigned_integral SizeType_>
class AddressSpaceAllocatorBase {
NON_COPYABLE(AddressSpaceAllocatorBase);
NON_MOVEABLE(AddressSpaceAllocatorBase);
public:
using AddressType = AddressType_;
using SizeType = SizeType_;
private:
static constexpr size_t MaxForbiddenRegions = 2;
private:

View file

@ -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;

View file

@ -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<typename... Args>
AslrSpaceManagerTemplate(Args &&... args) : m_impl(), m_allocator(m_impl.GetAslrSpaceBeginAddress(), m_impl.GetAslrSpaceEndAddress(), AslrSpaceGuardSize, m_impl.GetForbiddenRegions(), m_impl.GetForbiddenRegionCount(), std::forward<Args>(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<typename MapFunction, typename UnmapFunction>
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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
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);
};
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "os_process_code_memory_impl.hpp"
#include "os_aslr_space_manager.hpp"
namespace ams::os::impl {
namespace {
class ProcessAddressSpaceAllocator final : public AddressSpaceAllocatorBase<u64, u64> {
private:
using Base = AddressSpaceAllocatorBase<u64, u64>;
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<ProcessAddressSpaceAllocator, AslrSpaceManagerImpl>;
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<svc::MemoryAttribute>(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();
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#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));
}
}