From a0925517358b2d2fae2c4f6c469699e185663fd3 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:43:43 +0900 Subject: [PATCH] [Vulkan/D3D12] Validate upload range bounds before vastcpy The D3D12 backend's existing check only validated the last page of the range. The Vulkan backend had no check at all and did not respect the gpu_allow_invalid_upload_range cvar. Both now verify start and end pages are mapped before copying, preventing access violations when games reference unmapped guest physical memory during transitions. --- src/xenia/gpu/d3d12/d3d12_shared_memory.cc | 21 ++++++++++------ src/xenia/gpu/vulkan/vulkan_shared_memory.cc | 26 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/xenia/gpu/d3d12/d3d12_shared_memory.cc b/src/xenia/gpu/d3d12/d3d12_shared_memory.cc index 24a4d12b9..64bc28b82 100644 --- a/src/xenia/gpu/d3d12/d3d12_shared_memory.cc +++ b/src/xenia/gpu/d3d12/d3d12_shared_memory.cc @@ -422,17 +422,24 @@ bool D3D12SharedMemory::UploadRanges( upload_range_length << page_size_log2()); if (upload_range_length > 0 && !cvars::gpu_allow_invalid_upload_range) { + // Check both start and end of the range for unmapped memory. + const uint32_t range_start_addr = upload_range_start << page_size_log2(); const uint32_t upload_range_last_page = upload_range_start + upload_range_length - 1; + const uint32_t range_end_addr = upload_range_last_page + << page_size_log2(); - const memory::PageAccess page_access = - memory().GetPhysicalHeap()->QueryRangeAccess( - upload_range_last_page << page_size_log2(), - upload_range_last_page - << page_size_log2()); // Check only last page + const memory::PageAccess start_access = + memory().GetPhysicalHeap()->QueryRangeAccess(range_start_addr, + range_start_addr); + const memory::PageAccess end_access = + memory().GetPhysicalHeap()->QueryRangeAccess(range_end_addr, + range_end_addr); - if (page_access == xe::memory::PageAccess::kNoAccess) { - XELOGE("Invalid upload range for GPU: {:08X}", upload_range_start); + if (start_access == xe::memory::PageAccess::kNoAccess || + end_access == xe::memory::PageAccess::kNoAccess) { + XELOGE("Invalid upload range for GPU: {:08X} length {:08X}", + upload_range_start, upload_range_length); return false; } } diff --git a/src/xenia/gpu/vulkan/vulkan_shared_memory.cc b/src/xenia/gpu/vulkan/vulkan_shared_memory.cc index 72d44089d..76286b938 100644 --- a/src/xenia/gpu/vulkan/vulkan_shared_memory.cc +++ b/src/xenia/gpu/vulkan/vulkan_shared_memory.cc @@ -19,6 +19,8 @@ #include "xenia/gpu/vulkan/vulkan_command_processor.h" #include "xenia/ui/vulkan/vulkan_util.h" +DECLARE_bool(gpu_allow_invalid_upload_range); + DEFINE_bool(vulkan_sparse_shared_memory, true, "Enable sparse binding for shared memory emulation. Disabling it " "increases video memory usage - a 512 MB buffer is created - but " @@ -405,6 +407,30 @@ bool VulkanSharedMemory::UploadRanges( uint32_t upload_range_length = upload_page_ranges[i].second; trace_writer_.WriteMemoryRead(upload_range_start << page_size_log2(), upload_range_length << page_size_log2()); + + if (upload_range_length > 0 && !cvars::gpu_allow_invalid_upload_range) { + const uint32_t range_start_addr = upload_range_start << page_size_log2(); + const uint32_t upload_range_last_page = + upload_range_start + upload_range_length - 1; + const uint32_t range_end_addr = upload_range_last_page + << page_size_log2(); + + const memory::PageAccess start_access = + memory().GetPhysicalHeap()->QueryRangeAccess(range_start_addr, + range_start_addr); + const memory::PageAccess end_access = + memory().GetPhysicalHeap()->QueryRangeAccess(range_end_addr, + range_end_addr); + if (start_access == xe::memory::PageAccess::kNoAccess || + end_access == xe::memory::PageAccess::kNoAccess) { + XELOGE( + "Vulkan shared memory: Invalid upload range {:08X} length {:08X}", + upload_range_start, upload_range_length); + successful = false; + break; + } + } + while (upload_range_length) { VkBuffer upload_buffer; VkDeviceSize upload_buffer_offset, upload_buffer_size;