stb_image is a widely used, single-header C library for image loading, loved for its simplicity and permissive MIT license. It's used in countless indie games and software projects. But sometimes, even such well-tested libraries can have subtle vulnerabilities. One such issue, tracked as CVE-2023-45666, affects how stb_image processes GIF images. Let’s dig into what happened, see some example code, and understand why this is a real headache for C programmers.

The Heart of the Problem

When reading GIF files, stb_image uses an internal function called stbi__load_gif_main. It sometimes returns data in a pointer called *delays — an array containing the frame delays for animated GIFs.

If everything goes right, you get a valid pointer in *delays alongside your loaded image. If something goes wrong, you should get NULL, and there shouldn’t be any memory left to worry about. However, due to a logic mishap in how failure and cleanup are handled, there are cases where *delays might point to allocated memory _even after a failure_. The result?

Relevant pieces from the source (simplified for clarity)

unsigned char *stbi__load_gif_main(/*...*/, int **delays);
/*...
   delays is allocated memory for gif frame delays
   returns pointer to pixel data, or NULL on failure
*/ 

The function sets *delays = at the start.
But on failure, for some code paths like failing in stbi__convert_format(), it _might_ not reset or free the allocated *delays.

Therefore, if you do this in your program

int *delays;
unsigned char *image = stbi__load_gif_main(..., &delays);

if (image != NULL) {
    // Use data
    free(delays); // OK
} else {
    // Failure; nothing to free, right?
    // WRONG: In some cases, delays is not NULL and needs to be freed
}

If you flip it around

int *delays;
unsigned char *image = stbi__load_gif_main(..., &delays);
free(delays);   // can lead to double-free if 'delays' was never allocated or already freed internally.

Either way, you're at risk of a memory leak (never freeing something you should) or a double-free (freeing twice).

Bloat your program’s memory consumption (by repeatedly forcing leaks)

- Crash your program (by triggering a double-free, which many allocators treat as a program-ending event)

Here’s a minimal reproduction pattern

int *delays;
unsigned char *pixels = stbi__load_gif_main(malicious_gif, &delays);
if (!pixels) {
    // If delays is *not* NULL, you have a memory leak.
    // If you always free it, you risk a double-free.
}

If this code is exposed in a server or a persistent process, an attacker could exploit it to cause a denial of service.

delays is not NULL

int *delays = NULL;
unsigned char *pixels = stbi__load_gif_main(gif_data, &delays);

if (pixels && delays)
    free(delays);
// If pixels == NULL, always set delays = NULL yourself before calling

Vendor Patch

An upstream patch would ensure that *delays is always set to NULL on failure, or is always released in every code path on failure.
See GitHub Issue #1526 and this commit diff (if and when applied) for details.

Summary

- stb_image's GIF loader can leak memory, or cause a double-free, due to inconsistent cleanup of the delays pointer on load failure.
- CVE-2023-45666 is low-risk for most apps, but crucial for any app handling many untrusted GIFs, e.g., image viewers or web servers.
- If you rely on stb_image, audit your code, ideally update your stb_image version, and always remember: in C, you must be paranoid about your pointers.

References

- CVE-2023-45666 on cve.org
- Official stb_image repository
- GitHub issue discussion
- stb_image Documentation (see GIF loading)


Stay safe — and always double-check your pointer logic!

Timeline

Published on: 10/21/2023 00:15:09 UTC
Last modified on: 11/04/2023 06:15:53 UTC