From ad5bd81d3f187e0a77d5846f86c83267c7deb23c Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 11 Oct 2023 07:59:37 -0700 Subject: [PATCH] kern: implement PermissionLock, update KPageTableBase attribute/alignment checks --- .../mesosphere/kern_k_memory_block.hpp | 14 ++++- .../kern_k_memory_block_manager.hpp | 4 +- .../source/kern_k_memory_block_manager.cpp | 62 ++++++++++++++++++- .../source/kern_k_page_table_base.cpp | 49 ++++++++++----- .../source/svc/kern_svc_memory.cpp | 5 +- 5 files changed, 114 insertions(+), 20 deletions(-) diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_memory_block.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_memory_block.hpp index eacf909af..9d0a97738 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_memory_block.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_memory_block.hpp @@ -191,7 +191,7 @@ namespace ams::kern { KMemoryAttribute_Uncached = ams::svc::MemoryAttribute_Uncached, KMemoryAttribute_PermissionLocked = ams::svc::MemoryAttribute_PermissionLocked, - KMemoryAttribute_SetMask = KMemoryAttribute_Uncached, + KMemoryAttribute_SetMask = KMemoryAttribute_Uncached | KMemoryAttribute_PermissionLocked, }; enum KMemoryBlockDisableMergeAttribute : u8 { @@ -331,6 +331,10 @@ namespace ams::kern { return this->GetEndAddress() - 1; } + constexpr KMemoryState GetState() const { + return m_memory_state; + } + constexpr u16 GetIpcLockCount() const { return m_ipc_lock_count; } @@ -450,6 +454,14 @@ namespace ams::kern { } } + constexpr void UpdateAttribute(u32 mask, u32 attr) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT((mask & KMemoryAttribute_IpcLocked) == 0); + MESOSPHERE_ASSERT((mask & KMemoryAttribute_DeviceShared) == 0); + + m_attribute = static_cast((m_attribute & ~mask) | attr); + } + constexpr void Split(KMemoryBlock *block, KProcessAddress addr) { MESOSPHERE_ASSERT_THIS(); MESOSPHERE_ASSERT(this->GetAddress() < addr); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_memory_block_manager.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_memory_block_manager.hpp index 99caae0c2..4a67e86f5 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_memory_block_manager.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_memory_block_manager.hpp @@ -104,7 +104,9 @@ namespace ams::kern { void Update(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr); void UpdateLock(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, MemoryBlockLockFunction lock_func, KMemoryPermission perm); - void UpdateIfMatch(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr); + void UpdateIfMatch(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr); + + void UpdateAttribute(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, u32 mask, u32 attr); iterator FindIterator(KProcessAddress address) const { return m_memory_block_tree.find(KMemoryBlock(util::ConstantInitialize, address, 1, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None)); diff --git a/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp b/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp index 4ccf06895..4ba4a566a 100644 --- a/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp +++ b/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp @@ -223,7 +223,7 @@ namespace ams::kern { } /* Update block state. */ - it->Update(state, perm, attr, cur_address == address, set_disable_attr, clear_disable_attr); + it->Update(state, perm, attr, it->GetAddress() == address, set_disable_attr, clear_disable_attr); cur_address += cur_info.GetSize(); remaining_pages -= cur_info.GetNumPages(); } @@ -233,7 +233,7 @@ namespace ams::kern { this->CoalesceForUpdate(allocator, address, num_pages); } - void KMemoryBlockManager::UpdateIfMatch(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr) { + void KMemoryBlockManager::UpdateIfMatch(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr) { /* Ensure for auditing that we never end up with an invalid tree. */ KScopedMemoryBlockManagerAuditor auditor(this); MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); @@ -270,7 +270,7 @@ namespace ams::kern { } /* Update block state. */ - it->Update(state, perm, attr, false, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_None); + it->Update(state, perm, attr, it->GetAddress() == address, set_disable_attr, clear_disable_attr); cur_address += cur_info.GetSize(); remaining_pages -= cur_info.GetNumPages(); } else { @@ -336,6 +336,62 @@ namespace ams::kern { this->CoalesceForUpdate(allocator, address, num_pages); } + void KMemoryBlockManager::UpdateAttribute(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, u32 mask, u32 attr) { + /* Ensure for auditing that we never end up with an invalid tree. */ + KScopedMemoryBlockManagerAuditor auditor(this); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); + + KProcessAddress cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); + + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + KMemoryInfo cur_info = it->GetMemoryInfo(); + + if ((it->GetAttribute() & mask) != attr) { + /* If we need to, create a new block before and insert it. */ + if (cur_info.GetAddress() != GetInteger(cur_address)) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; + + cur_info = it->GetMemoryInfo(); + cur_address = cur_info.GetAddress(); + } + + /* If we need to, create a new block after and insert it. */ + if (cur_info.GetSize() > remaining_size) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + + cur_info = it->GetMemoryInfo(); + } + + /* Update block state. */ + it->UpdateAttribute(mask, attr); + cur_address += cur_info.GetSize(); + remaining_pages -= cur_info.GetNumPages(); + } else { + /* If we already have the right attributes, just advance. */ + if (cur_address + remaining_size < cur_info.GetEndAddress()) { + remaining_pages = 0; + cur_address += remaining_size; + } else { + remaining_pages = (cur_address + remaining_size - cur_info.GetEndAddress()) / PageSize; + cur_address = cur_info.GetEndAddress(); + } + } + it++; + } + + this->CoalesceForUpdate(allocator, address, num_pages); + } + /* Debug. */ bool KMemoryBlockManager::CheckState() const { /* If we fail, we should dump blocks. */ diff --git a/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/libraries/libmesosphere/source/kern_k_page_table_base.cpp index 1a69c860f..090521c4c 100644 --- a/libraries/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libraries/libmesosphere/source/kern_k_page_table_base.cpp @@ -987,7 +987,7 @@ namespace ams::kern { /* Verify that the destination memory is aliasable code. */ size_t num_dst_allocator_blocks; - R_TRY(this->CheckMemoryStateContiguous(std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState_FlagCanCodeAlias, KMemoryState_FlagCanCodeAlias, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_None)); + R_TRY(this->CheckMemoryStateContiguous(std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState_FlagCanCodeAlias, KMemoryState_FlagCanCodeAlias, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All & ~KMemoryAttribute_PermissionLocked, KMemoryAttribute_None)); /* Determine whether any pages being unmapped are code. */ bool any_code_pages = false; @@ -1649,9 +1649,10 @@ namespace ams::kern { KMemoryAttribute old_attr; size_t num_allocator_blocks; constexpr u32 AttributeTestMask = ~(KMemoryAttribute_SetMask | KMemoryAttribute_DeviceShared); + const u32 state_test_mask = ((mask & KMemoryAttribute_Uncached) ? static_cast(KMemoryState_FlagCanChangeAttribute) : 0) | ((mask & KMemoryAttribute_PermissionLocked) ? static_cast(KMemoryState_FlagCanPermissionLock) : 0); R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), std::addressof(num_allocator_blocks), addr, size, - KMemoryState_FlagCanChangeAttribute, KMemoryState_FlagCanChangeAttribute, + state_test_mask, state_test_mask, KMemoryPermission_None, KMemoryPermission_None, AttributeTestMask, KMemoryAttribute_None, ~AttributeTestMask)); @@ -1663,15 +1664,18 @@ namespace ams::kern { /* We're going to perform an update, so create a helper. */ KScopedPageTableUpdater updater(this); - /* Determine the new attribute. */ - const KMemoryAttribute new_attr = static_cast(((old_attr & ~mask) | (attr & mask))); + /* If we need to, perform a change attribute operation. */ + if ((mask & KMemoryAttribute_Uncached) != 0) { + /* Determine the new attribute. */ + const KMemoryAttribute new_attr = static_cast(((old_attr & ~mask) | (attr & mask))); - /* Perform operation. */ - const KPageProperties properties = { old_perm, false, (new_attr & KMemoryAttribute_Uncached) != 0, DisableMergeAttribute_None }; - R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null, false, properties, OperationType_ChangePermissionsAndRefreshAndFlush, false)); + /* Perform operation. */ + const KPageProperties properties = { old_perm, false, (new_attr & KMemoryAttribute_Uncached) != 0, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null, false, properties, OperationType_ChangePermissionsAndRefreshAndFlush, false)); + } /* Update the blocks. */ - m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, old_perm, new_attr, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_None); + m_memory_block_manager.UpdateAttribute(std::addressof(allocator), addr, num_pages, mask, attr); R_SUCCEED(); } @@ -1957,10 +1961,16 @@ namespace ams::kern { /* Select an address to map at. */ KProcessAddress addr = Null; - const size_t phys_alignment = std::min(std::min(util::GetAlignment(GetInteger(phys_addr)), util::GetAlignment(size)), MaxPhysicalMapAlignment); for (s32 block_type = KPageTable::GetMaxBlockType(); block_type >= 0; block_type--) { const size_t alignment = KPageTable::GetBlockSize(static_cast(block_type)); - if (alignment > phys_alignment) { + + const KPhysicalAddress aligned_phys = util::AlignUp(GetInteger(phys_addr), alignment) + alignment - 1; + if (aligned_phys <= phys_addr) { + continue; + } + + const KPhysicalAddress last_aligned_paddr = util::AlignDown(GetInteger(last) + 1, alignment) - 1; + if (!(last_aligned_paddr <= last && aligned_phys <= last_aligned_paddr)) { continue; } @@ -2142,10 +2152,16 @@ namespace ams::kern { /* Select an address to map at. */ KProcessAddress addr = Null; - const size_t phys_alignment = std::min(std::min(util::GetAlignment(GetInteger(phys_addr)), util::GetAlignment(size)), MaxPhysicalMapAlignment); for (s32 block_type = KPageTable::GetMaxBlockType(); block_type >= 0; block_type--) { const size_t alignment = KPageTable::GetBlockSize(static_cast(block_type)); - if (alignment > phys_alignment) { + + const KPhysicalAddress aligned_phys = util::AlignUp(GetInteger(phys_addr), alignment) + alignment - 1; + if (aligned_phys <= phys_addr) { + continue; + } + + const KPhysicalAddress last_aligned_paddr = util::AlignDown(GetInteger(last) + 1, alignment) - 1; + if (!(last_aligned_paddr <= last && aligned_phys <= last_aligned_paddr)) { continue; } @@ -4467,7 +4483,9 @@ namespace ams::kern { /* Update the relevant memory blocks. */ m_memory_block_manager.UpdateIfMatch(std::addressof(allocator), address, size / PageSize, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, - KMemoryState_Normal, KMemoryPermission_UserReadWrite, KMemoryAttribute_None); + KMemoryState_Normal, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, + address == this->GetAliasRegionStart() ? KMemoryBlockDisableMergeAttribute_Normal : KMemoryBlockDisableMergeAttribute_None, + KMemoryBlockDisableMergeAttribute_None); R_SUCCEED(); } @@ -4562,6 +4580,9 @@ namespace ams::kern { /* Iterate over the memory, unmapping as we go. */ auto it = m_memory_block_manager.FindIterator(cur_address); + + const auto clear_merge_attr = (it->GetState() == KMemoryState_Normal && it->GetAddress() == this->GetAliasRegionStart() && it->GetAddress() == address) ? KMemoryBlockDisableMergeAttribute_Normal : KMemoryBlockDisableMergeAttribute_None; + while (true) { /* Check that the iterator is valid. */ MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); @@ -4594,7 +4615,7 @@ namespace ams::kern { m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, mapped_size); /* Update memory blocks. */ - m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_None); + m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, clear_merge_attr); /* We succeeded. */ R_SUCCEED(); diff --git a/libraries/libmesosphere/source/svc/kern_svc_memory.cpp b/libraries/libmesosphere/source/svc/kern_svc_memory.cpp index a6994fae4..95173a0db 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_memory.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_memory.cpp @@ -58,10 +58,13 @@ namespace ams::kern::svc { R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); /* Validate the attribute and mask. */ - constexpr u32 SupportedMask = ams::svc::MemoryAttribute_Uncached; + constexpr u32 SupportedMask = ams::svc::MemoryAttribute_Uncached | ams::svc::MemoryAttribute_PermissionLocked; R_UNLESS((mask | attr) == mask, svc::ResultInvalidCombination()); R_UNLESS((mask | attr | SupportedMask) == SupportedMask, svc::ResultInvalidCombination()); + /* Check that permission locked is either being set or not masked. */ + R_UNLESS((mask & ams::svc::MemoryAttribute_PermissionLocked) == (attr & ams::svc::MemoryAttribute_PermissionLocked), svc::ResultInvalidCombination()); + /* Validate that the region is in range for the current process. */ auto &page_table = GetCurrentProcess().GetPageTable(); R_UNLESS(page_table.Contains(address, size), svc::ResultInvalidCurrentMemory());