Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cgif_raw.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ static int LZW_GenerateStream(LZWResult* pResult, const uint32_t numPixel, const
if(pContext == NULL) {
return CGIF_EALLOC;
}
memset(pContext, 0, sizeof(LZWGenState));

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Found by the new ealloc_raw test with ASAN:
When an early malloc fails, the goto cleanup path frees all pContext fields, including ones never assigned. Zero-initialize pContext so unassigned pointers are NULL and safe to free.

pContext->pTreeInit = malloc((initDictLen * sizeof(uint16_t)) * initDictLen);
if(pContext->pTreeInit == NULL) {
r = CGIF_EALLOC;
Expand Down
97 changes: 97 additions & 0 deletions tests/ealloc_raw.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include "cgif_raw.h"

#define WIDTH 2
#define HEIGHT 2

/* malloc failure injection */
static int fail_after;
static int malloc_count;

static void* cgif_test_malloc(size_t size) {
if(fail_after > 0) {
++malloc_count;
if(malloc_count == fail_after) {
return NULL;
}
}
return malloc(size);
}

/* redirect malloc calls inside cgif_raw.c to our wrapper */
#define malloc(s) cgif_test_malloc(s)
#include "../src/cgif_raw.c"
#undef malloc

/* no-op write callback */
static int writeFn(void* pContext, const uint8_t* pData, const size_t numBytes) {
(void)pContext;
(void)pData;
(void)numBytes;
return 0;
}

int main(void) {
CGIFRaw* pGIF;
CGIFRaw_Config gConfig;
CGIFRaw_FrameConfig fConfig;
cgif_result r;
uint8_t aPalette[] = {
0x00, 0x00, 0x00, // black
0xFF, 0xFF, 0xFF, // white
};
uint8_t aImageData[WIDTH * HEIGHT];
memset(aImageData, 0, sizeof(aImageData));

for(fail_after = 1; ; ++fail_after) {
memset(&gConfig, 0, sizeof(gConfig));
memset(&fConfig, 0, sizeof(fConfig));
gConfig.pWriteFn = writeFn;
gConfig.width = WIDTH;
gConfig.height = HEIGHT;
gConfig.pGCT = aPalette;
gConfig.sizeGCT = 2;

fConfig.pImageData = aImageData;
fConfig.width = WIDTH;
fConfig.height = HEIGHT;
fConfig.attrFlags = CGIF_RAW_FRAME_ATTR_INTERLACED;

malloc_count = 0;

pGIF = cgif_raw_newgif(&gConfig);
if(pGIF == NULL) {
if(malloc_count >= fail_after) {
continue; // expected: our injected malloc failure
}
fputs("unexpected NULL from cgif_raw_newgif\n", stderr);
return 1;
}

r = cgif_raw_addframe(pGIF, &fConfig);
if(r != CGIF_OK) {
if(r == CGIF_EALLOC && malloc_count >= fail_after) {
cgif_raw_close(pGIF);
continue; // expected: our injected malloc failure
}
fprintf(stderr, "unexpected error from cgif_raw_addframe: %d\n", r);
return 1;
}

r = cgif_raw_close(pGIF);
if(malloc_count < fail_after) {
// all mallocs succeeded without hitting our failure point -- done
if(r != CGIF_OK) {
fprintf(stderr, "unexpected error from cgif_raw_close: %d\n", r);
return 1;
}
break;
}
}

return 0;
}
8 changes: 8 additions & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ foreach t : tests_index + tests_rgb
test(name, test_exe, priority : 0)
endforeach

# malloc failure tests (compile source directly to intercept malloc)
test_ealloc_raw_exe = executable(
'test_ealloc_raw',
'ealloc_raw.c',
include_directories : ['../inc/'],
)
test('ealloc_raw', test_ealloc_raw_exe, priority : 0)

sha256sumc = find_program('scripts/sha256sum.py')
# get the ordering right:
# md5sum check on output GIFs should be run once all of the above tests are done.
Expand Down
Loading