Skip to content

Fix unchecked integer overflow in encoder UV plane allocation#3233

Open
jortles wants to merge 2 commits into
AOMediaCodec:mainfrom
jortles:fix/encoder-overflow-checks
Open

Fix unchecked integer overflow in encoder UV plane allocation#3233
jortles wants to merge 2 commits into
AOMediaCodec:mainfrom
jortles:fix/encoder-overflow-checks

Conversation

@jortles
Copy link
Copy Markdown
Contributor

@jortles jortles commented May 29, 2026

Summary

  • Add pre-multiplication overflow checks to codec_avm.c and codec_svt.c for UV plane size computations, consistent with the existing safe pattern in avifImageAllocatePlanes() (avif.c:435-441)
  • Add overflow check in avifImageCopyProperties() before numProperties allocation, consistent with avifImagePushProperty() (avif.c:387)

Details

Three sites compute allocation sizes via unchecked integer multiplication, unlike their parallel functions which validate before multiplying:

codec_avm.c:919channelSize * monoUVWidth (uint32_t) and monoUVHeight * monoUVRowBytes (size_t) used for avifAlloc() without overflow checks. The parallel code in avifImageAllocatePlanes() checks width > UINT32_MAX / channelSize and height > PTRDIFF_MAX / fullRowBytes before the same operations.

codec_svt.c:286uvWidth * bytesPerPixel (uint32_t) and uvHeight * uvRowBytes (size_t) computed without pre-multiplication validation. The existing post-hoc check (uvSize > UINT32_MAX / 2) cannot detect a wrap that already occurred.

avif.c:237numProperties * sizeof(...) passed to avifAlloc() without an overflow guard. avifImagePushProperty() in the same file already checks numProperties < SIZE_MAX / sizeof(avifImageItemProperty) before an identical allocation.

Test plan

  • Builds cleanly with cmake --build . --target avif_obj (zero warnings)
  • Existing test suite passes (CI)

Add pre-multiplication overflow checks to codec_avm.c and codec_svt.c
for UV plane size computations, consistent with the existing safe
pattern in avifImageAllocatePlanes() (avif.c:435-441). Also add an
overflow check in avifImageCopyProperties() before the numProperties
allocation, consistent with avifImagePushProperty() (avif.c:387).

Without these checks, crafted image dimensions can silently wrap the
uint32_t multiplication, leading to undersized allocations.

Signed-off-by: Anthony Hurtado <amhurtado@pm.me>
Copilot AI review requested due to automatic review settings May 29, 2026 18:48
Copy link
Copy Markdown
Contributor

@y-guyon y-guyon left a comment

Choose a reason for hiding this comment

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

Thank you for your interest in libavif.

Comment thread src/codec_avm.c Outdated
// Allocate the U plane if necessary.
if (!avmImageAllocated) {
uint32_t channelSize = avifImageUsesU16(image) ? 2 : 1;
if (monoUVWidth > UINT32_MAX / channelSize) {
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.

monoUVWidth is always below UINT32_MAX / channelSize because channelSize is at most 2 and monoUVWidth = (image->width + 1) >> 1.

However I wonder if there should be a check for the image->width + 1 part. Maybe avm_codec_enc_init() fails earlier in case of a huge dimension, but that could change in the future.

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.

Good catch — you're right that monoUVWidth can never exceed UINT32_MAX / channelSize given the current math. I've removed that unreachable check and added a guard for the image->width + 1 overflow instead, which is the actual risk. Same change applied to codec_svt.c.

Comment thread src/codec_avm.c Outdated
return AVIF_RESULT_INVALID_ARGUMENT;
}
uint32_t monoUVRowBytes = channelSize * monoUVWidth;
if (monoUVHeight > PTRDIFF_MAX / monoUVRowBytes) {
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.

I see these checks were inspired from:

libavif/src/avif.c

Lines 433 to 440 in d8b4e04

const uint32_t channelSize = avifImageUsesU16(image) ? 2 : 1;
if (image->width > UINT32_MAX / channelSize) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
const uint32_t fullRowBytes = channelSize * image->width;
if (image->height > PTRDIFF_MAX / fullRowBytes) {
return AVIF_RESULT_INVALID_ARGUMENT;
}

But PTRDIFF_MAX makes little sense in both places in my opinion. It should be SIZE_MAX right?

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.

Agreed — SIZE_MAX is the correct bound here. Changed in both codec_avm.c and codec_svt.c.

- Add UINT32_MAX dimension guard before (width+1)>>1 to prevent
  the +1 overflow, as suggested by reviewer
- Remove unreachable monoUVWidth > UINT32_MAX/channelSize check
  (provably safe given the dimension guard)
- Change PTRDIFF_MAX to SIZE_MAX for size_t allocation bound

Signed-off-by: Anthony Hurtado <amhurtado@pm.me>
Comment thread src/codec_avm.c
// monochrome. Manually set UV planes to 0.5.

// avmImage is always 420 when we're monochrome
if (image->width == UINT32_MAX || image->height == UINT32_MAX) {
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.

Maybe image->width should be cast to size_t instead of compared against UINT32_MAX? It does not matter much for such huge dimensions I guess.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, we should allow UINT32_MAX.

As Yannis suggested, we can cast image->width to a larger type such as uint64_t. (size_t is 32 bits in 32-bit systems.) We use this approach elsewhere.

Another approach is to replace (image->width + 1) >> 1 with image->width / 2 + image->width % 2, or (since image->width >= 1) 1 + (image->width - 1) / 2, or some other similar trick.

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