Commit 2d2c13e
crispasr integration
fix(stb_vorbis): heap overflow on untrusted audio — the allocation truncates, the consumer does not
Closes the PLAN item OPEN since 2026-08-18. Reachable from crispasr_audio_load,
so the CLI, the server's upload path and every binding.
THE MECHANISM, confirmed by arithmetic against the original ASAN report:
f->comment_list_length = get32_packet(f); // attacker u32
f->comment_list = setup_malloc(f, sizeof(char*) * length); // int param
memset(f->comment_list, 0, sizeof(char*) * length); // size_t
setup_malloc's parameter is an `int`. With length = 1646854400 the product is
13,174,835,200, which truncates to 289,933,312 — EXACTLY the allocation size the
report showed. The memset computes the same product, but `sizeof` makes it a
size_t, so it is the full 13,174,835,200 — EXACTLY the write size the report
showed. One quantity, computed two ways, disagreeing across a boundary.
UPSTREAM HAS NOT FIXED IT. nothings/stb master still has `setup_malloc(vorb *f,
int sz)` and the same truncating multiply, so there was nothing to pull (which
is what the OPEN item asked to check first) and upstream is vulnerable by the
same truncation — lacking our memset, its `for` loop instead walks
comment_list[i] past the short allocation. Worth reporting there.
THE MEMSET IS ITSELF AN EARLIER CRISPASR PATCH, added to fix a different fuzz
crash (vorbis_deinit freeing an unwritten array). It did not create the
truncation; it converted a gradual overflow into an immediate 13 GB one.
THE FIX, two layers:
1. At the call site, bound the count before the multiply. Two bounds: the
product must not truncate (INT_MAX/sizeof(char*), minus setup_malloc's
(sz+7)&~7 rounding), and — derived from the data rather than picked — every
comment costs at least 4 bytes for its own u32 length, so a file of N bytes
cannot declare more than N/4 comments. That cannot reject a real file: one
with C comments necessarily carries >= 4*C bytes. stream_len is 0 for
stdio-backed streams, where only the first bound applies.
2. At the choke point, setup_malloc rejects a negative sz. Nearly every caller
passes `sizeof(T) * count` computed in size_t, so this is not a one-off
shape; a truncated value landing negative would otherwise reach malloc as a
huge size_t.
The vendor string and the per-comment strings on the same path have the same
shape (attacker u32 length, then `len+1` in an int), so length_is_plausible()
bounds them by the same data-derived rule.
REPRODUCER, and it is verified in both directions.
tests/fuzz/regressions/ogg-comment-count-int-overflow.ogg, 101 bytes, generated
by tools/gen-ogg-comment-fuzz-seed.py. Against the pre-fix vendored decoder
under ASAN it reproduces the three-week-old report EXACTLY: WRITE of size
13174835200, 0 bytes after a 289933312-byte region, start_decoder:3683,
setup_malloc:960, stb_vorbis_open_memory:5141, stb_vorbis_decode_memory:5419 —
every size and every line number. After the fix the same input is rejected
cleanly. Reachability through crispasr_audio_load is established by the original
report's own stack, which is where it was found.
AND A CORRECTION TO THE CORPUS README. The seed it documented as missing
declared comment_list_length = 0x3FFFFFFF. I generated it and it reproduces
NOTHING against current code: 8 * 0x3FFFFFFF truncates to -8, so setup_malloc
fails and the header is rejected. Restoring that exact value would have added a
seed that gates nothing — the same disease as the empty replay set it was
written to fix. That row is retired rather than restored, with the reason.1 parent cfbf5a6 commit 2d2c13e
4 files changed
Lines changed: 103 additions & 4 deletions
File tree
- examples
- tests/fuzz/regressions
- tools
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
947 | 947 | | |
948 | 948 | | |
949 | 949 | | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
950 | 963 | | |
951 | 964 | | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
952 | 973 | | |
953 | 974 | | |
954 | 975 | | |
| |||
3650 | 3671 | | |
3651 | 3672 | | |
3652 | 3673 | | |
| 3674 | + | |
| 3675 | + | |
| 3676 | + | |
| 3677 | + | |
3653 | 3678 | | |
3654 | 3679 | | |
3655 | 3680 | | |
| |||
3661 | 3686 | | |
3662 | 3687 | | |
3663 | 3688 | | |
3664 | | - | |
| 3689 | + | |
| 3690 | + | |
| 3691 | + | |
| 3692 | + | |
| 3693 | + | |
| 3694 | + | |
| 3695 | + | |
| 3696 | + | |
| 3697 | + | |
| 3698 | + | |
| 3699 | + | |
| 3700 | + | |
| 3701 | + | |
| 3702 | + | |
| 3703 | + | |
| 3704 | + | |
| 3705 | + | |
| 3706 | + | |
| 3707 | + | |
| 3708 | + | |
| 3709 | + | |
| 3710 | + | |
| 3711 | + | |
| 3712 | + | |
| 3713 | + | |
| 3714 | + | |
| 3715 | + | |
| 3716 | + | |
| 3717 | + | |
| 3718 | + | |
| 3719 | + | |
| 3720 | + | |
3665 | 3721 | | |
3666 | 3722 | | |
3667 | 3723 | | |
| |||
3685 | 3741 | | |
3686 | 3742 | | |
3687 | 3743 | | |
| 3744 | + | |
| 3745 | + | |
3688 | 3746 | | |
3689 | 3747 | | |
3690 | 3748 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
19 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
20 | 28 | | |
21 | 29 | | |
22 | 30 | | |
23 | 31 | | |
24 | | - | |
| 32 | + | |
| 33 | + | |
25 | 34 | | |
26 | 35 | | |
27 | 36 | | |
| |||
Binary file not shown.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
0 commit comments