From 4d293bb5cbb65d3551dd0d22e92b7065a7ebba14 Mon Sep 17 00:00:00 2001
From: Lioncash <mathew1800@gmail.com>
Date: Fri, 12 Apr 2019 00:38:54 -0400
Subject: [PATCH] kernel/svc: Implement svcUnmapProcessCodeMemory

Essentially performs the inverse of svcMapProcessCodeMemory. This unmaps
the aliasing region first, then restores the general traits of the
aliased memory.

What this entails, is:

- Restoring Read/Write permissions to the VMA.
- Restoring its memory state to reflect it as a general heap memory region.
- Clearing the memory attributes on the region.
---
 src/core/hle/kernel/svc.cpp        | 70 +++++++++++++++++++++++++++++-
 src/core/hle/kernel/vm_manager.cpp | 51 ++++++++++++++++++++++
 src/core/hle/kernel/vm_manager.h   | 23 ++++++++++
 3 files changed, 143 insertions(+), 1 deletion(-)

diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 0aa2e358e..d48a2203a 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -1257,6 +1257,74 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
     return vm_manager.MapCodeMemory(dst_address, src_address, size);
 }
 
+ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address,
+                                  u64 src_address, u64 size) {
+    LOG_DEBUG(Kernel_SVC,
+              "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, "
+              "size=0x{:016X}",
+              process_handle, dst_address, src_address, size);
+
+    if (!Common::Is4KBAligned(dst_address)) {
+        LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).",
+                  dst_address);
+        return ERR_INVALID_ADDRESS;
+    }
+
+    if (!Common::Is4KBAligned(src_address)) {
+        LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).",
+                  src_address);
+        return ERR_INVALID_ADDRESS;
+    }
+
+    if (size == 0 || Common::Is4KBAligned(size)) {
+        LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size);
+        return ERR_INVALID_SIZE;
+    }
+
+    if (!IsValidAddressRange(dst_address, size)) {
+        LOG_ERROR(Kernel_SVC,
+                  "Destination address range overflows the address space (dst_address=0x{:016X}, "
+                  "size=0x{:016X}).",
+                  dst_address, size);
+        return ERR_INVALID_ADDRESS_STATE;
+    }
+
+    if (!IsValidAddressRange(src_address, size)) {
+        LOG_ERROR(Kernel_SVC,
+                  "Source address range overflows the address space (src_address=0x{:016X}, "
+                  "size=0x{:016X}).",
+                  src_address, size);
+        return ERR_INVALID_ADDRESS_STATE;
+    }
+
+    const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+    auto process = handle_table.Get<Process>(process_handle);
+    if (!process) {
+        LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).",
+                  process_handle);
+        return ERR_INVALID_HANDLE;
+    }
+
+    auto& vm_manager = process->VMManager();
+    if (!vm_manager.IsWithinAddressSpace(src_address, size)) {
+        LOG_ERROR(Kernel_SVC,
+                  "Source address range is not within the address space (src_address=0x{:016X}, "
+                  "size=0x{:016X}).",
+                  src_address, size);
+        return ERR_INVALID_ADDRESS_STATE;
+    }
+
+    if (!vm_manager.IsWithinASLRRegion(dst_address, size)) {
+        LOG_ERROR(Kernel_SVC,
+                  "Destination address range is not within the ASLR region (dst_address=0x{:016X}, "
+                  "size=0x{:016X}).",
+                  dst_address, size);
+        return ERR_INVALID_MEMORY_RANGE;
+    }
+
+    return vm_manager.UnmapCodeMemory(dst_address, src_address, size);
+}
+
 /// Exits the current process
 static void ExitProcess(Core::System& system) {
     auto* current_process = system.Kernel().CurrentProcess();
@@ -2286,7 +2354,7 @@ static const FunctionDef SVC_Table[] = {
     {0x75, nullptr, "UnmapProcessMemory"},
     {0x76, SvcWrap<QueryProcessMemory>, "QueryProcessMemory"},
     {0x77, SvcWrap<MapProcessCodeMemory>, "MapProcessCodeMemory"},
-    {0x78, nullptr, "UnmapProcessCodeMemory"},
+    {0x78, SvcWrap<UnmapProcessCodeMemory>, "UnmapProcessCodeMemory"},
     {0x79, nullptr, "CreateProcess"},
     {0x7A, nullptr, "StartProcess"},
     {0x7B, nullptr, "TerminateProcess"},
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 76b491c47..f0c0c12fc 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -331,6 +331,57 @@ ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 si
     return ReprotectRange(dst_address, size, VMAPermission::Read);
 }
 
+ResultCode VMManager::UnmapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
+    constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
+    const auto src_check_result = CheckRangeState(
+        src_address, size, MemoryState::All, MemoryState::Heap, VMAPermission::None,
+        VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::Locked, ignore_attribute);
+
+    if (src_check_result.Failed()) {
+        return src_check_result.Code();
+    }
+
+    // Yes, the kernel only checks the first page of the region.
+    const auto dst_check_result =
+        CheckRangeState(dst_address, Memory::PAGE_SIZE, MemoryState::FlagModule,
+                        MemoryState::FlagModule, VMAPermission::None, VMAPermission::None,
+                        MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
+
+    if (dst_check_result.Failed()) {
+        return dst_check_result.Code();
+    }
+
+    const auto dst_memory_state = std::get<MemoryState>(*dst_check_result);
+    const auto dst_contiguous_check_result = CheckRangeState(
+        dst_address, size, MemoryState::All, dst_memory_state, VMAPermission::None,
+        VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
+
+    if (dst_contiguous_check_result.Failed()) {
+        return dst_contiguous_check_result.Code();
+    }
+
+    const auto unmap_result = UnmapRange(dst_address, size);
+    if (unmap_result.IsError()) {
+        return unmap_result;
+    }
+
+    // With the mirrored portion unmapped, restore the original region's traits.
+    const auto src_vma_result = CarveVMARange(src_address, size);
+    if (src_vma_result.Failed()) {
+        return src_vma_result.Code();
+    }
+    auto src_vma_iter = *src_vma_result;
+    src_vma_iter->second.state = MemoryState::Heap;
+    src_vma_iter->second.attribute = MemoryAttribute::None;
+    Reprotect(src_vma_iter, VMAPermission::ReadWrite);
+
+    if (dst_memory_state == MemoryState::ModuleCode) {
+        Core::System::GetInstance().InvalidateCpuInstructionCaches();
+    }
+
+    return unmap_result;
+}
+
 MemoryInfo VMManager::QueryMemory(VAddr address) const {
     const auto vma = FindVMA(address);
     MemoryInfo memory_info{};
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 27120d3b1..288eb9450 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -441,6 +441,29 @@ public:
     ///
     ResultCode MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size);
 
+    /// Unmaps a region of memory designated as code module memory.
+    ///
+    /// @param dst_address The base address of the memory region aliasing the source memory region.
+    /// @param src_address The base address of the memory region being aliased.
+    /// @param size        The size of the memory region to unmap in bytes.
+    ///
+    /// @pre Both memory ranges lie within the actual addressable address space.
+    ///
+    /// @pre The memory region being unmapped has been previously been mapped
+    ///      by a call to MapCodeMemory.
+    ///
+    /// @post After execution of the function, if successful. the aliasing memory region
+    ///       will be unmapped and the aliased region will have various traits about it
+    ///       restored to what they were prior to the original mapping call preceding
+    ///       this function call.
+    ///       <p>
+    ///       What this also entails is as follows:
+    ///           1. The state of the memory region will now indicate a general heap region.
+    ///           2. All memory attributes for the memory region are cleared.
+    ///           3. Memory permissions for the region are restored to user read/write.
+    ///
+    ResultCode UnmapCodeMemory(VAddr dst_address, VAddr src_address, u64 size);
+
     /// Queries the memory manager for information about the given address.
     ///
     /// @param address The address to query the memory manager about for information.