@@ -130,6 +130,9 @@ bool TextureLoaderFactory::validate(DataReader reader,
130130 // yield a bound large enough to let an over-large uncompressedByteLength through. Treat
131131 // any overflow as absurd dimensions and reject.
132132 constexpr uint64_t kMaxBytesPerTexel = 16u ; // RGBA32F; >= any uncompressed format
133+ // Maximum plausible expansion ratio for a supercompressed (ZSTD/ZLIB) level. Real texture
134+ // data never approaches this; a decompression bomb exceeds it by orders of magnitude.
135+ constexpr uint64_t kMaxSupercompressionRatio = 1024u ;
133136 uint64_t maxLevelBytes = kMaxBytesPerTexel ;
134137 for (const uint32_t dimension :
135138 {range.width , range.height , range.depth , range.numLayers , std::max (range.numFaces , 1u )}) {
@@ -159,6 +162,20 @@ bool TextureLoaderFactory::validate(DataReader reader,
159162 " Uncompressed level data exceeds the texture dimensions." );
160163 return false ;
161164 }
165+ // For supercompressed levels, uncompressedByteLength sizes the inflate destination
166+ // (ktxTexture2_LoadImageData allocates inflatedDataCapacity, then ktxTexture2_inflate*Int
167+ // spins filling it). The dimension bound above is attacker-influenced: a header declaring
168+ // absurd dimensions (e.g. depth in the millions) inflates maxLevelBytes enough to admit a
169+ // multi-gigabyte uncompressedByteLength, turning a ~1KB compressed payload into a
170+ // multi-second decompression bomb. Independently cap the expansion ratio against the
171+ // compressed size (levelByteLength is already bounded by the input length above).
172+ if (header->supercompressionScheme != 0u &&
173+ levelUncompressedByteLength > levelByteLength * kMaxSupercompressionRatio ) {
174+ igl::Result::setResult (outResult,
175+ igl::Result::Code::InvalidOperation,
176+ " Supercompressed level expansion ratio is implausible." );
177+ return false ;
178+ }
162179 }
163180
164181 if (header->vkFormat != 0u ) {
0 commit comments