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