diff --git a/stratosphere/libstratosphere b/stratosphere/libstratosphere index f575d0644..1d81da123 160000 --- a/stratosphere/libstratosphere +++ b/stratosphere/libstratosphere @@ -1 +1 @@ -Subproject commit f575d0644635582d83d484e7fc9691f6c17630b1 +Subproject commit 1d81da1230728993922126f756f6499b24be3eda diff --git a/stratosphere/ro/Makefile b/stratosphere/ro/Makefile index e43faaab2..17c54ea6f 100644 --- a/stratosphere/ro/Makefile +++ b/stratosphere/ro/Makefile @@ -26,7 +26,7 @@ endif #--------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR)) BUILD := build -SOURCES := source +SOURCES := source source/impl DATA := data INCLUDES := include ../../common/include EXEFS_SRC := exefs_src diff --git a/stratosphere/ro/source/impl/ro_nro_utils.cpp b/stratosphere/ro/source/impl/ro_nro_utils.cpp new file mode 100644 index 000000000..acb4b67bd --- /dev/null +++ b/stratosphere/ro/source/impl/ro_nro_utils.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2019 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 "ro_nro_utils.hpp" + +namespace sts::ro::impl { + + namespace { + + constexpr size_t MaxMapRetries = 0x200; + + } + + Result MapNro(u64 *out_base_address, Handle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) { + map::MappedCodeMemory nro_mcm(ResultRoInternalError); + map::MappedCodeMemory bss_mcm(ResultRoInternalError); + u64 base_address; + + /* Map the NRO, and map the BSS immediately after it. */ + size_t i; + for (i = 0; i < MaxMapRetries; i++) { + map::MappedCodeMemory tmp_nro_mcm(ResultRoInternalError); + R_TRY(map::MapCodeMemoryInProcess(tmp_nro_mcm, process_handle, nro_heap_address, nro_heap_size)); + base_address = tmp_nro_mcm.GetDstAddress(); + + if (bss_heap_size > 0) { + map::MappedCodeMemory tmp_bss_mcm(process_handle, base_address + nro_heap_size, bss_heap_address, bss_heap_size); + R_TRY_CATCH(tmp_bss_mcm.GetResult()) { + R_CATCH(ResultKernelInvalidMemoryState) { + continue; + } + } R_END_TRY_CATCH; + + if (!map::CanAddGuardRegionsInProcess(process_handle, base_address, nro_heap_size + bss_heap_size)) { + continue; + } + + bss_mcm = std::move(tmp_bss_mcm); + } else { + if (!map::CanAddGuardRegionsInProcess(process_handle, base_address, nro_heap_size)) { + continue; + } + } + nro_mcm = std::move(tmp_nro_mcm); + break; + } + if (i == MaxMapRetries) { + return ResultRoInsufficientAddressSpace; + } + + /* Invalidation here actually prevents them from unmapping at scope exit. */ + nro_mcm.Invalidate(); + bss_mcm.Invalidate(); + + *out_base_address = base_address; + return ResultSuccess; + } + + Result SetNroPerms(Handle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size) { + const u64 rx_offset = 0; + const u64 ro_offset = rx_offset + rx_size; + const u64 rw_offset = ro_offset + ro_size; + + R_TRY(svcSetProcessMemoryPermission(process_handle, base_address + rx_offset, rx_size, Perm_Rx)); + R_TRY(svcSetProcessMemoryPermission(process_handle, base_address + ro_offset, ro_size, Perm_R )); + R_TRY(svcSetProcessMemoryPermission(process_handle, base_address + rw_offset, rw_size, Perm_Rw)); + + return ResultSuccess; + } + + Result UnmapNro(Handle process_handle, u64 base_address, u64 nro_heap_address, u64 bss_heap_address, u64 bss_heap_size, u64 code_size, u64 rw_size) { + /* First, unmap bss. */ + if (bss_heap_size > 0) { + R_TRY(svcUnmapProcessCodeMemory(process_handle, base_address + code_size + rw_size, bss_heap_address, bss_heap_size)); + } + + /* Next, unmap .rwdata */ + if (rw_size > 0) { + R_TRY(svcUnmapProcessCodeMemory(process_handle, base_address + code_size, nro_heap_address + code_size, rw_size)); + } + + /* Finally, unmap .text + .rodata. */ + R_TRY(svcUnmapProcessCodeMemory(process_handle, base_address, nro_heap_address, code_size)); + + return ResultSuccess; + } + +} diff --git a/stratosphere/ro/source/impl/ro_nro_utils.hpp b/stratosphere/ro/source/impl/ro_nro_utils.hpp new file mode 100644 index 000000000..d0087a25e --- /dev/null +++ b/stratosphere/ro/source/impl/ro_nro_utils.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2019 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 +#include + +namespace sts::ro::impl { + + /* Utilities for working with NROs. */ + Result MapNro(u64 *out_base_address, Handle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size); + Result SetNroPerms(Handle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size); + Result UnmapNro(Handle process_handle, u64 base_address, u64 nro_heap_address, u64 bss_heap_address, u64 bss_heap_size, u64 code_size, u64 rw_size); + +} \ No newline at end of file diff --git a/stratosphere/ro/source/impl/ro_nrr_utils.cpp b/stratosphere/ro/source/impl/ro_nrr_utils.cpp new file mode 100644 index 000000000..9e07241cc --- /dev/null +++ b/stratosphere/ro/source/impl/ro_nrr_utils.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018-2019 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 "ro_nrr_utils.hpp" +#include "ro_service_impl.hpp" + +namespace sts::ro::impl { + + namespace { + + /* Helper functions. */ + Result ValidateNrrSignature(const NrrHeader *header) { + /* TODO: Implement RSA-2048 PSS..... */ + + /* TODO: Check PSS fixed-key signature. */ + if (false) { + return ResultRoNotAuthorized; + } + + /* Check TitleID pattern is valid. */ + if (!header->IsTitleIdValid()) { + return ResultRoNotAuthorized; + } + + /* TODO: Check PSS signature over hashes. */ + if (false) { + return ResultRoNotAuthorized; + } + + return ResultSuccess; + } + + Result ValidateNrr(const NrrHeader *header, u64 size, u64 title_id, ModuleType expected_type, bool enforce_type) { + /* Check magic. */ + if (!header->IsMagicValid()) { + return ResultRoInvalidNrr; + } + + /* Check size. */ + if (header->GetSize() != size) { + return ResultRoInvalidSize; + } + + /* Only perform checks if we must. */ + const bool ease_nro_restriction = ShouldEaseNroRestriction(); + if (!ease_nro_restriction) { + /* Check signature. */ + R_TRY(ValidateNrrSignature(header)); + + /* Check title id. */ + if (title_id != header->GetTitleId()) { + return ResultRoInvalidNrr; + } + + /* Check type. */ + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_700 && enforce_type) { + if (expected_type != header->GetType()) { + return ResultRoInvalidNrrType; + } + } + } + + return ResultSuccess; + } + + } + + /* Utilities for working with NRRs. */ + Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size, ModuleType expected_type, bool enforce_type) { + map::MappedCodeMemory nrr_mcm(ResultRoInternalError); + + /* First, map the NRR. */ + R_TRY(map::MapCodeMemoryInProcess(nrr_mcm, process_handle, nrr_heap_address, nrr_heap_size)); + + const u64 code_address = nrr_mcm.GetDstAddress(); + uintptr_t map_address; + if (R_FAILED(map::LocateMappableSpace(&map_address, nrr_heap_size))) { + return ResultRoInsufficientAddressSpace; + } + + /* Nintendo...does not check the return value of this map. We will check, instead of aborting if it fails. */ + map::AutoCloseMap nrr_map(map_address, process_handle, code_address, nrr_heap_size); + if (!nrr_map.IsSuccess()) { + return nrr_map.GetResult(); + } + + NrrHeader *nrr_header = reinterpret_cast(map_address); + R_TRY(ValidateNrr(nrr_header, nrr_heap_size, title_id, expected_type, enforce_type)); + + /* Invalidation here actually prevents them from unmapping at scope exit. */ + nrr_map.Invalidate(); + nrr_mcm.Invalidate(); + + *out_header = nrr_header; + *out_mapped_code_address = code_address; + return ResultSuccess; + } + + Result UnmapNrr(Handle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address) { + R_TRY(svcUnmapProcessMemory(reinterpret_cast(const_cast(header)), process_handle, mapped_code_address, nrr_heap_size)); + R_TRY(svcUnmapProcessCodeMemory(process_handle, mapped_code_address, nrr_heap_address, nrr_heap_size)); + return ResultSuccess; + } + +} diff --git a/stratosphere/ro/source/impl/ro_nrr_utils.hpp b/stratosphere/ro/source/impl/ro_nrr_utils.hpp new file mode 100644 index 000000000..5235223a0 --- /dev/null +++ b/stratosphere/ro/source/impl/ro_nrr_utils.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2019 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 +#include + +namespace sts::ro::impl { + + /* Utilities for working with NRRs. */ + Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size, ModuleType expected_type, bool enforce_type); + Result UnmapNrr(Handle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address); + +} \ No newline at end of file diff --git a/stratosphere/ro/source/impl/ro_patcher.cpp b/stratosphere/ro/source/impl/ro_patcher.cpp new file mode 100644 index 000000000..ce4db30d4 --- /dev/null +++ b/stratosphere/ro/source/impl/ro_patcher.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2019 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 "ro_patcher.hpp" + +namespace sts::ro::impl { + + namespace { + + constexpr const char *NroPatchesDirectory = "nro_patches"; + + /* NRO patches want to prevent modification of header, */ + /* but don't want to adjust offset relative to mapped location. */ + constexpr size_t NroPatchesProtectedSize = sizeof(NroHeader); + constexpr size_t NroPatchesProtectedOffset = 0; + + } + + /* Apply IPS patches. */ + void LocateAndApplyIpsPatchesToModule(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size) { + sts::patcher::LocateAndApplyIpsPatchesToModule(NroPatchesDirectory, NroPatchesProtectedSize, NroPatchesProtectedOffset, module_id, mapped_nro, mapped_size); + } + +} \ No newline at end of file diff --git a/stratosphere/ro/source/ro_patcher.hpp b/stratosphere/ro/source/impl/ro_patcher.hpp similarity index 77% rename from stratosphere/ro/source/ro_patcher.hpp rename to stratosphere/ro/source/impl/ro_patcher.hpp index ed88c7223..041023b45 100644 --- a/stratosphere/ro/source/ro_patcher.hpp +++ b/stratosphere/ro/source/impl/ro_patcher.hpp @@ -16,11 +16,11 @@ #pragma once #include -#include +#include -#include "ro_types.hpp" +namespace sts::ro::impl { -class PatchUtils { - public: - static void ApplyPatches(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size); -}; \ No newline at end of file + /* Apply IPS patches. */ + void LocateAndApplyIpsPatchesToModule(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size); + +} \ No newline at end of file diff --git a/stratosphere/ro/source/impl/ro_service_impl.cpp b/stratosphere/ro/source/impl/ro_service_impl.cpp new file mode 100644 index 000000000..b023abd76 --- /dev/null +++ b/stratosphere/ro/source/impl/ro_service_impl.cpp @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2018-2019 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 +#include +#include +#include + +#include "ro_nrr_utils.hpp" +#include "ro_nro_utils.hpp" +#include "ro_patcher.hpp" +#include "ro_service_impl.hpp" + +namespace sts::ro::impl { + + namespace { + + /* Convenience definitions. */ + constexpr size_t MaxSessions = 0x4; + constexpr size_t MaxNrrInfos = 0x40; + constexpr size_t MaxNroInfos = 0x40; + + /* Types. */ + struct Sha256Hash { + u8 hash[SHA256_HASH_SIZE]; + + bool operator==(const Sha256Hash &o) const { + return std::memcmp(this, &o, sizeof(*this)) == 0; + } + bool operator!=(const Sha256Hash &o) const { + return std::memcmp(this, &o, sizeof(*this)) != 0; + } + bool operator<(const Sha256Hash &o) const { + return std::memcmp(this, &o, sizeof(*this)) < 0; + } + bool operator>(const Sha256Hash &o) const { + return std::memcmp(this, &o, sizeof(*this)) > 0; + } + }; + static_assert(sizeof(Sha256Hash) == sizeof(Sha256Hash::hash), "Sha256Hash definition!"); + + struct NroInfo { + u64 base_address; + u64 nro_heap_address; + u64 nro_heap_size; + u64 bss_heap_address; + u64 bss_heap_size; + u64 code_size; + u64 rw_size; + ModuleId module_id; + bool in_use; + }; + + struct NrrInfo { + const NrrHeader *header; + u64 nrr_heap_address; + u64 nrr_heap_size; + u64 mapped_code_address; + bool in_use; + }; + + struct ProcessContext { + NroInfo nro_infos[MaxNroInfos]; + NrrInfo nrr_infos[MaxNrrInfos]; + Handle process_handle; + u64 process_id; + bool in_use; + + u64 GetTitleId(Handle other_process_h) const { + /* Automatically select a handle, allowing for override. */ + Handle process_h = this->process_handle; + if (other_process_h != INVALID_HANDLE) { + process_h = other_process_h; + } + + u64 title_id = 0; + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) { + /* 3.0.0+: Use svcGetInfo. */ + R_ASSERT(svcGetInfo(&title_id, InfoType_TitleId, process_h, 0)); + } else { + /* 1.0.0-2.3.0: We're not inside loader, so ask pm. */ + u64 process_id = 0; + R_ASSERT(svcGetProcessId(&process_id, process_h)); + R_ASSERT(pminfoGetTitleId(&title_id, process_id)); + } + return title_id; + } + + Result GetNrrInfoByAddress(NrrInfo **out, u64 nrr_heap_address) { + for (size_t i = 0; i < MaxNrrInfos; i++) { + if (this->nrr_infos[i].in_use && this->nrr_infos[i].nrr_heap_address == nrr_heap_address) { + if (out != nullptr) { + *out = &this->nrr_infos[i]; + } + return ResultSuccess; + } + } + return ResultRoNotRegistered; + } + + Result GetFreeNrrInfo(NrrInfo **out) { + for (size_t i = 0; i < MaxNrrInfos; i++) { + if (!this->nrr_infos[i].in_use) { + if (out != nullptr) { + *out = &this->nrr_infos[i]; + } + return ResultSuccess; + } + } + return ResultRoTooManyNrr; + } + + Result GetNroInfoByAddress(NroInfo **out, u64 nro_address) { + for (size_t i = 0; i < MaxNroInfos; i++) { + if (this->nro_infos[i].in_use && this->nro_infos[i].base_address == nro_address) { + if (out != nullptr) { + *out = &this->nro_infos[i]; + } + return ResultSuccess; + } + } + return ResultRoNotLoaded; + } + + Result GetNroInfoByModuleId(NroInfo **out, const ModuleId *module_id) { + for (size_t i = 0; i < MaxNroInfos; i++) { + if (this->nro_infos[i].in_use && std::memcmp(&this->nro_infos[i].module_id, module_id, sizeof(*module_id)) == 0) { + if (out != nullptr) { + *out = &this->nro_infos[i]; + } + return ResultSuccess; + } + } + return ResultRoNotLoaded; + } + + Result GetFreeNroInfo(NroInfo **out) { + for (size_t i = 0; i < MaxNroInfos; i++) { + if (!this->nro_infos[i].in_use) { + if (out != nullptr) { + *out = &this->nro_infos[i]; + } + return ResultSuccess; + } + } + return ResultRoTooManyNro; + } + + Result ValidateHasNroHash(const NroHeader *nro_header) const { + /* Calculate hash. */ + Sha256Hash hash; + sha256CalculateHash(&hash, nro_header, nro_header->GetSize()); + + for (size_t i = 0; i < MaxNrrInfos; i++) { + if (this->nrr_infos[i].in_use) { + const NrrHeader *nrr_header = this->nrr_infos[i].header; + const Sha256Hash *nro_hashes = reinterpret_cast(nrr_header->GetHashes()); + if (std::binary_search(nro_hashes, nro_hashes + nrr_header->GetNumHashes(), hash)) { + return ResultSuccess; + } + } + } + + return ResultRoNotAuthorized; + } + + Result ValidateNro(ModuleId *out_module_id, u64 *out_rx_size, u64 *out_ro_size, u64 *out_rw_size, u64 base_address, u64 expected_nro_size, u64 expected_bss_size) { + /* Find space to map the NRO. */ + uintptr_t map_address; + if (R_FAILED(map::LocateMappableSpace(&map_address, expected_nro_size))) { + return ResultRoInsufficientAddressSpace; + } + + /* Actually map the NRO. */ + map::AutoCloseMap nro_map(map_address, this->process_handle, base_address, expected_nro_size); + if (!nro_map.IsSuccess()) { + return nro_map.GetResult(); + } + + /* Validate header. */ + const NroHeader *header = reinterpret_cast(map_address); + if (!header->IsMagicValid()) { + return ResultRoInvalidNro; + } + + const u64 nro_size = header->GetSize(); + const u64 text_ofs = header->GetTextOffset(); + const u64 text_size = header->GetTextSize(); + const u64 ro_ofs = header->GetRoOffset(); + const u64 ro_size = header->GetRoSize(); + const u64 rw_ofs = header->GetRwOffset(); + const u64 rw_size = header->GetRwSize(); + const u64 bss_size = header->GetBssSize(); + if (nro_size != expected_nro_size || bss_size != expected_bss_size) { + return ResultRoInvalidNro; + } + if ((text_size & 0xFFF) || (ro_size & 0xFFF) || (rw_size & 0xFFF) || (bss_size & 0xFFF)) { + return ResultRoInvalidNro; + } + if (text_ofs > ro_ofs || ro_ofs > rw_ofs) { + return ResultRoInvalidNro; + } + if (text_ofs != 0 || text_ofs + text_size != ro_ofs || ro_ofs + ro_size != rw_ofs || rw_ofs + rw_size != nro_size) { + return ResultRoInvalidNro; + } + + /* Verify NRO hash. */ + R_TRY(this->ValidateHasNroHash(header)); + + /* Check if NRO has already been loaded. */ + const ModuleId *module_id = header->GetModuleId(); + if (R_SUCCEEDED(this->GetNroInfoByModuleId(nullptr, module_id))) { + return ResultRoAlreadyLoaded; + } + + /* Apply patches to NRO. */ + LocateAndApplyIpsPatchesToModule(module_id, reinterpret_cast(map_address), nro_size); + + /* Copy to output. */ + *out_module_id = *module_id; + *out_rx_size = text_size; + *out_ro_size = ro_size; + *out_rw_size = rw_size; + return ResultSuccess; + } + }; + + /* Globals. */ + ProcessContext g_process_contexts[MaxSessions] = {}; + bool g_is_development_hardware = false; + bool g_is_development_function_enabled = false; + + /* Context Helpers. */ + ProcessContext *GetContextById(size_t context_id) { + if (context_id == InvalidContextId) { + return nullptr; + } else if (context_id < MaxSessions) { + return &g_process_contexts[context_id]; + } else { + std::abort(); + } + } + + ProcessContext *GetContextByProcessId(u64 process_id) { + for (size_t i = 0; i < MaxSessions; i++) { + if (g_process_contexts[i].process_id == process_id) { + return &g_process_contexts[i]; + } + } + return nullptr; + } + + size_t AllocateContext(Handle process_handle, u64 process_id) { + /* Find a free process context. */ + for (size_t i = 0; i < MaxSessions; i++) { + ProcessContext *context = &g_process_contexts[i]; + if (!context->in_use) { + std::memset(context, 0, sizeof(*context)); + context->process_id = process_id; + context->process_handle = process_handle; + context->in_use = true; + return i; + } + } + + /* Failure to find a free context is actually an abort condition. */ + std::abort(); + } + + void FreeContext(size_t context_id) { + ProcessContext *context = GetContextById(context_id); + if (context != nullptr) { + if (context->process_handle != INVALID_HANDLE) { + for (size_t i = 0; i < MaxNrrInfos; i++) { + if (context->nrr_infos[i].in_use) { + UnmapNrr(context->process_handle, context->nrr_infos[i].header, context->nrr_infos[i].nrr_heap_address, context->nrr_infos[i].nrr_heap_size, context->nrr_infos[i].mapped_code_address); + } + } + svcCloseHandle(context->process_handle); + } + std::memset(context, 0, sizeof(*context)); + context->in_use = false; + } + } + + } + + /* Access utilities. */ + void SetDevelopmentHardware(bool is_development_hardware) { + g_is_development_hardware = is_development_hardware; + } + + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled) { + g_is_development_function_enabled = is_development_function_enabled; + } + + bool IsDevelopmentHardware() { + return g_is_development_hardware; + } + + bool IsDevelopmentFunctionEnabled() { + return g_is_development_function_enabled; + } + + bool ShouldEaseNroRestriction() { + /* Retrieve whether we should ease restrictions from set:sys. */ + bool should_ease = false; + if (R_FAILED(setsysGetSettingsItemValue("ro", "ease_nro_restriction", &should_ease, sizeof(should_ease)))) { + return false; + } + + /* Nintendo only allows easing restriction on dev, we will allow on production, as well. */ + /* should_ease &= IsDevelopmentFunctionEnabled(); */ + return should_ease; + } + + /* Context utilities. */ + Result RegisterProcess(size_t *out_context_id, Handle process_handle, u64 process_id) { + /* Validate process handle. */ + { + u64 handle_pid = 0; + + /* Validate handle is a valid process handle. */ + if (R_FAILED(svcGetProcessId(&handle_pid, process_handle))) { + return ResultRoInvalidProcess; + } + + /* Validate process id. */ + if (handle_pid != process_id) { + return ResultRoInvalidProcess; + } + } + + /* Check if a process context already exists. */ + if (GetContextByProcessId(process_id) != nullptr) { + return ResultRoInvalidSession; + } + + *out_context_id = AllocateContext(process_handle, process_id); + return ResultSuccess; + } + + Result ValidateProcess(size_t context_id, u64 process_id) { + const ProcessContext *ctx = GetContextById(context_id); + if (ctx == nullptr || ctx->process_id != process_id) { + return ResultRoInvalidProcess; + } + return ResultSuccess; + } + + void UnregisterProcess(size_t context_id) { + FreeContext(context_id); + } + + /* Service implementations. */ + Result LoadNrr(size_t context_id, Handle process_h, u64 nrr_address, u64 nrr_size, ModuleType expected_type, bool enforce_type) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + if (context == nullptr) { + std::abort(); + } + + /* Get title id. */ + const u64 title_id = context->GetTitleId(process_h); + + /* Validate address/size. */ + if (nrr_address & 0xFFF) { + return ResultRoInvalidAddress; + } + if (nrr_size == 0 || (nrr_size & 0xFFF) || !(nrr_address < nrr_address + nrr_size)) { + return ResultRoInvalidSize; + } + + /* Check we have space for a new NRR. */ + NrrInfo *nrr_info = nullptr; + R_TRY(context->GetFreeNrrInfo(&nrr_info)); + + /* Map. */ + NrrHeader *header = nullptr; + u64 mapped_code_address = 0; + R_TRY(MapAndValidateNrr(&header, &mapped_code_address, context->process_handle, title_id, nrr_address, nrr_size, expected_type, enforce_type)); + + /* Set NRR info. */ + nrr_info->in_use = true; + nrr_info->header = header; + nrr_info->nrr_heap_address = nrr_address; + nrr_info->nrr_heap_size = nrr_size; + nrr_info->mapped_code_address = mapped_code_address; + + return ResultSuccess; + } + + Result UnloadNrr(size_t context_id, u64 nrr_address) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + if (context == nullptr) { + std::abort(); + } + + /* Validate address. */ + if (nrr_address & 0xFFF) { + return ResultRoInvalidAddress; + } + + /* Check the NRR is loaded. */ + NrrInfo *nrr_info = nullptr; + R_TRY(context->GetNrrInfoByAddress(&nrr_info, nrr_address)); + + /* Unmap. */ + const NrrInfo nrr_backup = *nrr_info; + { + /* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */ + nrr_info->in_use = false; + std::memset(nrr_info, 0, sizeof(*nrr_info)); + } + return UnmapNrr(context->process_handle, nrr_backup.header, nrr_backup.nrr_heap_address, nrr_backup.nrr_heap_size, nrr_backup.mapped_code_address); + } + + Result LoadNro(u64 *out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + if (context == nullptr) { + std::abort(); + } + + /* Validate address/size. */ + if (nro_address & 0xFFF) { + return ResultRoInvalidAddress; + } + if (nro_size == 0 || (nro_size & 0xFFF) || !(nro_address < nro_address + nro_size)) { + return ResultRoInvalidSize; + } + if (bss_address & 0xFFF) { + return ResultRoInvalidAddress; + } + if ((bss_size & 0xFFF) || (bss_size > 0 && !(bss_address < bss_address + bss_size))) { + return ResultRoInvalidSize; + } + + const u64 total_size = nro_size + bss_size; + if (total_size < nro_size || total_size < bss_size) { + return ResultRoInvalidSize; + } + + /* Check we have space for a new NRO. */ + NroInfo *nro_info = nullptr; + R_TRY(context->GetFreeNroInfo(&nro_info)); + nro_info->nro_heap_address = nro_address; + nro_info->nro_heap_size = nro_size; + nro_info->bss_heap_address = bss_address; + nro_info->bss_heap_size = bss_size; + + /* Map the NRO. */ + R_TRY(MapNro(&nro_info->base_address, context->process_handle, nro_address, nro_size, bss_address, bss_size)); + + /* Validate the NRO (parsing region extents). */ + u64 rx_size, ro_size, rw_size; + R_TRY_CLEANUP(context->ValidateNro(&nro_info->module_id, &rx_size, &ro_size, &rw_size, nro_info->base_address, nro_size, bss_size), { + UnmapNro(context->process_handle, nro_info->base_address, nro_address, bss_address, bss_size, nro_size, 0); + }); + + /* Set NRO perms. */ + R_TRY_CLEANUP(SetNroPerms(context->process_handle, nro_info->base_address, rx_size, ro_size, rw_size + bss_size), { + UnmapNro(context->process_handle, nro_info->base_address, nro_address, bss_address, bss_size, rx_size + ro_size, rw_size); + }); + + nro_info->code_size = rx_size + ro_size; + nro_info->rw_size = rw_size; + nro_info->in_use = true; + *out_address = nro_info->base_address; + return ResultSuccess; + } + + Result UnloadNro(size_t context_id, u64 nro_address) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + if (context == nullptr) { + std::abort(); + } + + /* Validate address. */ + if (nro_address & 0xFFF) { + return ResultRoInvalidAddress; + } + + /* Check the NRO is loaded. */ + NroInfo *nro_info = nullptr; + R_TRY(context->GetNroInfoByAddress(&nro_info, nro_address)); + + /* Unmap. */ + const NroInfo nro_backup = *nro_info; + { + /* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */ + nro_info->in_use = false; + std::memset(nro_info, 0, sizeof(*nro_info)); + } + return UnmapNro(context->process_handle, nro_backup.base_address, nro_backup.nro_heap_address, nro_backup.bss_heap_address, nro_backup.bss_heap_size, nro_backup.code_size, nro_backup.rw_size); + } + + /* Debug service implementations. */ + Result GetProcessModuleInfo(u32 *out_count, LoaderModuleInfo *out_infos, size_t max_out_count, u64 process_id) { + size_t count = 0; + const ProcessContext *context = GetContextByProcessId(process_id); + if (context != nullptr) { + for (size_t i = 0; i < MaxNroInfos && count < max_out_count; i++) { + const NroInfo *nro_info = &context->nro_infos[i]; + if (!nro_info->in_use) { + continue; + } + + /* Just copy out the info. */ + LoaderModuleInfo *out_info = &out_infos[count++]; + memcpy(out_info->build_id, &nro_info->module_id, sizeof(nro_info->module_id)); + out_info->base_address = nro_info->base_address; + out_info->size = nro_info->nro_heap_size + nro_info->bss_heap_size; + } + } + + *out_count = static_cast(count); + return ResultSuccess; + } + +} diff --git a/stratosphere/ro/source/impl/ro_service_impl.hpp b/stratosphere/ro/source/impl/ro_service_impl.hpp new file mode 100644 index 000000000..07a707080 --- /dev/null +++ b/stratosphere/ro/source/impl/ro_service_impl.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2019 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 +#include + +namespace sts::ro::impl { + + /* Definitions. */ + constexpr size_t InvalidContextId = static_cast(-1); + + /* Access utilities. */ + void SetDevelopmentHardware(bool is_development_hardware); + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled); + bool IsDevelopmentHardware(); + bool IsDevelopmentFunctionEnabled(); + bool ShouldEaseNroRestriction(); + + /* Context utilities. */ + Result RegisterProcess(size_t *out_context_id, Handle process_handle, u64 process_id); + Result ValidateProcess(size_t context_id, u64 process_id); + void UnregisterProcess(size_t context_id); + + /* Service implementations. */ + Result LoadNrr(size_t context_id, Handle process_h, u64 nrr_address, u64 nrr_size, ModuleType expected_type, bool enforce_type); + Result UnloadNrr(size_t context_id, u64 nrr_address); + Result LoadNro(u64 *out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size); + Result UnloadNro(size_t context_id, u64 nro_address); + + /* Debug service implementations. */ + Result GetProcessModuleInfo(u32 *out_count, LoaderModuleInfo *out_infos, size_t max_out_count, u64 process_id); + +} diff --git a/stratosphere/ro/source/ro_debug_monitor.cpp b/stratosphere/ro/source/ro_debug_monitor.cpp index c0eb68697..213f65630 100644 --- a/stratosphere/ro/source/ro_debug_monitor.cpp +++ b/stratosphere/ro/source/ro_debug_monitor.cpp @@ -14,16 +14,20 @@ * along with this program. If not, see . */ +#include #include #include -#include #include "ro_debug_monitor.hpp" -#include "ro_registration.hpp" +#include "impl/ro_service_impl.hpp" -Result DebugMonitorService::GetProcessModuleInfo(Out count, OutBuffer out_infos, u64 pid) { - if (out_infos.num_elements > INT_MAX) { - return ResultRoInvalidSize; +namespace sts::ro { + + Result DebugMonitorService::GetProcessModuleInfo(Out count, OutBuffer out_infos, u64 pid) { + if (out_infos.num_elements > INT_MAX) { + return ResultRoInvalidSize; + } + return impl::GetProcessModuleInfo(count.GetPointer(), out_infos.buffer, out_infos.num_elements, pid); } - return Registration::GetProcessModuleInfo(count.GetPointer(), out_infos.buffer, out_infos.num_elements, pid); -} \ No newline at end of file + +} diff --git a/stratosphere/ro/source/ro_debug_monitor.hpp b/stratosphere/ro/source/ro_debug_monitor.hpp index d8273e2df..b675f83d9 100644 --- a/stratosphere/ro/source/ro_debug_monitor.hpp +++ b/stratosphere/ro/source/ro_debug_monitor.hpp @@ -18,16 +18,20 @@ #include #include -enum DebugMonitorServiceCmd { - Dmnt_Cmd_GetProcessModuleInfo = 0 -}; +namespace sts::ro { -class DebugMonitorService final : public IServiceObject { - private: - /* Actual commands. */ - Result GetProcessModuleInfo(Out count, OutBuffer out_infos, u64 pid); - public: - DEFINE_SERVICE_DISPATCH_TABLE { - MakeServiceCommandMeta(), - }; -}; + class DebugMonitorService final : public IServiceObject { + protected: + enum class CommandId { + GetProcessModuleInfo = 0, + }; + private: + /* Actual commands. */ + Result GetProcessModuleInfo(Out count, OutBuffer out_infos, u64 pid); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MakeServiceCommandMeta(), + }; + }; + +} diff --git a/stratosphere/ro/source/ro_main.cpp b/stratosphere/ro/source/ro_main.cpp index 486c0e4de..aa1a055d4 100644 --- a/stratosphere/ro/source/ro_main.cpp +++ b/stratosphere/ro/source/ro_main.cpp @@ -22,10 +22,11 @@ #include #include #include +#include +#include #include "ro_debug_monitor.hpp" #include "ro_service.hpp" -#include "ro_registration.hpp" extern "C" { extern u32 __start__; @@ -85,24 +86,34 @@ void __appExit(void) { setsysExit(); } +using namespace sts; + /* Helpers to create RO objects. */ -static const auto MakeRoServiceForSelf = []() { return std::make_shared(RoModuleType_ForSelf); }; -static const auto MakeRoServiceForOthers = []() { return std::make_shared(RoModuleType_ForOthers); }; +static const auto MakeRoServiceForSelf = []() { return std::make_shared(ro::ModuleType::ForSelf); }; +static const auto MakeRoServiceForOthers = []() { return std::make_shared(ro::ModuleType::ForOthers); }; int main(int argc, char **argv) { - /* Initialize. */ - Registration::Initialize(); + /* Initialize Debug config. */ + { + DoWithSmSession([]() { + R_ASSERT(splInitialize()); + }); + ON_SCOPE_EXIT { splExit(); }; + + ro::SetDevelopmentHardware(spl::IsDevelopmentHardware()); + ro::SetDevelopmentFunctionEnabled(spl::IsDevelopmentFunctionEnabled()); + } /* Static server manager. */ static auto s_server_manager = WaitableManager(1); /* Create services. */ - s_server_manager.AddWaitable(new ServiceServer("ro:dmnt", 2)); + s_server_manager.AddWaitable(new ServiceServer("ro:dmnt", 2)); /* NOTE: Official code passes 32 for ldr:ro max sessions. We will pass 2, because that's the actual limit. */ - s_server_manager.AddWaitable(new ServiceServer("ldr:ro", 2)); + s_server_manager.AddWaitable(new ServiceServer("ldr:ro", 2)); if (GetRuntimeFirmwareVersion() >= FirmwareVersion_700) { - s_server_manager.AddWaitable(new ServiceServer("ro:1", 2)); + s_server_manager.AddWaitable(new ServiceServer("ro:1", 2)); } /* Loop forever, servicing our services. */ diff --git a/stratosphere/ro/source/ro_map.cpp b/stratosphere/ro/source/ro_map.cpp deleted file mode 100644 index 9dec810c3..000000000 --- a/stratosphere/ro/source/ro_map.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 - -#include "ro_map.hpp" - -bool MapUtils::CanAddGuardRegions(Handle process_handle, u64 address, u64 size) { - MemoryInfo mem_info; - u32 page_info; - - /* Nintendo doesn't validate SVC return values at all. */ - /* TODO: Should we allow these to fail? */ - R_ASSERT(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address - 1)); - if (mem_info.type == MemType_Unmapped && address - GuardRegionSize >= mem_info.addr) { - R_ASSERT(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address + size)); - return mem_info.type == MemType_Unmapped && address + size + GuardRegionSize <= mem_info.addr + mem_info.size; - } - - return false; -} - -Result MapUtils::LocateSpaceForMap(u64 *out, u64 out_size) { - if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) { - return LocateSpaceForMapModern(out, out_size); - } else { - return LocateSpaceForMapDeprecated(out, out_size); - } -} - -Result MapUtils::MapCodeMemoryForProcess(MappedCodeMemory &out_mcm, Handle process_handle, u64 base_address, u64 size) { - if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) { - return MapCodeMemoryForProcessModern(out_mcm, process_handle, base_address, size); - } else { - if (R_FAILED(MapCodeMemoryForProcessDeprecated(out_mcm, process_handle, true, base_address, size))) { - R_TRY(MapCodeMemoryForProcessDeprecated(out_mcm, process_handle, false, base_address, size)); - } - return ResultSuccess; - } -} - -Result MapUtils::LocateSpaceForMapModern(u64 *out, u64 out_size) { - MemoryInfo mem_info = {}; - AddressSpaceInfo address_space = {}; - u32 page_info = 0; - u64 cur_base = 0, cur_end = 0; - - R_TRY(GetAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE)); - - cur_base = address_space.addspace_base; - - cur_end = cur_base + out_size; - if (cur_end <= cur_base) { - return ResultKernelOutOfMemory; - } - - while (true) { - if (address_space.heap_size && (address_space.heap_base <= cur_end - 1 && cur_base <= address_space.heap_end - 1)) { - /* If we overlap the heap region, go to the end of the heap region. */ - if (cur_base == address_space.heap_end) { - return ResultKernelOutOfMemory; - } - cur_base = address_space.heap_end; - } else if (address_space.map_size && (address_space.map_base <= cur_end - 1 && cur_base <= address_space.map_end - 1)) { - /* If we overlap the map region, go to the end of the map region. */ - if (cur_base == address_space.map_end) { - return ResultKernelOutOfMemory; - } - cur_base = address_space.map_end; - } else { - R_ASSERT(svcQueryMemory(&mem_info, &page_info, cur_base)); - if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { - *out = cur_base; - return ResultSuccess; - } - if (mem_info.addr + mem_info.size <= cur_base) { - return ResultKernelOutOfMemory; - } - cur_base = mem_info.addr + mem_info.size; - if (cur_base >= address_space.addspace_end) { - return ResultKernelOutOfMemory; - } - } - cur_end = cur_base + out_size; - if (cur_base + out_size <= cur_base) { - return ResultKernelOutOfMemory; - } - } -} - - -Result MapUtils::LocateSpaceForMapDeprecated(u64 *out, u64 out_size) { - MemoryInfo mem_info = {}; - u32 page_info = 0; - - u64 cur_base = 0x8000000ULL; - do { - R_TRY(svcQueryMemory(&mem_info, &page_info, cur_base)); - - if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { - *out = cur_base; - return ResultSuccess; - } - - const u64 mem_end = mem_info.addr + mem_info.size; - if (mem_info.type == 0x10 || mem_end < cur_base || (mem_end >> 31)) { - return ResultKernelOutOfMemory; - } - - cur_base = mem_end; - } while (true); -} - -Result MapUtils::MapCodeMemoryForProcessModern(MappedCodeMemory &out_mcm, Handle process_handle, u64 base_address, u64 size) { - AddressSpaceInfo address_space = {}; - - R_TRY(GetAddressSpaceInfo(&address_space, process_handle)); - - if (size > address_space.addspace_size) { - return ResultRoInsufficientAddressSpace; - } - - u64 try_address; - for (unsigned int i = 0; i < LocateRetryCount; i++) { - while (true) { - try_address = address_space.addspace_base + (StratosphereRandomUtils::GetRandomU64((u64)(address_space.addspace_size - size) >> 12) << 12); - if (address_space.heap_size && (address_space.heap_base <= try_address + size - 1 && try_address <= address_space.heap_end - 1)) { - continue; - } - if (address_space.map_size && (address_space.map_base <= try_address + size - 1 && try_address <= address_space.map_end - 1)) { - continue; - } - break; - } - MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size); - - R_TRY_CATCH(tmp_mcm.GetResult()) { - R_CATCH(ResultKernelInvalidMemoryState) { - continue; - } - } R_END_TRY_CATCH; - - if (!CanAddGuardRegions(process_handle, try_address, size)) { - continue; - } - - /* We're done searching. */ - out_mcm = std::move(tmp_mcm); - return ResultSuccess; - } - - return ResultRoInsufficientAddressSpace; -} - -Result MapUtils::MapCodeMemoryForProcessDeprecated(MappedCodeMemory &out_mcm, Handle process_handle, bool is_64_bit, u64 base_address, u64 size) { - u64 addspace_base, addspace_size; - if (is_64_bit) { - addspace_base = 0x8000000ULL; - addspace_size = 0x78000000ULL; - } else { - addspace_base = 0x200000ULL; - addspace_size = 0x3FE0000ULL; - } - - if (size > addspace_size) { - return ResultRoInsufficientAddressSpace; - } - - u64 try_address; - for (unsigned int i = 0; i < LocateRetryCount; i++) { - try_address = addspace_base + (StratosphereRandomUtils::GetRandomU64((u64)(addspace_size - size) >> 12) << 12); - - MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size); - - R_TRY_CATCH(tmp_mcm.GetResult()) { - R_CATCH(ResultKernelInvalidMemoryState) { - continue; - } - } R_END_TRY_CATCH; - - if (!CanAddGuardRegions(process_handle, try_address, size)) { - continue; - } - - /* We're done searching. */ - out_mcm = std::move(tmp_mcm); - return ResultSuccess; - } - - return ResultRoInsufficientAddressSpace; -} - -Result MapUtils::GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h) { - R_TRY(svcGetInfo(&out->heap_base, 4, process_h, 0)); - R_TRY(svcGetInfo(&out->heap_size, 5, process_h, 0)); - R_TRY(svcGetInfo(&out->map_base, 2, process_h, 0)); - R_TRY(svcGetInfo(&out->map_size, 3, process_h, 0)); - R_TRY(svcGetInfo(&out->addspace_base, 12, process_h, 0)); - R_TRY(svcGetInfo(&out->addspace_size, 13, process_h, 0)); - - out->heap_end = out->heap_base + out->heap_size; - out->map_end = out->map_base + out->map_size; - out->addspace_end = out->addspace_base + out->addspace_size; - return ResultSuccess; -} diff --git a/stratosphere/ro/source/ro_map.hpp b/stratosphere/ro/source/ro_map.hpp deleted file mode 100644 index d03e72342..000000000 --- a/stratosphere/ro/source/ro_map.hpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 - -class MappedCodeMemory { - private: - Handle process_handle = INVALID_HANDLE; - Result result = ResultRoInternalError; - u64 dst_address = 0; - u64 src_address = 0; - u64 size = 0; - public: - MappedCodeMemory() : process_handle(INVALID_HANDLE), result(ResultRoInternalError), dst_address(0), src_address(0), size(0) { - /* ... */ - } - - MappedCodeMemory(Handle p_h, u64 dst, u64 src, u64 sz) : process_handle(p_h), dst_address(dst), src_address(src), size(sz) { - this->result = svcMapProcessCodeMemory(this->process_handle, this->dst_address, this->src_address, this->size); - } - - ~MappedCodeMemory() { - if (this->process_handle != INVALID_HANDLE && this->size > 0 && R_SUCCEEDED(this->result)) { - R_ASSERT((this->result = svcUnmapProcessCodeMemory(this->process_handle, this->dst_address, this->src_address, this->size))); - } - } - - u64 GetDstAddress() const { - return this->dst_address; - } - - Result GetResult() const { - return this->result; - } - - bool IsSuccess() const { - return R_SUCCEEDED(this->result); - } - - void Invalidate() { - this->process_handle = INVALID_HANDLE; - } - - MappedCodeMemory &operator=(MappedCodeMemory &&o) { - this->process_handle = o.process_handle; - this->result = o.result; - this->dst_address = o.dst_address; - this->src_address = o.src_address; - this->size = o.size; - o.Invalidate(); - return *this; - } -}; - -class AutoCloseMap { - private: - Handle process_handle; - Result result; - void *mapped_address; - u64 base_address; - u64 size; - public: - AutoCloseMap(void *mp, Handle p_h, u64 ba, u64 sz) : process_handle(p_h), mapped_address(mp), base_address(ba), size(sz) { - this->result = svcMapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size); - } - AutoCloseMap(u64 mp, Handle p_h, u64 ba, u64 sz) : process_handle(p_h), mapped_address(reinterpret_cast(mp)), base_address(ba), size(sz) { - this->result = svcMapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size); - } - - ~AutoCloseMap() { - if (this->process_handle != INVALID_HANDLE && R_SUCCEEDED(this->result)) { - R_ASSERT((this->result = svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size))); - } - } - - Result GetResult() const { - return this->result; - } - - bool IsSuccess() const { - return R_SUCCEEDED(this->result); - } - - void Invalidate() { - this->process_handle = INVALID_HANDLE; - } -}; - - -class MapUtils { - public: - static constexpr size_t GuardRegionSize = 0x4000; - static constexpr size_t LocateRetryCount = 0x200; - public: - struct AddressSpaceInfo { - u64 heap_base; - u64 heap_size; - u64 heap_end; - u64 map_base; - u64 map_size; - u64 map_end; - u64 addspace_base; - u64 addspace_size; - u64 addspace_end; - }; - private: - static Result GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h); - static Result LocateSpaceForMapDeprecated(u64 *out, u64 out_size); - static Result LocateSpaceForMapModern(u64 *out, u64 out_size); - - static Result MapCodeMemoryForProcessDeprecated(MappedCodeMemory &out_mcm, Handle process_handle, bool is_64_bit, u64 base_address, u64 size); - static Result MapCodeMemoryForProcessModern(MappedCodeMemory &out_mcm, Handle process_handle, u64 base_address, u64 size); - public: - static Result LocateSpaceForMap(u64 *out, u64 out_size); - static Result MapCodeMemoryForProcess(MappedCodeMemory &out_mcm, Handle process_handle, u64 base_address, u64 size); - static bool CanAddGuardRegions(Handle process_handle, u64 address, u64 size); -}; \ No newline at end of file diff --git a/stratosphere/ro/source/ro_nrr.cpp b/stratosphere/ro/source/ro_nrr.cpp deleted file mode 100644 index 69f83e797..000000000 --- a/stratosphere/ro/source/ro_nrr.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 -#include -#include - -#include "ro_nrr.hpp" -#include "ro_registration.hpp" - -Result NrrUtils::ValidateNrrSignature(const NrrHeader *header) { - /* TODO: Implement RSA-2048 PSS..... */ - - /* TODO: Check PSS fixed-key signature. */ - if (false) { - return ResultRoNotAuthorized; - } - - /* Check TitleID pattern is valid. */ - if ((header->title_id & header->title_id_mask) != header->title_id_pattern) { - return ResultRoNotAuthorized; - } - - /* TODO: Check PSS signature over hashes. */ - if (false) { - return ResultRoNotAuthorized; - } - - return ResultSuccess; -} - -Result NrrUtils::ValidateNrr(const NrrHeader *header, u64 size, u64 title_id, RoModuleType expected_type, bool enforce_type) { - if (header->magic != MagicNrr0) { - return ResultRoInvalidNrr; - } - if (header->nrr_size != size) { - return ResultRoInvalidSize; - } - - bool ease_nro_restriction = Registration::ShouldEaseNroRestriction(); - - /* Check signature. */ - if (!ease_nro_restriction) { - R_TRY(ValidateNrrSignature(header)); - } - - /* Check title id. */ - if (title_id != header->title_id) { - if (!ease_nro_restriction) { - return ResultRoInvalidNrr; - } - } - - /* Check type. */ - if (GetRuntimeFirmwareVersion() >= FirmwareVersion_700) { - if (!enforce_type || expected_type != static_cast(header->nrr_type)) { - if (!ease_nro_restriction) { - return ResultRoInvalidNrrType; - } - } - } - - return ResultSuccess; -} \ No newline at end of file diff --git a/stratosphere/ro/source/ro_nrr.hpp b/stratosphere/ro/source/ro_nrr.hpp deleted file mode 100644 index d49ab6374..000000000 --- a/stratosphere/ro/source/ro_nrr.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 - -enum RoModuleType : u32 { - RoModuleType_ForSelf = 0, - RoModuleType_ForOthers = 1, -}; - -struct NrrHeader { - u32 magic; - u32 _0x4; - u32 _0x8; - u32 _0xC; - u64 title_id_mask; - u64 title_id_pattern; - u64 _0x20; - u64 _0x28; - u8 modulus[0x100]; - u8 fixed_key_signature[0x100]; - u8 nrr_signature[0x100]; - u64 title_id; - u32 nrr_size; - u8 nrr_type; /* 7.0.0+ */ - u8 _0x33D[3]; - u32 hash_offset; - u32 num_hashes; - u64 _0x348; -}; -static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader definition!"); - -class NrrUtils { - public: - static constexpr u32 MagicNrr0 = 0x3052524E; - private: - static Result ValidateNrrSignature(const NrrHeader *header); - public: - static Result ValidateNrr(const NrrHeader *header, u64 size, u64 title_id, RoModuleType expected_type, bool enforce_type); -}; \ No newline at end of file diff --git a/stratosphere/ro/source/ro_patcher.cpp b/stratosphere/ro/source/ro_patcher.cpp deleted file mode 100644 index 0ac52c98c..000000000 --- a/stratosphere/ro/source/ro_patcher.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 -#include -#include -#include -#include - -#include -#include "ro_patcher.hpp" -#include "ro_registration.hpp" - -/* IPS Patching adapted from Luma3DS (https://github.com/AuroraWright/Luma3DS/blob/master/sysmodules/loader/source/patcher.c) */ - -#define IPS_MAGIC "PATCH" -#define IPS_TAIL "EOF" - -#define IPS32_MAGIC "IPS32" -#define IPS32_TAIL "EEOF" - -static inline u8 HexNybbleToU8(const char nybble) { - if ('0' <= nybble && nybble <= '9') { - return nybble - '0'; - } else if ('a' <= nybble && nybble <= 'f') { - return nybble - 'a' + 0xa; - } else { - return nybble - 'A' + 0xA; - } -} - -static bool MatchesBuildId(const char *name, size_t name_len, const u8 *build_id) { - /* Validate name is hex build id. */ - for (unsigned int i = 0; i < name_len - 4; i++) { - if (isxdigit(name[i]) == 0) { - return false; - } - } - - /* Read build id from name. */ - u8 build_id_from_name[0x20] = {0}; - for (unsigned int name_ofs = 0, id_ofs = 0; name_ofs < name_len - 4; id_ofs++) { - build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]) << 4; - build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]); - } - - return memcmp(build_id, build_id_from_name, sizeof(build_id_from_name)) == 0; -} - -static void ApplyIpsPatch(u8 *mapped_nro, size_t mapped_size, bool is_ips32, FILE *f_ips) { - u8 buffer[4]; - while (fread(buffer, is_ips32 ? 4 : 3, 1, f_ips) == 1) { - if (is_ips32 && memcmp(buffer, IPS32_TAIL, 4) == 0) { - break; - } else if (!is_ips32 && memcmp(buffer, IPS_TAIL, 3) == 0) { - break; - } - - /* Offset of patch. */ - u32 patch_offset; - if (is_ips32) { - patch_offset = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; - } else { - patch_offset = (buffer[0] << 16) | (buffer[1] << 8) | (buffer[2]); - } - - /* Size of patch. */ - if (fread(buffer, 2, 1, f_ips) != 1) { - break; - } - u32 patch_size = (buffer[0] << 8) | (buffer[1]); - - /* Check for RLE encoding. */ - if (patch_size == 0) { - /* Size of RLE. */ - if (fread(buffer, 2, 1, f_ips) != 1) { - break; - } - - u32 rle_size = (buffer[0] << 8) | (buffer[1]); - - /* Value for RLE. */ - if (fread(buffer, 1, 1, f_ips) != 1) { - break; - } - - if (patch_offset < sizeof(Registration::NroHeader)) { - if (patch_offset + rle_size > sizeof(Registration::NroHeader)) { - u32 diff = sizeof(Registration::NroHeader) - patch_offset; - patch_offset += diff; - rle_size -= diff; - goto IPS_RLE_PATCH_OFFSET_WITHIN_BOUNDS; - } - } else { - IPS_RLE_PATCH_OFFSET_WITHIN_BOUNDS: - if (patch_offset + rle_size > mapped_size) { - rle_size = mapped_size - patch_offset; - } - memset(mapped_nro + patch_offset, buffer[0], rle_size); - } - } else { - if (patch_offset < sizeof(Registration::NroHeader)) { - if (patch_offset + patch_size > sizeof(Registration::NroHeader)) { - u32 diff = sizeof(Registration::NroHeader) - patch_offset; - patch_offset += diff; - patch_size -= diff; - fseek(f_ips, diff, SEEK_CUR); - goto IPS_DATA_PATCH_OFFSET_WITHIN_BOUNDS; - } else { - fseek(f_ips, patch_size, SEEK_CUR); - } - } else { - IPS_DATA_PATCH_OFFSET_WITHIN_BOUNDS: - u32 read_size = patch_size; - if (patch_offset + read_size > mapped_size) { - read_size = mapped_size - patch_offset; - } - if (fread(mapped_nro + patch_offset, read_size, 1, f_ips) != 1) { - break; - } - if (patch_size > read_size) { - fseek(f_ips, patch_size - read_size, SEEK_CUR); - } - } - } - } -} - -void PatchUtils::ApplyPatches(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size) { - /* Inspect all patches from /atmosphere/nro_patches/<*>/<*>.ips */ - char path[FS_MAX_PATH+1] = {0}; - snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/nro_patches"); - DIR *patches_dir = opendir(path); - struct dirent *pdir_ent; - if (patches_dir != NULL) { - /* Iterate over the patches directory to find patch subdirectories. */ - while ((pdir_ent = readdir(patches_dir)) != NULL) { - if (strcmp(pdir_ent->d_name, ".") == 0 || strcmp(pdir_ent->d_name, "..") == 0) { - continue; - } - snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/nro_patches/%s", pdir_ent->d_name); - DIR *patch_dir = opendir(path); - struct dirent *ent; - if (patch_dir != NULL) { - /* Iterate over the patch subdirectory to find .ips patches. */ - while ((ent = readdir(patch_dir)) != NULL) { - if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { - continue; - } - size_t name_len = strlen(ent->d_name); - if ((4 < name_len && name_len <= 0x44) && ((name_len & 1) == 0) && strcmp(ent->d_name + name_len - 4, ".ips") == 0 && MatchesBuildId(ent->d_name, name_len, module_id->build_id)) { - snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/nro_patches/%s/%s", pdir_ent->d_name, ent->d_name); - FILE *f_ips = fopen(path, "rb"); - if (f_ips != NULL) { - u8 header[5]; - if (fread(header, 5, 1, f_ips) == 1) { - if (memcmp(header, IPS_MAGIC, 5) == 0) { - ApplyIpsPatch(mapped_nro, mapped_size, false, f_ips); - } else if (memcmp(header, IPS32_MAGIC, 5) == 0) { - ApplyIpsPatch(mapped_nro, mapped_size, true, f_ips); - } - } - fclose(f_ips); - } - } - } - closedir(patch_dir); - } - } - closedir(patches_dir); - } -} \ No newline at end of file diff --git a/stratosphere/ro/source/ro_registration.cpp b/stratosphere/ro/source/ro_registration.cpp deleted file mode 100644 index 7ecff50b3..000000000 --- a/stratosphere/ro/source/ro_registration.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 -#include -#include - -#include "ro_registration.hpp" -#include "ro_map.hpp" -#include "ro_nrr.hpp" -#include "ro_patcher.hpp" - -/* Declare process contexts as global array. */ -static Registration::RoProcessContext g_process_contexts[Registration::MaxSessions] = {}; - -static bool g_is_development_hardware, g_is_development_function_enabled; - -void Registration::Initialize() { - DoWithSmSession([&]() { - R_ASSERT(splInitialize()); - }); - ON_SCOPE_EXIT { splExit(); }; - - R_ASSERT(splIsDevelopment(&g_is_development_hardware)); - - { - u64 out_val = 0; - R_ASSERT(splGetConfig(SplConfigItem_IsDebugMode, &out_val)); - g_is_development_function_enabled = out_val != 0; - } -} - -bool Registration::ShouldEaseNroRestriction() { - bool should_ease = false; - - if (R_FAILED(setsysGetSettingsItemValue("ro", "ease_nro_restriction", &should_ease, sizeof(should_ease)))) { - return false; - } - - /* Nintendo only allows easing restriction on dev, we will allow on production, as well. */ - /* should_ease &= g_is_development_function_enabled; */ - return should_ease; -} - -Result Registration::RegisterProcess(RoProcessContext **out_context, Handle process_handle, u64 process_id) { - /* Check if a process context already exists. */ - for (size_t i = 0; i < Registration::MaxSessions; i++) { - if (g_process_contexts[i].process_id == process_id) { - return ResultRoInvalidSession; - } - } - - /* Find a free process context. */ - for (size_t i = 0; i < Registration::MaxSessions; i++) { - if (!g_process_contexts[i].in_use) { - std::memset(&g_process_contexts[i], 0, sizeof(g_process_contexts[i])); - g_process_contexts[i].process_id = process_id; - g_process_contexts[i].process_handle = process_handle; - g_process_contexts[i].in_use = true; - *out_context = &g_process_contexts[i]; - return ResultSuccess; - } - } - - /* Failure to find a free context is actually an abort condition. */ - /* TODO: Should this return an unofficial error code? */ - std::abort(); -} - -void Registration::UnregisterProcess(RoProcessContext *context) { - if (context->process_handle != INVALID_HANDLE) { - for (size_t i = 0; i < Registration::MaxNrrInfos; i++) { - if (context->nrr_in_use[i]) { - UnmapNrr(context->process_handle, context->nrr_infos[i].header, context->nrr_infos[i].nrr_heap_address, context->nrr_infos[i].nrr_heap_size, context->nrr_infos[i].mapped_code_address); - } - } - svcCloseHandle(context->process_handle); - } - std::memset(context, 0, sizeof(*context)); -} - -Result Registration::GetProcessModuleInfo(u32 *out_count, LoaderModuleInfo *out_infos, size_t max_out_count, u64 process_id) { - size_t count = 0; - for (size_t sess = 0; sess < Registration::MaxSessions; sess++) { - if (g_process_contexts[sess].process_id == process_id) { - /* For convenience, helper. */ - const RoProcessContext *context = &g_process_contexts[sess]; - - for (size_t i = 0; i < Registration::MaxNroInfos && count < max_out_count; i++) { - if (!context->nro_in_use[i]) { - continue; - } - - /* Just copy out the info. */ - LoaderModuleInfo *out_info = &out_infos[count++]; - memcpy(out_info->build_id, &context->nro_infos[i].module_id, sizeof(context->nro_infos[i].module_id)); - out_info->base_address = context->nro_infos[i].base_address; - out_info->size = context->nro_infos[i].nro_heap_size + context->nro_infos[i].bss_heap_size; - } - - break; - } - } - - *out_count = static_cast(count); - return ResultSuccess; -} - -Result Registration::LoadNrr(RoProcessContext *context, u64 title_id, u64 nrr_address, u64 nrr_size, RoModuleType expected_type, bool enforce_type) { - /* Validate address/size. */ - if (nrr_address & 0xFFF) { - return ResultRoInvalidAddress; - } - if (nrr_size == 0 || (nrr_size & 0xFFF) || !(nrr_address < nrr_address + nrr_size)) { - return ResultRoInvalidSize; - } - - /* Check we have space for a new NRR. */ - size_t slot = 0; - for (slot = 0; slot < Registration::MaxNrrInfos; slot++) { - if (!context->nrr_in_use[slot]) { - break; - } - } - if (slot == Registration::MaxNrrInfos) { - return ResultRoTooManyNrr; - } - - NrrInfo *nrr_info = &context->nrr_infos[slot]; - - /* Map. */ - NrrHeader *header = nullptr; - u64 mapped_code_address = 0; - R_TRY(MapAndValidateNrr(&header, &mapped_code_address, context->process_handle, title_id, nrr_address, nrr_size, expected_type, enforce_type)); - - /* Set NRR info. */ - nrr_info->header = header; - nrr_info->nrr_heap_address = nrr_address; - nrr_info->nrr_heap_size = nrr_size; - nrr_info->mapped_code_address = mapped_code_address; - context->nrr_in_use[slot] = true; - - /* TODO. */ - return ResultSuccess; -} - -Result Registration::UnloadNrr(RoProcessContext *context, u64 nrr_address) { - /* Validate address. */ - if (nrr_address & 0xFFF) { - return ResultRoInvalidAddress; - } - - /* Check the NRR is loaded. */ - size_t slot = 0; - for (slot = 0; slot < Registration::MaxNrrInfos; slot++) { - if (!context->nrr_in_use[slot]) { - continue; - } - - if (context->nrr_infos[slot].nrr_heap_address == nrr_address) { - break; - } - } - if (slot == Registration::MaxNrrInfos) { - return ResultRoNotRegistered; - } - - /* Unmap. */ - const NrrInfo nrr_info = context->nrr_infos[slot]; - { - /* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */ - context->nrr_in_use[slot] = false; - std::memset(&context->nrr_infos[slot], 0, sizeof(context->nrr_infos[slot])); - } - return UnmapNrr(context->process_handle, nrr_info.header, nrr_info.nrr_heap_address, nrr_info.nrr_heap_size, nrr_info.mapped_code_address); -} - - -Result Registration::LoadNro(u64 *out_address, RoProcessContext *context, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { - /* Validate address/size. */ - if (nro_address & 0xFFF) { - return ResultRoInvalidAddress; - } - if (nro_size == 0 || (nro_size & 0xFFF) || !(nro_address < nro_address + nro_size)) { - return ResultRoInvalidSize; - } - if (bss_address & 0xFFF) { - return ResultRoInvalidAddress; - } - if ((bss_size & 0xFFF) || (bss_size > 0 && !(bss_address < bss_address + bss_size))) { - return ResultRoInvalidSize; - } - - const u64 total_size = nro_size + bss_size; - if (total_size < nro_size || total_size < bss_size) { - return ResultRoInvalidSize; - } - - /* Check we have space for a new NRO. */ - size_t slot = 0; - for (slot = 0; slot < Registration::MaxNroInfos; slot++) { - if (!context->nro_in_use[slot]) { - break; - } - } - if (slot == Registration::MaxNroInfos) { - return ResultRoTooManyNro; - } - - NroInfo *nro_info = &context->nro_infos[slot]; - nro_info->nro_heap_address = nro_address; - nro_info->nro_heap_size = nro_size; - nro_info->bss_heap_address = bss_address; - nro_info->bss_heap_size = bss_size; - - /* Map the NRO. */ - R_TRY(MapNro(&nro_info->base_address, context->process_handle, nro_address, nro_size, bss_address, bss_size)); - - /* Validate the NRO (parsing region extents). */ - u64 rx_size, ro_size, rw_size; - R_TRY_CLEANUP(ValidateNro(&nro_info->module_id, &rx_size, &ro_size, &rw_size, context, nro_info->base_address, nro_size, bss_size), { - UnmapNro(context->process_handle, nro_info->base_address, nro_address, bss_address, bss_size, nro_size, 0); - }); - - /* Set NRO perms. */ - R_TRY_CLEANUP(SetNroPerms(context->process_handle, nro_info->base_address, rx_size, ro_size, rw_size + bss_size), { - UnmapNro(context->process_handle, nro_info->base_address, nro_address, bss_address, bss_size, rx_size + ro_size, rw_size); - }); - - nro_info->code_size = rx_size + ro_size; - nro_info->rw_size = rw_size; - context->nro_in_use[slot] = true; - *out_address = nro_info->base_address; - return ResultSuccess; -} - -bool Registration::IsNroHashPresent(RoProcessContext *context, const Sha256Hash *hash) { - for (size_t i = 0; i < Registration::MaxNrrInfos; i++) { - if (context->nrr_in_use[i]) { - const Sha256Hash *nro_hashes = reinterpret_cast(reinterpret_cast(context->nrr_infos[i].header) + context->nrr_infos[i].header->hash_offset); - if (std::binary_search(nro_hashes, nro_hashes + context->nrr_infos[i].header->num_hashes, *hash)) { - return true; - } - } - } - return false; -} - -Result Registration::ValidateNro(ModuleId *out_module_id, u64 *out_rx_size, u64 *out_ro_size, u64 *out_rw_size, RoProcessContext *context, u64 base_address, u64 nro_size, u64 bss_size) { - /* Find space to map the NRO. */ - u64 map_address; - if (R_FAILED(MapUtils::LocateSpaceForMap(&map_address, nro_size))) { - return ResultRoInsufficientAddressSpace; - } - - /* Actually map the NRO. */ - AutoCloseMap nro_map(map_address, context->process_handle, base_address, nro_size); - if (!nro_map.IsSuccess()) { - return nro_map.GetResult(); - } - - /* Validate header. */ - const Registration::NroHeader *header = reinterpret_cast(map_address); - if (header->magic != MagicNro0) { - return ResultRoInvalidNro; - } - if (header->nro_size != nro_size || header->bss_size != bss_size) { - return ResultRoInvalidNro; - } - if ((header->text_size & 0xFFF) || (header->ro_size & 0xFFF) || (header->rw_size & 0xFFF) || (header->bss_size & 0xFFF)) { - return ResultRoInvalidNro; - } - if (header->text_offset > header->ro_offset || header->ro_offset > header->rw_offset) { - return ResultRoInvalidNro; - } - if (header->text_offset != 0 || header->text_offset + header->text_size != header->ro_offset || header->ro_offset + header->ro_size != header->rw_offset || header->rw_offset + header->rw_size != header->nro_size) { - return ResultRoInvalidNro; - } - - /* Verify NRO hash. */ - { - Sha256Hash hash; - sha256CalculateHash(&hash, header, nro_size); - if (!IsNroHashPresent(context, &hash)) { - return ResultRoNotAuthorized; - } - } - - ModuleId module_id; - std::memcpy(&module_id, header->build_id, sizeof(module_id)); - - /* Check if NRO has already been loaded. */ - for (size_t i = 0; i < Registration::MaxNroInfos; i++) { - if (context->nro_in_use[i]) { - if (std::memcmp(&context->nro_infos[i].module_id, &module_id, sizeof(module_id)) == 0) { - return ResultRoAlreadyLoaded; - } - } - } - - /* Apply patches to NRO. */ - PatchUtils::ApplyPatches(&module_id, reinterpret_cast(map_address), nro_size); - - *out_module_id = module_id; - *out_rx_size = header->text_size; - *out_ro_size = header->ro_size; - *out_rw_size = header->rw_size; - return ResultSuccess; -} - -Result Registration::SetNroPerms(Handle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size) { - const u64 rx_offset = 0; - const u64 ro_offset = rx_offset + rx_size; - const u64 rw_offset = ro_offset + ro_size; - - R_TRY(svcSetProcessMemoryPermission(process_handle, base_address + rx_offset, rx_size, Perm_Rx)); - R_TRY(svcSetProcessMemoryPermission(process_handle, base_address + ro_offset, ro_size, Perm_R )); - R_TRY(svcSetProcessMemoryPermission(process_handle, base_address + rw_offset, rw_size, Perm_Rw)); - - return ResultSuccess; -} - -Result Registration::UnloadNro(RoProcessContext *context, u64 nro_address) { - /* Validate address. */ - if (nro_address & 0xFFF) { - return ResultRoInvalidAddress; - } - - /* Check the NRO is loaded. */ - size_t slot = 0; - for (slot = 0; slot < Registration::MaxNroInfos; slot++) { - if (!context->nro_in_use[slot]) { - continue; - } - - if (context->nro_infos[slot].base_address == nro_address) { - break; - } - } - if (slot == Registration::MaxNroInfos) { - return ResultRoNotLoaded; - } - - /* Unmap. */ - const NroInfo nro_info = context->nro_infos[slot]; - { - /* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */ - context->nro_in_use[slot] = false; - std::memset(&context->nro_infos[slot], 0, sizeof(context->nro_infos[slot])); - } - return UnmapNro(context->process_handle, nro_info.base_address, nro_info.nro_heap_address, nro_info.bss_heap_address, nro_info.bss_heap_size, nro_info.code_size, nro_info.rw_size); -} - -Result Registration::MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size, RoModuleType expected_type, bool enforce_type) { - MappedCodeMemory nrr_mcm; - - /* First, map the NRR. */ - R_TRY(MapUtils::MapCodeMemoryForProcess(nrr_mcm, process_handle, nrr_heap_address, nrr_heap_size)); - - const u64 code_address = nrr_mcm.GetDstAddress(); - u64 map_address; - if (R_FAILED(MapUtils::LocateSpaceForMap(&map_address, nrr_heap_size))) { - return ResultRoInsufficientAddressSpace; - } - - /* Nintendo...does not check the return value of this map. We will check, instead of aborting if it fails. */ - AutoCloseMap nrr_map(map_address, process_handle, code_address, nrr_heap_size); - if (!nrr_map.IsSuccess()) { - return nrr_map.GetResult(); - } - - NrrHeader *nrr_header = reinterpret_cast(map_address); - R_TRY(NrrUtils::ValidateNrr(nrr_header, nrr_heap_size, title_id, expected_type, enforce_type)); - - /* Invalidation here actually prevents them from unmapping at scope exit. */ - nrr_map.Invalidate(); - nrr_mcm.Invalidate(); - - *out_header = nrr_header; - *out_mapped_code_address = code_address; - return ResultSuccess; -} - -Result Registration::UnmapNrr(Handle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address) { - R_TRY(svcUnmapProcessMemory((void *)header, process_handle, mapped_code_address, nrr_heap_size)); - return svcUnmapProcessCodeMemory(process_handle, mapped_code_address, nrr_heap_address, nrr_heap_size); -} - -Result Registration::MapNro(u64 *out_base_address, Handle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) { - MappedCodeMemory nro_mcm; - MappedCodeMemory bss_mcm; - u64 base_address; - - /* Map the NRO, and map the BSS immediately after it. */ - size_t i = 0; - for (i = 0; i < MapUtils::LocateRetryCount; i++) { - MappedCodeMemory tmp_nro_mcm; - R_TRY(MapUtils::MapCodeMemoryForProcess(tmp_nro_mcm, process_handle, nro_heap_address, nro_heap_size)); - base_address = tmp_nro_mcm.GetDstAddress(); - - if (bss_heap_size > 0) { - MappedCodeMemory tmp_bss_mcm(process_handle, base_address + nro_heap_size, bss_heap_address, bss_heap_size); - R_TRY_CATCH(tmp_bss_mcm.GetResult()) { - R_CATCH(ResultKernelInvalidMemoryState) { - continue; - } - } R_END_TRY_CATCH; - - if (!MapUtils::CanAddGuardRegions(process_handle, base_address, nro_heap_size + bss_heap_size)) { - continue; - } - - bss_mcm = std::move(tmp_bss_mcm); - } else { - if (!MapUtils::CanAddGuardRegions(process_handle, base_address, nro_heap_size)) { - continue; - } - } - nro_mcm = std::move(tmp_nro_mcm); - break; - } - if (i == MapUtils::LocateRetryCount) { - return ResultRoInsufficientAddressSpace; - } - - /* Invalidation here actually prevents them from unmapping at scope exit. */ - nro_mcm.Invalidate(); - bss_mcm.Invalidate(); - - *out_base_address = base_address; - return ResultSuccess; -} - -Result Registration::UnmapNro(Handle process_handle, u64 base_address, u64 nro_heap_address, u64 bss_heap_address, u64 bss_heap_size, u64 code_size, u64 rw_size) { - /* First, unmap bss. */ - if (bss_heap_size > 0) { - R_TRY(svcUnmapProcessCodeMemory(process_handle, base_address + code_size + rw_size, bss_heap_address, bss_heap_size)); - } - - /* Next, unmap .rwdata */ - if (rw_size > 0) { - R_TRY(svcUnmapProcessCodeMemory(process_handle, base_address + code_size, nro_heap_address + code_size, rw_size)); - } - - /* Finally, unmap .text + .rodata. */ - R_TRY(svcUnmapProcessCodeMemory(process_handle, base_address, nro_heap_address, code_size)); - - return ResultSuccess; -} diff --git a/stratosphere/ro/source/ro_registration.hpp b/stratosphere/ro/source/ro_registration.hpp deleted file mode 100644 index f416534f1..000000000 --- a/stratosphere/ro/source/ro_registration.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 - -#include "ro_nrr.hpp" -#include "ro_types.hpp" - -class Registration { - public: - /* NOTE: 2 ldr:ro, 2 ro:1. Nintendo only actually supports 2 total, but we'll be a little more generous. */ - static constexpr size_t MaxSessions = 0x4; - static constexpr size_t MaxNrrInfos = 0x40; - static constexpr size_t MaxNroInfos = 0x40; - - static constexpr u32 MagicNro0 = 0x304F524E; - public: - struct NroHeader { - u32 entrypoint_insn; - u32 mod_offset; - u64 padding; - u32 magic; - u32 _0x14; - u32 nro_size; - u32 _0x1C; - u32 text_offset; - u32 text_size; - u32 ro_offset; - u32 ro_size; - u32 rw_offset; - u32 rw_size; - u32 bss_size; - u32 _0x3C; - unsigned char build_id[0x20]; - u8 _0x60[0x20]; - }; - static_assert(sizeof(NroHeader) == 0x80, "NroHeader definition!"); - - struct NroInfo { - u64 base_address; - u64 nro_heap_address; - u64 nro_heap_size; - u64 bss_heap_address; - u64 bss_heap_size; - u64 code_size; - u64 rw_size; - ModuleId module_id; - }; - struct NrrInfo { - NrrHeader *header; - u64 nrr_heap_address; - u64 nrr_heap_size; - u64 mapped_code_address; - }; - struct RoProcessContext { - bool nro_in_use[MaxNroInfos]; - bool nrr_in_use[MaxNrrInfos]; - NroInfo nro_infos[MaxNroInfos]; - NrrInfo nrr_infos[MaxNrrInfos]; - Handle process_handle; - u64 process_id; - bool in_use; - }; - private: - static Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size, RoModuleType expected_type, bool enforce_type); - static Result UnmapNrr(Handle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address); - static bool IsNroHashPresent(RoProcessContext *context, const Sha256Hash *hash); - - static Result MapNro(u64 *out_base_address, Handle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size); - static Result ValidateNro(ModuleId *out_module_id, u64 *out_rx_size, u64 *out_ro_size, u64 *out_rw_size, RoProcessContext *context, u64 base_address, u64 nro_size, u64 bss_size); - static Result SetNroPerms(Handle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size); - static Result UnmapNro(Handle process_handle, u64 base_address, u64 nro_heap_address, u64 bss_heap_address, u64 bss_heap_size, u64 code_size, u64 rw_size); - public: - static void Initialize(); - static bool ShouldEaseNroRestriction(); - - static Result RegisterProcess(RoProcessContext **out_context, Handle process_handle, u64 process_id); - static void UnregisterProcess(RoProcessContext *context); - - static Result LoadNrr(RoProcessContext *context, u64 title_id, u64 nrr_address, u64 nrr_size, RoModuleType expected_type, bool enforce_type); - static Result UnloadNrr(RoProcessContext *context, u64 nrr_address); - static Result LoadNro(u64 *out_address, RoProcessContext *context, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size); - static Result UnloadNro(RoProcessContext *context, u64 nro_address); - - static Result GetProcessModuleInfo(u32 *out_count, LoaderModuleInfo *out_infos, size_t max_out_count, u64 process_id); -}; \ No newline at end of file diff --git a/stratosphere/ro/source/ro_service.cpp b/stratosphere/ro/source/ro_service.cpp index d03562f3e..4bfa845ec 100644 --- a/stratosphere/ro/source/ro_service.cpp +++ b/stratosphere/ro/source/ro_service.cpp @@ -20,83 +20,53 @@ #include #include "ro_service.hpp" -#include "ro_registration.hpp" +#include "impl/ro_service_impl.hpp" -RelocatableObjectsService::~RelocatableObjectsService() { - if (this->IsInitialized()) { - Registration::UnregisterProcess(this->context); - this->context = nullptr; +namespace sts::ro { + + void SetDevelopmentHardware(bool is_development_hardware) { + impl::SetDevelopmentHardware(is_development_hardware); } + + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled) { + impl::SetDevelopmentFunctionEnabled(is_development_function_enabled); + } + + Service::Service(ModuleType t) : context_id(impl::InvalidContextId), type(t) { + /* ... */ + } + + Service::~Service() { + impl::UnregisterProcess(this->context_id); + } + + Result Service::LoadNro(Out load_address, PidDescriptor pid_desc, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { + R_TRY(impl::ValidateProcess(this->context_id, pid_desc.pid)); + return impl::LoadNro(load_address.GetPointer(), this->context_id, nro_address, nro_size, bss_address, bss_size); + } + + Result Service::UnloadNro(PidDescriptor pid_desc, u64 nro_address) { + R_TRY(impl::ValidateProcess(this->context_id, pid_desc.pid)); + return impl::UnloadNro(this->context_id, nro_address); + } + + Result Service::LoadNrr(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size) { + R_TRY(impl::ValidateProcess(this->context_id, pid_desc.pid)); + return impl::LoadNrr(this->context_id, INVALID_HANDLE, nrr_address, nrr_size, ModuleType::ForSelf, true); + } + + Result Service::UnloadNrr(PidDescriptor pid_desc, u64 nrr_address) { + R_TRY(impl::ValidateProcess(this->context_id, pid_desc.pid)); + return impl::UnloadNrr(this->context_id, nrr_address); + } + + Result Service::Initialize(PidDescriptor pid_desc, CopiedHandle process_h) { + return impl::RegisterProcess(&this->context_id, process_h.handle, pid_desc.pid); + } + + Result Service::LoadNrrEx(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size, CopiedHandle process_h) { + R_TRY(impl::ValidateProcess(this->context_id, pid_desc.pid)); + return impl::LoadNrr(this->context_id, process_h.handle, nrr_address, nrr_size, this->type, this->type == ModuleType::ForOthers); + } + } - -bool RelocatableObjectsService::IsProcessIdValid(u64 process_id) { - if (!this->IsInitialized()) { - return false; - } - - return this->context->process_id == process_id; -} - -u64 RelocatableObjectsService::GetTitleId(Handle process_handle) { - u64 title_id = 0; - if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) { - /* 3.0.0+: Use svcGetInfo. */ - R_ASSERT(svcGetInfo(&title_id, 18, process_handle, 0)); - } else { - /* 1.0.0-2.3.0: We're not inside loader, so ask pm. */ - u64 process_id = 0; - R_ASSERT(svcGetProcessId(&process_id, process_handle)); - R_ASSERT(pminfoGetTitleId(&title_id, process_id)); - } - return title_id; -} - -Result RelocatableObjectsService::LoadNro(Out load_address, PidDescriptor pid_desc, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { - if (!this->IsProcessIdValid(pid_desc.pid)) { - return ResultRoInvalidProcess; - } - - return Registration::LoadNro(load_address.GetPointer(), this->context, nro_address, nro_size, bss_address, bss_size); -} - -Result RelocatableObjectsService::UnloadNro(PidDescriptor pid_desc, u64 nro_address) { - if (!this->IsProcessIdValid(pid_desc.pid)) { - return ResultRoInvalidProcess; - } - - return Registration::UnloadNro(this->context, nro_address); -} - -Result RelocatableObjectsService::LoadNrr(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size) { - if (!this->IsProcessIdValid(pid_desc.pid)) { - return ResultRoInvalidProcess; - } - - return Registration::LoadNrr(this->context, GetTitleId(this->context->process_handle), nrr_address, nrr_size, RoModuleType_ForSelf, true); -} - -Result RelocatableObjectsService::UnloadNrr(PidDescriptor pid_desc, u64 nrr_address) { - if (!this->IsProcessIdValid(pid_desc.pid)) { - return ResultRoInvalidProcess; - } - - return Registration::UnloadNrr(this->context, nrr_address); -} - -Result RelocatableObjectsService::Initialize(PidDescriptor pid_desc, CopiedHandle process_h) { - /* Validate the input pid/process handle. */ - u64 handle_pid = 0; - if (R_FAILED(svcGetProcessId(&handle_pid, process_h.handle)) || handle_pid != pid_desc.pid) { - return ResultRoInvalidProcess; - } - - return Registration::RegisterProcess(&this->context, process_h.handle, pid_desc.pid); -} - -Result RelocatableObjectsService::LoadNrrEx(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size, CopiedHandle process_h) { - if (!this->IsProcessIdValid(pid_desc.pid)) { - return ResultRoInvalidProcess; - } - - return Registration::LoadNrr(this->context, GetTitleId(process_h.handle), nrr_address, nrr_size, this->type, this->type == RoModuleType_ForOthers); -} \ No newline at end of file diff --git a/stratosphere/ro/source/ro_service.hpp b/stratosphere/ro/source/ro_service.hpp index aa693011c..5e2914610 100644 --- a/stratosphere/ro/source/ro_service.hpp +++ b/stratosphere/ro/source/ro_service.hpp @@ -18,48 +18,48 @@ #include #include +#include -#include "ro_registration.hpp" +namespace sts::ro { -enum RoServiceCmd { - Ro_Cmd_LoadNro = 0, - Ro_Cmd_UnloadNro = 1, - Ro_Cmd_LoadNrr = 2, - Ro_Cmd_UnloadNrr = 3, - Ro_Cmd_Initialize = 4, - Ro_Cmd_LoadNrrEx = 10, -}; + /* Access utilities. */ + void SetDevelopmentHardware(bool is_development_hardware); + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled); -class RelocatableObjectsService final : public IServiceObject { - private: - Registration::RoProcessContext *context = nullptr; - RoModuleType type; - public: - explicit RelocatableObjectsService(RoModuleType t) : type(t) { - /* ... */ - } - virtual ~RelocatableObjectsService() override; - private: - bool IsInitialized() const { - return this->context != nullptr; - } - bool IsProcessIdValid(u64 process_id); - static u64 GetTitleId(Handle process_handle); - private: - /* Actual commands. */ - Result LoadNro(Out load_address, PidDescriptor pid_desc, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size); - Result UnloadNro(PidDescriptor pid_desc, u64 nro_address); - Result LoadNrr(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size); - Result UnloadNrr(PidDescriptor pid_desc, u64 nrr_address); - Result Initialize(PidDescriptor pid_desc, CopiedHandle process_h); - Result LoadNrrEx(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size, CopiedHandle process_h); - public: - DEFINE_SERVICE_DISPATCH_TABLE { - MakeServiceCommandMeta(), - MakeServiceCommandMeta(), - MakeServiceCommandMeta(), - MakeServiceCommandMeta(), - MakeServiceCommandMeta(), - MakeServiceCommandMeta(), - }; -}; + class Service final : public IServiceObject { + protected: + enum class CommandId { + LoadNro = 0, + UnloadNro = 1, + LoadNrr = 2, + UnloadNrr = 3, + Initialize = 4, + LoadNrrEx = 10, + }; + private: + size_t context_id; + ModuleType type; + public: + explicit Service(ModuleType t); + virtual ~Service() override; + private: + /* Actual commands. */ + Result LoadNro(Out load_address, PidDescriptor pid_desc, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size); + Result UnloadNro(PidDescriptor pid_desc, u64 nro_address); + Result LoadNrr(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size); + Result UnloadNrr(PidDescriptor pid_desc, u64 nrr_address); + Result Initialize(PidDescriptor pid_desc, CopiedHandle process_h); + Result LoadNrrEx(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size, CopiedHandle process_h); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MakeServiceCommandMeta(), + MakeServiceCommandMeta(), + MakeServiceCommandMeta(), + MakeServiceCommandMeta(), + MakeServiceCommandMeta(), + MakeServiceCommandMeta(), + }; + + }; + +} diff --git a/stratosphere/ro/source/ro_types.hpp b/stratosphere/ro/source/ro_types.hpp deleted file mode 100644 index b6473adb1..000000000 --- a/stratosphere/ro/source/ro_types.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 - -struct ModuleId { - u8 build_id[0x20]; -}; -static_assert(sizeof(ModuleId) == sizeof(LoaderModuleInfo::build_id), "ModuleId definition!"); - -struct Sha256Hash { - u8 hash[0x20]; - - bool operator==(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) == 0; - } - bool operator!=(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) != 0; - } - bool operator<(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) < 0; - } - bool operator>(const Sha256Hash &o) const { - return std::memcmp(this, &o, sizeof(*this)) > 0; - } -}; -static_assert(sizeof(Sha256Hash) == sizeof(Sha256Hash::hash), "Sha256Hash definition!");