security(codecs): admit HEIF coded image size, not just transformed

When transforms (orientation/clean-aperture crop) are applied, admission
checked only the transformed output size. The delegated HEVC decoder
allocates the full coded image before cropping, so a HEIF with a small
visible aperture over a large coded image bypassed the limit policy.

Always check the coded dimensions against the full limits before
delegating, in addition to the transformed size.

Note: the related SPS-driven and grid-tile allocations (findings
75048229 and 2db4c59e) occur inside the pinned heic-rs dependency from
parameter sets the container does not expose; fully bounding those needs
a decoder-side change and is tracked separately.

Finding: ec5cec31-8a62-4963-8045-e3ec9d9e4931 (CWE-400)
This commit is contained in:
Dylan M. Taylor
2026-10-22 07:10:49 -04:00
parent 1f2be70057
commit 9edcb97687
+7
View File
@@ -45,6 +45,13 @@ pub(crate) fn decode(bytes: &[u8], limits: &Limits, keep_orientation: bool) -> R
let sample = if info.sixteen_bit { SampleType::U16 } else { SampleType::U8 };
let (w, h) = if keep_orientation { (info.coded_width, info.coded_height) } else { (info.width, info.height) };
limits.check(w, h, layout, sample)?;
// The delegated decoder allocates the full coded image before applying any clean-aperture
// crop or orientation transform, so the coded dimensions must pass the limit policy even
// when the displayed (transformed) size is small. Checking only the transformed size let a
// tiny visible image hide a large coded allocation.
if !keep_orientation && (info.coded_width, info.coded_height) != (w, h) {
limits.check(info.coded_width, info.coded_height, layout, sample)?;
}
let options = photocraft_heif::Options { max_pixels: limits.max_pixels, apply_transforms: !keep_orientation };
let decoded = photocraft_heif::decode(bytes, &options).map_err(err)?;
// The decoder re-reads the container; trust its output's shape, not the probe's.