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 <noreply@anthropic.com>
This commit is contained in:
Niko 2026-10-13 02:19:43 +02:00
parent 1ed6c21067
commit ed3f2776ed
2 changed files with 3 additions and 1 deletions

View File

@ -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;

View File

@ -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;