Skip to content

Bound exp-Golomb prefix and KPARAM macros in VLC coefficient readers - #290

Open
fkyslov wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
fkyslov:fix-vlc-and-metadata-overflows
Open

fkyslov wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
fkyslov:fix-vlc-and-metadata-overflows

Conversation

@fkyslov

@fkyslov fkyslov commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Bounds exp-Golomb prefix length and clamps KPARAM_* macros in the VLC coefficient readers:

  • dec_vlc_read_kparam0() & dec_vlc_read_1bit_read() (src/oapv_vlc.c):
    • Bound prefix length k < 30 inside and after the prefix loops and accumulate symbol as u32 so malformed bitstreams with long zero runs are rejected before k or symbol can overflow.
  • KPARAM_DC, KPARAM_AC, KPARAM_RUN (src/oapv_vlc.h):
    • Clamp KPARAM_* macros using oapv_clip3() to [OAPV_KPARAM_*_MIN, OAPV_KPARAM_*_MAX].

Testing

  • Verified all 24 ctest unit/conformance tests pass with AddressSanitizer and UndefinedBehaviorSanitizer.

- Bound prefix length k < 30 inside and after the prefix loops in dec_vlc_read_kparam0() and dec_vlc_read_1bit_read(), and accumulate symbol as u32 to avoid signed integer overflow on malformed bitstreams.
- Clamp KPARAM_DC, KPARAM_AC, and KPARAM_RUN macros with oapv_clip3().

Signed-off-by: Fyodor Kyslov <kyslov@google.com>
@fkyslov fkyslov changed the title Harden VLC exp-Golomb, metadata payload, and encoder frame cleanup Bound exp-Golomb prefix and KPARAM macros in VLC coefficient readers Sep 25, 2026
@fkyslov
fkyslov force-pushed the fix-vlc-and-metadata-overflows branch from be58705 to afbd785 Compare September 25, 2026 22:28

@kpchoi kpchoi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Firstly, Regarding 679 and 719 of your patch,

The in-loop k checks added to dec_vlc_read_kparam0() and dec_vlc_read_1bit_read() are not needed. These loops cannot run forever, even on a malformed bitstream.

  • At the end of the buffer, BSR_FLUSH_1BYTE() does not read past it. It fills code with all 1-bits (~(u64)0) and sets is_eob.
  • The next BSR_READ_1BIT() then returns 1, so flag becomes true and the loop exits through break.
  • Inside these loops k is only incremented and never used as a shift count. The existing check after the loop rejects too large a k before it is used.

The error is detected later instead (deferred error checking). The callers test BSR_IS_UNEXPECTED_EOB() after parsing the coefficients and return OAPV_ERR_MALFORMED_BITSTREAM. This is intentional. These two functions are the k = 0 paths and are the ones called most often in coefficient decoding, so a per-bit check in their loops would slow down decoding.

Comments explaining this will be added near these loops.

@kpchoi kpchoi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The oapv_clip3() change to the KPARAM_* macros is not needed either. Their arguments are never negative, and OAPV_KPARAM_*_MIN is 0, so the lower clamp can never take effect:

  • KPARAM_DC() gets abs_dc_diff, which is at least 1 at that point.
  • KPARAM_AC() gets level, which is at least 1.
  • KPARAM_RUN() gets run, which has already been range-checked to [0, OAPV_BLK_D - scan_pos_offset].
  • On the encoder side, only absolute values are passed.

These macros are evaluated for every coefficient. In VLC decoding even one extra branch or compare per coefficient noticeably affects decoding speed, so oapv_clip3() is intentionally not used here.

Comment thread src/oapv_vlc.c
static int dec_vlc_read_kparam0(oapv_bs_t *bs)
{
int symbol;
u32 symbol;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Changing symbol from int to u32 is not needed for correctness. The additions are already done in unsigned arithmetic (int + u32, int + u64), so there is no signed overflow. With the k check after the loop, the result always fits in int.

Still, the change is fine to keep as a cosmetic change. It matches dec_vlc_read(), which already uses u32 symbol, and it keeps code reviewers and coding agents from mistaking this pattern for a signed overflow.

Comment thread src/oapv_vlc.c
}
}
oapv_assert_rv(k < 32, -1); /* prevent too large (impossible) k value */
oapv_assert_rv(k < 30, -1); /* prevent too large (impossible) k value */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a correct comparison. thanks.

Comment thread src/oapv_vlc.c
break;
}
else {
oapv_assert_rv(k < 30, -1); /* prevent too large (impossible) k value */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This line is strongly suggested to be removed because of the reason commented.

Comment thread src/oapv_vlc.c
break;
}
else {
oapv_assert_rv(k < 30, -1); /* prevent too large (impossible) k value */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This line is strongly suggested to be removed because of the reason commented.

Comment thread src/oapv_vlc.h
Comment on lines +38 to +40
#define KPARAM_DC(level) oapv_clip3(OAPV_KPARAM_DC_MIN, OAPV_KPARAM_DC_MAX, (level)>>1)
#define KPARAM_AC(level) oapv_clip3(OAPV_KPARAM_AC_MIN, OAPV_KPARAM_AC_MAX, (level)>>2)
#define KPARAM_RUN(run) oapv_clip3(OAPV_KPARAM_RUN_MIN, OAPV_KPARAM_RUN_MAX, (run)>>2)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This 3 line are not required to be changed because of the reason commented.

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.

2 participants