From 1e5b8d9e7c4d08e423d2ea3701251c1605312aa3 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 23:02:17 -0500 Subject: [PATCH] fix(shader): report a truncated memory, image or export instruction as out of range ReadRawComputeShader decodes a growing prefix of guest memory and reads more when the decoder throws std::out_of_range, which the vector, scalar and instruction-length checks throw for an instruction cut off by the end of the span. The memory (SMEM, DS, MUBUF, MTBUF, FLAT), image and export decoders threw std::runtime_error instead, so a two-word instruction across a window boundary failed the read: vkQuake terminated on "truncated FLAT instruction". These now throw std::out_of_range too. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tests/Submit.cpp | 7 +++++++ .../recompiler/RdnaDecoder/src/RdnaExportOpDecoder.cpp | 2 +- .../recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp | 4 ++-- .../recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index e774ae46e..d6c1360a4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -577,6 +577,13 @@ int main() { "raw compute cache eviction lost snapshot lifetime or exceeded its entry limit"); check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress + 4); }).empty(), "raw compute accepted a misaligned entry"); check(!expectFailure([] { AgcDriver::DriverDetail::ReadRawComputeShader(0); }).empty(), "raw compute accepted an unmapped entry"); + alignas(256) std::array straddling{}; + straddling.fill(0xbf800000); + straddling[63] = 0xf4000000; + straddling[64] = 0xfa000000; + straddling[65] = 0xbf810000; + const auto straddled = AgcDriver::DriverDetail::ReadRawComputeShader(reinterpret_cast(straddling.data())); + check(straddled->code.size() == 66 && straddled->code[64] == 0xfa000000, "raw compute stopped at a memory instruction across its first read window"); #ifdef _WIN32 auto* mapping = static_cast(VirtualAlloc(nullptr, 8192, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); check(mapping != nullptr, "cannot allocate raw compute boundary test"); diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaExportOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaExportOpDecoder.cpp index 89e5645f7..24e45182b 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaExportOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaExportOpDecoder.cpp @@ -14,7 +14,7 @@ RdnaInstruction DecodeRdnaExportOp(std::span code, std::uin RdnaInstruction DecodeRdnaExportOp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex) { const std::size_t index = wordIndex; if (index >= code.size() || code.size() - index < 2u) { - throw std::runtime_error("truncated export instruction"); + throw std::out_of_range("truncated export instruction"); } if (programCounter % 4u != 0u || programCounter > std::numeric_limits::max() - 7u) { throw std::runtime_error("invalid export program counter"); diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index 640be3b59..f97615925 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -323,7 +323,7 @@ RdnaInstruction DecodeRdnaImageOp(std::span code, std::uint RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex) { const std::size_t index = wordIndex; if (index >= code.size() || code.size() - index < 2u) { - throw std::runtime_error("truncated MIMG instruction"); + throw std::out_of_range("truncated MIMG instruction"); } const auto word0 = code[index]; const auto word1 = code[index + 1u]; @@ -343,7 +343,7 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span> 1u) & 3u; const auto wordCount = 2u + nsa; if (code.size() - index < wordCount) { - throw std::runtime_error("truncated MIMG NSA payload"); + throw std::out_of_range("truncated MIMG NSA payload"); } if (programCounter % 4u != 0u || programCounter > std::numeric_limits::max() - (wordCount * 4u - 1u)) { throw std::runtime_error("invalid MIMG program counter"); diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 8f8c5c142..ba168a8f3 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -696,7 +696,7 @@ void setRawWords(RdnaInstruction& instruction, std::span co void requireTwoWords(std::span code, std::uint32_t wordIndex, std::uint32_t programCounter, const char* reason) { const std::size_t index = wordIndex; if (index >= code.size() || code.size() - index < 2u) { - throw std::runtime_error(reason); + throw std::out_of_range(reason); } if (programCounter % 4u != 0u || programCounter > std::numeric_limits::max() - 7u) { throw std::runtime_error("invalid memory instruction program counter"); @@ -1036,7 +1036,7 @@ RdnaInstruction DecodeRdnaDs(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex) { const auto programCounter = toProgramCounter(wordIndex); if (static_cast(wordIndex) >= code.size()) { - throw std::runtime_error("truncated memory instruction"); + throw std::out_of_range("truncated memory instruction"); } switch (code[wordIndex] >> 26u) { case 0x36u: return DecodeRdnaDs(programCounter, code, wordIndex);