From 51311a7332eaa26cb44fa561c7e6024547b1d74c Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Tue, 21 Jul 2020 22:13:16 -0700 Subject: [PATCH] kern: add KProcess::Initialize (for non-kip processes) --- .../arch/arm64/kern_k_process_page_table.hpp | 4 + .../nintendo/nx/kern_k_system_control.hpp | 4 +- .../mesosphere/kern_k_capabilities.hpp | 2 + .../mesosphere/kern_k_memory_manager.hpp | 11 +- .../mesosphere/kern_k_page_table_base.hpp | 2 + .../nintendo/nx/kern_k_system_control.cpp | 10 +- .../source/kern_k_capabilities.cpp | 39 ++++++ .../source/kern_k_memory_manager.cpp | 22 +++- .../source/kern_k_page_table_base.cpp | 30 ++++- .../libmesosphere/source/kern_k_process.cpp | 116 +++++++++++++++++- 10 files changed, 232 insertions(+), 8 deletions(-) diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp index 406812e98..c4fbe143d 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp @@ -100,6 +100,10 @@ namespace ams::kern::arch::arm64 { return this->page_table.MapPages(out_addr, num_pages, state, perm); } + Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm) { + return this->page_table.MapPages(address, num_pages, state, perm); + } + Result UnmapPages(KProcessAddress addr, size_t num_pages, KMemoryState state) { return this->page_table.UnmapPages(addr, num_pages, state); } diff --git a/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_system_control.hpp b/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_system_control.hpp index 2f51e5d88..080cc8c32 100644 --- a/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_system_control.hpp +++ b/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_system_control.hpp @@ -68,8 +68,10 @@ namespace ams::kern::board::nintendo::nx { /* User access. */ static void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args); - /* Constant calculations. */ + /* Secure Memory. */ static size_t CalculateRequiredSecureMemorySize(size_t size, u32 pool); + static Result AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool); + static void FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool); }; } \ No newline at end of file diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp index 3022ea502..6dff162d6 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp @@ -253,10 +253,12 @@ namespace ams::kern { Result SetCapability(const util::BitPack32 cap, u32 &set_flags, u32 &set_svc, KProcessPageTable *page_table); Result SetCapabilities(const u32 *caps, s32 num_caps, KProcessPageTable *page_table); + Result SetCapabilities(svc::KUserPointer user_caps, s32 num_caps, KProcessPageTable *page_table); public: constexpr KCapabilities() = default; Result Initialize(const u32 *caps, s32 num_caps, KProcessPageTable *page_table); + Result Initialize(svc::KUserPointer user_caps, s32 num_caps, KProcessPageTable *page_table); constexpr u64 GetCoreMask() const { return this->core_mask; } constexpr u64 GetPriorityMask() const { return this->priority_mask; } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_memory_manager.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_memory_manager.hpp index 468469e2c..c59a39ca5 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_memory_manager.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_memory_manager.hpp @@ -53,6 +53,12 @@ namespace ams::kern { class Impl { private: using RefCount = u16; + public: + static size_t CalculateMetadataOverheadSize(size_t region_size); + + static constexpr size_t CalculateOptimizedProcessOverheadSize(size_t region_size) { + return (util::AlignUp((region_size / PageSize), BITSIZEOF(u64)) / BITSIZEOF(u64)) * sizeof(u64); + } private: KPageHeap heap; RefCount *page_reference_counts; @@ -68,6 +74,7 @@ namespace ams::kern { KVirtualAddress AllocateBlock(s32 index, bool random) { return this->heap.AllocateBlock(index, random); } void Free(KVirtualAddress addr, size_t num_pages) { this->heap.Free(addr, num_pages); } + void InitializeOptimizedMemory() { std::memset(GetVoidPointer(this->metadata_region), 0, CalculateOptimizedProcessOverheadSize(this->heap.GetSize())); } void TrackAllocationForOptimizedProcess(KVirtualAddress block, size_t num_pages); constexpr size_t GetSize() const { return this->heap.GetSize(); } @@ -127,8 +134,6 @@ namespace ams::kern { this->Free(this->heap.GetAddress() + free_start * PageSize, free_count); } } - public: - static size_t CalculateMetadataOverheadSize(size_t region_size); }; private: KLightLock pool_locks[Pool_Count]; @@ -165,6 +170,8 @@ namespace ams::kern { NOINLINE void Initialize(KVirtualAddress metadata_region, size_t metadata_region_size); + NOINLINE Result InitializeOptimizedMemory(u64 process_id, Pool pool); + NOINLINE KVirtualAddress AllocateContinuous(size_t num_pages, size_t align_pages, u32 option); NOINLINE Result Allocate(KPageGroup *out, size_t num_pages, u32 option); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp index dd9372713..3769a2dc1 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp @@ -294,7 +294,9 @@ namespace ams::kern { return this->MapPages(out_addr, num_pages, PageSize, Null, false, this->GetRegionAddress(state), this->GetRegionSize(state) / PageSize, state, perm); } + Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm); Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state); + Result MapPageGroup(KProcessAddress *out_addr, const KPageGroup &pg, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm); Result MapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state, KMemoryPermission perm); Result UnmapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state); diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp index a56809cd9..001f525e6 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp @@ -475,7 +475,7 @@ namespace ams::kern::board::nintendo::nx { } } - /* Constant calculations. */ + /* Secure Memory. */ size_t KSystemControl::CalculateRequiredSecureMemorySize(size_t size, u32 pool) { if (pool == KMemoryManager::Pool_Applet) { return 0; @@ -483,4 +483,12 @@ namespace ams::kern::board::nintendo::nx { return size; } + Result KSystemControl::AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool) { + MESOSPHERE_UNIMPLEMENTED(); + } + + void KSystemControl::FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool) { + MESOSPHERE_UNIMPLEMENTED(); + } + } \ No newline at end of file diff --git a/libraries/libmesosphere/source/kern_k_capabilities.cpp b/libraries/libmesosphere/source/kern_k_capabilities.cpp index d54d3b5cc..e8e5d2a86 100644 --- a/libraries/libmesosphere/source/kern_k_capabilities.cpp +++ b/libraries/libmesosphere/source/kern_k_capabilities.cpp @@ -34,6 +34,14 @@ namespace ams::kern { return this->SetCapabilities(caps, num_caps, page_table); } + Result KCapabilities::Initialize(svc::KUserPointer user_caps, s32 num_caps, KProcessPageTable *page_table) { + /* We're initializing a user process. */ + /* Most fields have already been cleared by our constructor. */ + + /* Parse the user capabilities array. */ + return this->SetCapabilities(user_caps, num_caps, page_table); + } + Result KCapabilities::SetCorePriorityCapability(const util::BitPack32 cap) { /* We can't set core/priority if we've already set them. */ R_UNLESS(this->core_mask == 0, svc::ResultInvalidArgument()); @@ -258,4 +266,35 @@ namespace ams::kern { return ResultSuccess(); } + Result KCapabilities::SetCapabilities(svc::KUserPointer user_caps, s32 num_caps, KProcessPageTable *page_table) { + u32 set_flags = 0, set_svc = 0; + + for (s32 i = 0; i < num_caps; i++) { + /* Read the cap from userspace. */ + u32 cap0; + R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap0), i)); + + const util::BitPack32 cap = { cap0 }; + if (GetCapabilityType(cap) == CapabilityType::MapRange) { + /* Check that the pair cap exists. */ + R_UNLESS((++i) < num_caps, svc::ResultInvalidCombination()); + + /* Read the second cap from userspace. */ + u32 cap1; + R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap1), i)); + + /* Check the pair cap is a map range cap. */ + const util::BitPack32 size_cap = { cap1 }; + R_UNLESS(GetCapabilityType(size_cap) == CapabilityType::MapRange, svc::ResultInvalidCombination()); + + /* Map the range. */ + R_TRY(this->MapRange(cap, size_cap, page_table)); + } else { + R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table)); + } + } + + return ResultSuccess(); + } + } diff --git a/libraries/libmesosphere/source/kern_k_memory_manager.cpp b/libraries/libmesosphere/source/kern_k_memory_manager.cpp index e5c58bd33..72e462fc3 100644 --- a/libraries/libmesosphere/source/kern_k_memory_manager.cpp +++ b/libraries/libmesosphere/source/kern_k_memory_manager.cpp @@ -87,6 +87,26 @@ namespace ams::kern { } } + Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) { + /* Lock the pool. */ + KScopedLightLock lk(this->pool_locks[pool]); + + /* Check that we don't already have an optimized process. */ + R_UNLESS(!this->has_optimized_process[pool], svc::ResultBusy()); + + /* Set the optimized process id. */ + this->optimized_process_ids[pool] = process_id; + this->has_optimized_process[pool] = true; + + /* Clear the management area for the optimized process. */ + for (auto *manager = this->GetFirstManager(pool, Direction_FromFront); manager != nullptr; manager = this->GetNextManager(manager, Direction_FromFront)) { + manager->InitializeOptimizedMemory(); + } + + return ResultSuccess(); + } + + KVirtualAddress KMemoryManager::AllocateContinuous(size_t num_pages, size_t align_pages, u32 option) { /* Early return if we're allocating no pages. */ if (num_pages == 0) { @@ -199,7 +219,7 @@ namespace ams::kern { size_t KMemoryManager::Impl::Initialize(const KMemoryRegion *region, Pool p, KVirtualAddress metadata, KVirtualAddress metadata_end) { /* Calculate metadata sizes. */ const size_t ref_count_size = (region->GetSize() / PageSize) * sizeof(u16); - const size_t optimize_map_size = (util::AlignUp((region->GetSize() / PageSize), BITSIZEOF(u64)) / BITSIZEOF(u64)) * sizeof(u64); + const size_t optimize_map_size = CalculateOptimizedProcessOverheadSize(region->GetSize()); const size_t manager_size = util::AlignUp(optimize_map_size + ref_count_size, PageSize); const size_t page_heap_size = KPageHeap::CalculateMetadataOverheadSize(region->GetSize()); const size_t total_metadata_size = manager_size + page_heap_size; diff --git a/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/libraries/libmesosphere/source/kern_k_page_table_base.cpp index 8a79ded60..1817d5f4f 100644 --- a/libraries/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libraries/libmesosphere/source/kern_k_page_table_base.cpp @@ -1350,13 +1350,41 @@ namespace ams::kern { } /* Update the blocks. */ - this->memory_block_manager.Update(&allocator, addr, num_pages, state, perm, KMemoryAttribute_None); + this->memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, KMemoryAttribute_None); /* We successfully mapped the pages. */ *out_addr = addr; return ResultSuccess(); } + Result KPageTableBase::MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm) { + /* Check that the map is in range. */ + const size_t size = num_pages * PageSize; + R_UNLESS(this->CanContain(address, size, state), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(this->general_lock); + + /* Check the memory state. */ + R_TRY(this->CheckMemoryState(address, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + KMemoryBlockManagerUpdateAllocator allocator(this->memory_block_slab_manager); + R_TRY(allocator.GetResult()); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Map the pages. */ + const KPageProperties properties = { perm, false, false, false }; + R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), address, num_pages, properties)); + + /* Update the blocks. */ + this->memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, KMemoryAttribute_None); + + return ResultSuccess(); + } + Result KPageTableBase::UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) { /* Check that the unmap is in range. */ const size_t size = num_pages * PageSize; diff --git a/libraries/libmesosphere/source/kern_k_process.cpp b/libraries/libmesosphere/source/kern_k_process.cpp index 3f0791958..cd958ace4 100644 --- a/libraries/libmesosphere/source/kern_k_process.cpp +++ b/libraries/libmesosphere/source/kern_k_process.cpp @@ -21,7 +21,12 @@ namespace ams::kern { constexpr u64 InitialProcessIdMin = 1; constexpr u64 InitialProcessIdMax = 0x50; + + constexpr u64 ProcessIdMin = InitialProcessIdMax + 1; + constexpr u64 ProcessIdMax = std::numeric_limits::max(); + std::atomic g_initial_process_id = InitialProcessIdMin; + std::atomic g_process_id = ProcessIdMin; } @@ -153,8 +158,115 @@ namespace ams::kern { return ResultSuccess(); } - Result KProcess::Initialize(const ams::svc::CreateProcessParameter ¶ms, svc::KUserPointer caps, s32 num_caps, KResourceLimit *res_limit, KMemoryManager::Pool pool) { - MESOSPHERE_UNIMPLEMENTED(); + Result KProcess::Initialize(const ams::svc::CreateProcessParameter ¶ms, svc::KUserPointer user_caps, s32 num_caps, KResourceLimit *res_limit, KMemoryManager::Pool pool) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(res_limit != nullptr); + + /* Set pool and resource limit. */ + this->memory_pool = pool; + this->resource_limit = res_limit; + + /* Get the memory sizes. */ + const size_t code_num_pages = params.code_num_pages; + const size_t system_resource_num_pages = params.system_resource_num_pages; + const size_t code_size = code_num_pages * PageSize; + const size_t system_resource_size = system_resource_num_pages * PageSize; + + /* Reserve memory for the system resource. */ + KScopedResourceReservation memory_reservation(this, ams::svc::LimitableResource_PhysicalMemoryMax, code_size + KSystemControl::CalculateRequiredSecureMemorySize(system_resource_size, pool)); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Setup page table resource objects. */ + KMemoryBlockSlabManager *mem_block_manager; + KBlockInfoManager *block_info_manager; + KPageTableManager *pt_manager; + + this->system_resource_address = Null; + this->system_resource_num_pages = 0; + + if (system_resource_num_pages != 0) { + /* Allocate secure memory. */ + R_TRY(KSystemControl::AllocateSecureMemory(std::addressof(this->system_resource_address), system_resource_size, pool)); + + /* Set the number of system resource pages. */ + MESOSPHERE_ASSERT(this->system_resource_address != Null); + this->system_resource_num_pages = system_resource_num_pages; + + /* Initialize managers. */ + const size_t rc_size = util::AlignUp(KPageTableManager::CalculateReferenceCountSize(system_resource_size), PageSize); + this->dynamic_page_manager.Initialize(this->system_resource_address + rc_size, system_resource_size - rc_size); + this->page_table_manager.Initialize(std::addressof(this->dynamic_page_manager), GetPointer(this->system_resource_address)); + this->memory_block_slab_manager.Initialize(std::addressof(this->dynamic_page_manager)); + this->block_info_manager.Initialize(std::addressof(this->dynamic_page_manager)); + + mem_block_manager = std::addressof(this->memory_block_slab_manager); + block_info_manager = std::addressof(this->block_info_manager); + pt_manager = std::addressof(this->page_table_manager); + } else { + const bool is_app = (params.flags & ams::svc::CreateProcessFlag_IsApplication); + mem_block_manager = std::addressof(is_app ? Kernel::GetApplicationMemoryBlockManager() : Kernel::GetSystemMemoryBlockManager()); + block_info_manager = std::addressof(Kernel::GetBlockInfoManager()); + pt_manager = std::addressof(Kernel::GetPageTableManager()); + } + + /* Ensure we don't leak any secure memory we allocated. */ + auto sys_resource_guard = SCOPE_GUARD { + if (this->system_resource_address != Null) { + /* Check that we have no outstanding allocations. */ + MESOSPHERE_ABORT_UNLESS(this->memory_block_slab_manager.GetUsed() == 0); + MESOSPHERE_ABORT_UNLESS(this->block_info_manager.GetUsed() == 0); + MESOSPHERE_ABORT_UNLESS(this->page_table_manager.GetUsed() == 0); + + /* Free the memory. */ + KSystemControl::FreeSecureMemory(this->system_resource_address, system_resource_size, pool); + + /* Clear our tracking variables. */ + this->system_resource_address = Null; + this->system_resource_num_pages = 0; + } + }; + + /* Setup page table. */ + /* NOTE: Nintendo passes process ID despite not having set it yet. */ + /* This goes completely unused, but even so... */ + { + const auto as_type = static_cast(params.flags & ams::svc::CreateProcessFlag_AddressSpaceMask); + const bool enable_aslr = (params.flags & ams::svc::CreateProcessFlag_EnableAslr); + R_TRY(this->page_table.Initialize(this->process_id, as_type, enable_aslr, !enable_aslr, pool, params.code_address, code_size, mem_block_manager, block_info_manager, pt_manager)); + } + auto pt_guard = SCOPE_GUARD { this->page_table.Finalize(); }; + + /* Ensure we can insert the code region. */ + R_UNLESS(this->page_table.CanContain(params.code_address, code_size, KMemoryState_Code), svc::ResultInvalidMemoryRegion()); + + /* Map the code region. */ + R_TRY(this->page_table.MapPages(params.code_address, code_num_pages, KMemoryState_Code, static_cast(KMemoryPermission_KernelRead | KMemoryPermission_NotMapped))); + + /* Initialize capabilities. */ + R_TRY(this->capabilities.Initialize(user_caps, num_caps, std::addressof(this->page_table))); + + /* Initialize the process id. */ + this->process_id = g_process_id++; + MESOSPHERE_ABORT_UNLESS(ProcessIdMin <= this->process_id); + MESOSPHERE_ABORT_UNLESS(this->process_id <= ProcessIdMax); + + /* If we should optimize memory allocations, do so. */ + if (this->system_resource_address != Null) { + R_TRY(Kernel::GetMemoryManager().InitializeOptimizedMemory(this->process_id, pool)); + } + + /* Initialize the rest of the process. */ + R_TRY(this->Initialize(params)); + + /* Open a reference to the resource limit. */ + this->resource_limit->Open(); + + /* We succeeded, so commit our memory reservation and cancel our guards. */ + sys_resource_guard.Cancel(); + pt_guard.Cancel(); + memory_reservation.Commit(); + + return ResultSuccess(); } void KProcess::DoWorkerTask() {