From ed3f2776ed284e35384cfa3423739a6f60ca63d3 Mon Sep 17 00:00:00 2001 From: Niko Date: Tue, 13 Oct 2026 02:19:43 +0200 Subject: [PATCH] fix(recompiler): reject a stored plan whose image table names a missing source DecodePlan checked every descriptor source index a plan holds except the two inside an indirect image table: the heap source and the material source. A damaged entry could therefore decode with a table that points past the plan's descriptor sources, and only the materializer's own bounds checks stood between it and an out-of-range read. The front end interns both sources before the image source that names them, so a plan it builds always passes the new check. agc_shader_disk_cache now requires a plan whose table heap or material source lies past the sources not to decode. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp | 2 ++ core/shader/recompiler/ShaderDiskCache.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index a732408bc..848a268bf 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -1455,6 +1455,8 @@ void verifyMalformedPlans() { rejects("an image past the sources", [](IrResourcePlan& plan) { plan.info.images[0].source = 5u; }); rejects("a descriptor of nine dwords", [](IrResourcePlan& plan) { plan.descriptorSources[1].dwordCount = 9u; }); rejects("a uniform fill of five words", [](IrResourcePlan& plan) { plan.uniformFill.fill.words = 5u; }); + rejects("an image table heap past the sources", [](IrResourcePlan& plan) { plan.descriptorSources[1].indirectImage->heapSource = 4u; }); + rejects("an image table material past the sources", [](IrResourcePlan& plan) { plan.descriptorSources[1].indirectImage->materialSource = 4u; }); auto foreign = handBuiltPlan(); IrValue stray(IrOpcode::Void, IrType::U32, 99u); foreign.srtReads[0].value = &stray; diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 02b6d699a..6ed976c86 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -1029,7 +1029,7 @@ bool decodePlan(Reader& reader, IrResourcePlan& plan) { if (!valid || !reader.Done() || fill.words > plan.uniformFill.values.size()) return false; const auto knownSource = [&](std::uint32_t source) { return source < plan.descriptorSources.size(); }; if (!std::ranges::all_of(plan.materializationSources, knownSource)) return false; - if (!std::ranges::all_of(plan.descriptorSources, [](const DescriptorSource& source) { return source.dwordCount <= source.dwords.size(); })) return false; + if (!std::ranges::all_of(plan.descriptorSources, [&](const DescriptorSource& source) { return source.dwordCount <= source.dwords.size() && (!source.indirectImage.has_value() || (knownSource(source.indirectImage->heapSource) && knownSource(source.indirectImage->materialSource))); })) return false; if (!std::ranges::all_of(plan.info.buffers, [&](const BufferResource& buffer) { return knownSource(buffer.source); })) return false; if (!std::ranges::all_of(plan.info.images, [&](const ImageResource& image) { return knownSource(image.source); })) return false; if (!std::ranges::all_of(plan.info.samplers, [&](const SamplerResource& sampler) { return knownSource(sampler.source); })) return false;