66#include "cgif_raw.h"
77
88/*
9- * Regression test for integer overflow in LZW buffer allocation (PR #107) .
9+ * Regression test for integer overflow in the LZW code- buffer allocation.
1010 *
11- * The original code computed the LZW data buffer size as :
11+ * LZW_GenerateStream() sizes pLZWData from :
1212 *
1313 * (numPixel + 2 + maxResets) * sizeof(uint16_t)
1414 *
15- * using uint32_t arithmetic. When numPixel approaches UINT32_MAX
16- * (e.g. a 65535x65535 GIF = ~4.3 billion pixels), the additive expression
17- * wraps, producing a drastically undersized malloc followed by a heap
18- * buffer overflow during LZW encoding .
15+ * On a 32-bit target size_t is 32 bits wide, so for a numPixel close to
16+ * UINT32_MAX (e.g. a 65535x65535 GIF is ~4.3 billion pixels) the additive
17+ * expression wraps and malloc receives a drastically undersized request.
18+ * LZW encoding then writes past the end of that buffer .
1919 *
20- * The fix promotes the arithmetic to size_t and adds explicit overflow
21- * guards that return CGIF_EALLOC before calling malloc .
20+ * The fix promotes the arithmetic to size_t and returns CGIF_EALLOC via the
21+ * overflow guard before malloc is ever called .
2222 *
23- * This test calls LZW_GenerateStream() directly (via source inclusion)
24- * with a numPixel value large enough to trigger the overflow. A malloc
25- * wrapper intercepts the pLZWData allocation and verifies that its size
26- * is not truncated.
23+ * We reach the static function by including the translation unit directly and
24+ * redirecting its malloc (same approach as ealloc_raw.c).
2725 *
28- * Expected results:
29- * main (unfixed): pLZWData malloc has a wrapped (tiny) size => FAIL
30- * fixed: overflow guard returns CGIF_EALLOC cleanly => PASS
26+ * Detecting the buffer without depending on malloc call order:
27+ * pLZWData is the only allocation that scales with numPixel. Every other
28+ * allocation is bounded by MAX_DICT_LEN and stays well under 64 KiB for the
29+ * inputs used here, so we treat any request larger than that threshold as the
30+ * LZW buffer. We record its size and return NULL so encoding never runs on a
31+ * dummy image buffer.
32+ *
33+ * main (unfixed), 32-bit: request wraps to ~161 KiB (< numPixel bytes) -> FAIL
34+ * fixed, 32-bit: guard returns CGIF_EALLOC, buffer never sized -> PASS
35+ * 64-bit (either): no wrap, request is > 8 GiB, we force NULL -> PASS
3136 */
3237
33- /* ---- malloc interception ---- */
38+ #define LZW_ALLOC_THRESHOLD 65536 /* every non-LZW allocation stays below this */
3439
35- static int malloc_count ;
36- static int overflow_detected ;
37- static size_t overflow_alloc_size ;
40+ static size_t lzwAllocSize ; /* size of the LZW-buffer request, 0 if none seen */
3841
3942static void * test_malloc (size_t size ) {
40- ++ malloc_count ;
41- /*
42- * Inside LZW_GenerateStream the malloc calls are:
43- * #1 pContext (small)
44- * #2 pTreeInit (small)
45- * #3 pTreeList (small)
46- * #4 pTreeMap (small)
47- * #5 pLZWData <-- the one that overflows on main
48- *
49- * On the fixed branch the overflow guard fires between #4 and #5,
50- * so malloc_count never reaches 5.
51- *
52- * For #5 we check whether the requested size is suspiciously small.
53- * The correct size for numPixel ~ 4.3 billion is > 8 GB.
54- * A uint32-wrapped size would be < 1 MB.
55- * We always return NULL for #5 to prevent the function from trying
56- * to LZW-encode our dummy (1-byte) image buffer.
57- */
58- if (malloc_count == 5 ) {
59- overflow_alloc_size = size ;
60- if (size < 100000000 ) { /* < 100 MB => uint32_t overflow occurred */
61- overflow_detected = 1 ;
62- }
63- return NULL ; /* never allocate — image data is a dummy */
43+ if (size > LZW_ALLOC_THRESHOLD ) {
44+ lzwAllocSize = size ;
45+ return NULL ; /* abort before encoding a dummy image */
6446 }
6547 return malloc (size );
6648}
@@ -73,43 +55,27 @@ static void* test_malloc(size_t size) {
7355int main (void ) {
7456 LZWResult result ;
7557 /*
76- * numPixel that triggers uint32_t overflow in (numPixel + 2 + maxResets):
77- *
78- * initDictLen = 4 (for a 1-colour palette)
79- * entriesPerCycle = MAX_DICT_LEN - 4 - 2 = 4090
80- * maxResets = 4294000000 / 4090 = 1049877
81- * sum = 4294000000 + 2 + 1049877 = 4295049879 (> UINT32_MAX)
82- * wrapped uint32 = 82583
83- * wrapped malloc = 82583 * 2 = 165166 bytes (~161 KB)
84- *
85- * On 32-bit (with fix): size_t overflow guard fires -> CGIF_EALLOC
86- * On 64-bit (with fix): correct size_t addition (8.6 GB) -> malloc #5
87- * -> our wrapper returns NULL -> CGIF_EALLOC
88- * On main (no fix): wrapped uint32 -> malloc #5 with 161 KB size
89- * -> overflow_detected = 1 -> FAIL
58+ * numPixel large enough to overflow (numPixel + 2 + maxResets) in 32-bit
59+ * size_t arithmetic. A 65535x65535 frame produces a value in this range.
9060 */
9161 const uint32_t numPixel = 4294000000U ;
92- const uint16_t initDictLen = 4 ; /* 1 << (calcInitCodeLen(1) - 1) */
93- const uint8_t initCodeLen = 3 ; /* calcInitCodeLen(1) */
62+ const uint16_t initDictLen = 4 ; /* 1-color palette */
63+ const uint8_t initCodeLen = 3 ;
9464
95- /* Minimal image data — the overflow check fires before encoding. */
9665 uint8_t imageData [1 ] = {0 };
9766
98- malloc_count = 0 ;
99- overflow_detected = 0 ;
100- overflow_alloc_size = 0 ;
67+ lzwAllocSize = 0 ;
10168 memset (& result , 0 , sizeof (result ));
10269
10370 int r = LZW_GenerateStream (& result , numPixel , imageData , initDictLen , initCodeLen );
10471
105- if ( overflow_detected ) {
72+ if ( lzwAllocSize != 0 && lzwAllocSize < ( size_t ) numPixel ) {
10673 fprintf (stderr ,
107- "FAIL: uint32 overflow in pLZWData allocation "
108- "(requested %zu bytes for %u pixels, expected > 8 GB)\n" ,
109- overflow_alloc_size , numPixel );
74+ "FAIL: LZW buffer request wrapped (%zu bytes for %u pixels)\n" ,
75+ lzwAllocSize , numPixel );
11076 return 1 ;
11177 }
112- if (r != CGIF_EALLOC ) {
78+ if (r != CGIF_EALLOC ) {
11379 fprintf (stderr , "FAIL: expected CGIF_EALLOC, got %d\n" , r );
11480 return 1 ;
11581 }
0 commit comments