Skip to content

Bound exp-Golomb prefix k < 30 in dec_vlc_read_kparam0() and dec_vlc_read_1bit_read() - #290

Merged
kpchoi merged 1 commit into
AcademySoftwareFoundation:mainfrom
fkyslov:fix-vlc-and-metadata-overflows
Sep 29, 2026
Merged

kpchoi merged 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 the exp-Golomb prefix length k < 30 and accumulates symbol in u32 in dec_vlc_read_kparam0() and dec_vlc_read_1bit_read() (src/oapv_vlc.c) to prevent signed/unsigned integer overflow and shift undefined behavior on malformed bitstreams with long zero-prefix runs.

Testing

  • Verified all 24 ctest unit/conformance tests pass with AddressSanitizer and UndefinedBehaviorSanitizer (-fsanitize=address,signed-integer-overflow,unsigned-integer-overflow,shift,bounds).
  • Verified against malformed VLC bitstreams.

@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 Outdated
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 Outdated
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 Outdated
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.

…read_1bit_read()

Signed-off-by: Fyodor Kyslov <kyslov@google.com>
@fkyslov fkyslov changed the title Bound exp-Golomb prefix and KPARAM macros in VLC coefficient readers Bound exp-Golomb prefix k < 30 in dec_vlc_read_kparam0() and dec_vlc_read_1bit_read() Sep 28, 2026
@fkyslov

fkyslov commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Updated per review feedback: removed the in-loop oapv_assert_rv(k < 30, -1) checks and reverted the redundant oapv_clip3 macro changes in src/oapv_vlc.h, keeping only the post-loop oapv_assert_rv(k < 30, -1) and u32 symbol in dec_vlc_read_kparam0() and dec_vlc_read_1bit_read().

@fkyslov
fkyslov force-pushed the fix-vlc-and-metadata-overflows branch from afbd785 to 76fd485 Compare September 28, 2026 17:58

@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.

Great. thanks.

@kpchoi
kpchoi merged commit d5c5606 into AcademySoftwareFoundation:main Sep 29, 2026
10 checks passed
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