Skip to content

AVIF alpha-plane compositing has no dimension-equality check before zip — mismatch silently corrupts alpha instead of erroring #3084

Description

@scadastrangelove

Summary

AvifDecoder::read_image's alpha-plane compositing loop zips the main picture's output buffer against the independently-decoded alpha picture's plane data, with no check that the two pictures actually have matching dimensions — Iterator::zip's silent-truncation-to-the-shorter-iterator semantics mean a dimension mismatch produces incomplete/incorrect alpha compositing rather than an error.

Details

Confirmed by direct source reading against image 0.25.10, src/codecs/avif/decoder.rs:

if let Some(picture) = self.alpha_picture {
    if picture.pixel_layout() != PixelLayout::I400 {
        return Err(...);  // checks chroma layout only
    }
    let stride = picture.stride(PlanarImageComponent::Y) as usize;
    let plane = picture.plane(PlanarImageComponent::Y);
    for (buf, slice) in Iterator::zip(
        buf.chunks_exact_mut(width as usize * 4),   // sized by the MAIN picture's width
        plane.as_ref().chunks_exact(stride),         // sized by the ALPHA picture's own stride
    ) {
        ...
    }
}

The primary picture and the alpha picture are decoded via two entirely separate dav1d::Decoder instances (AvifDecoder::new), each from its own coded data (ctx.primary_item_coded_data() / ctx.alpha_item_coded_data()). There is no check anywhere that alpha_picture's width/height match the primary picture's width/height before this loop. Iterator::zip silently stops at the shorter of its two input iterators, so a mismatch doesn't error — it just applies alpha values to only part of the image (or leaves remaining rows with whatever default/prior alpha was in the buffer).

Impact

A crafted AVIF file whose alpha auxiliary image has different dimensions than its primary image produces silently incorrect (partially-composited or entirely default-alpha) output rather than a decode error — a data-integrity bug, not a crash, in a format specifically used for images requiring correct transparency (UI assets, compositing pipelines).

Suggested direction

Not submitting a PR — the fix is a straightforward dimension-equality check (alpha_picture.width()/height() == picture.width()/height()) before the compositing loop, returning a decode error on mismatch, but flagging for maintainer confirmation since I haven't constructed an actual crafted-AVIF PoC to confirm dav1d doesn't already reject mismatched auxiliary-image dimensions somewhere in its own validation (in which case this code is unreachable dead-code-for-safety rather than a live gap) — this is a source-level reachability argument, not an empirically-triggered one.

Discovered by the rust-in-peace security pipeline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions