Skip to content

validate rowBytes and width overflow in avifImageRGBToYUV#3146

Open
uwezkhan wants to merge 1 commit into
AOMediaCodec:mainfrom
uwezkhan:rowbytes-check
Open

validate rowBytes and width overflow in avifImageRGBToYUV#3146
uwezkhan wants to merge 1 commit into
AOMediaCodec:mainfrom
uwezkhan:rowbytes-check

Conversation

@uwezkhan

Copy link
Copy Markdown
Contributor

Added validation in avifImageRGBToYUV to make sure rowBytes and width are safe before processing.

What was the issue

The function was using rowBytes directly without checking if it was actually large enough for the given width and pixel size.
If rowBytes is smaller than expected, it can read past the buffer.
Also, width * pixelSize could overflow in some cases.

What I changed

  • Added a check to prevent overflow in width * pixelSize
  • Added a check to ensure rowBytes is not smaller than required

Impact

This prevents out-of-bounds reads when invalid RGB input is passed.

@b4l4-sec

b4l4-sec commented Jul 2, 2026

Copy link
Copy Markdown

I've opened a fix PR for this in #3291.

Root cause confirmed under ASan: avifImageRGBToYUV() (and avifImageYUVToRGB()) never validate that rgb->rowBytes >= rgb->width * avifRGBImagePixelSize(rgb) before entering the pixel loop. The offset calculation at reformat.c:320:

rgb->pixels[ offsetBytesR + (i * rgbPixelBytes) + (j * rgbRowBytes) ]

reads 1 byte past the end of the heap allocation at column i=1 when rowBytes = pixelSize (1 pixel wide) and width = 100.

AddressSanitizer: heap-buffer-overflow READ of size 1
    #0 avifImageRGBToYUV src/reformat.c:320
    0x… is located 0 bytes after 4-byte region

Fix: 4-line early-return guard in each entry point + a regression test. See #3291.

b4l4-sec pushed a commit to b4l4-sec/libavif that referenced this pull request Jul 2, 2026
…oops

The rowBytes guard added in the previous commit (issue AOMediaCodec#3146) only covered
avifImageRGBToYUV and avifImageYUVToRGB.  The same missing validation is
present in three more public API functions whose manual C pixel loops access
pixels at offset (i * pixelSize + j * rowBytes) without first verifying
that rowBytes >= width * pixelSize:

  * avifRGBImagePremultiplyAlpha  (src/alpha.c)
  * avifRGBImageUnpremultiplyAlpha (src/alpha.c)
  * avifRGBImageApplyGainMap      (src/gainmap.c) — baseImage
  * avifRGBImageComputeGainMap    (src/gainmap.c) — baseRgbImage + altRgbImage

Both alpha functions are confirmed under ASan: heap-buffer-overflow READ at
alpha.c:261 when rowBytes = pixelSize and width = 100.

Add early-return guards returning AVIF_RESULT_REFORMAT_FAILED /
AVIF_RESULT_INVALID_ARGUMENT, and extend the regression test suite with
TEST(AlphaTest, UndersizedRowBytesFailsPremultiply).
Comment thread src/reformat.c
// pixel format. The slow-path conversion loop indexes pixels as:
// rgb->pixels[offsetBytes + (i * pixelBytes) + (j * rowBytes)]
// A rowBytes that is smaller than (width * pixelBytes) would cause the
// per-row offset to under-stride, producing out-of-bounds reads on the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test showing out-of-bounds reads with reasonable API use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went to write this and couldn't get an out-of-bounds read with reasonable use. The slow path reads rgb->pixels at the caller-supplied rgb->rowBytes, and avif.h already documents that rowBytes must be at least width * avifRGBImagePixelSize(). Every in-tree caller goes through avifRGBImageAllocatePixels(), which sets rowBytes to exactly that, so none of them can trip it.

The only way to reach the over-read is to hand-set rowBytes below that documented minimum. The ASan repro in #3291 does the same thing (rowBytes = pixelSize with width = 100), so it's that contract violation rather than reasonable use.

So this is really defense-in-depth against documented misuse, not a reachable bug. I'm fine closing it, or keeping the guard as a cheap INVALID_ARGUMENT return instead of an OOB read. Your call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants