Double Vision: The Heap Overflow in gif2apng
Jan 7, 2026·6 min read
Executive Summary (TL;DR)
The gif2apng tool uses a two-pass strategy to convert GIFs: count frames, allocate memory, then read data. CVE-2021-45911 exploits a mismatch where the second pass writes more data than the first pass allocated, leading to a heap buffer overflow. Fixing it requires a simple bounds check.
A classic heap-based buffer overflow in gif2apng version 1.9 allows attackers to cause a denial of service or potentially execute arbitrary code via a crafted GIF file. The vulnerability stems from a disconnect between the frame counting logic and the frame processing logic.
The Hook: The Two-Pass Trap
Parsing binary file formats is widely considered one of the circles of hell in software development. The GIF format, with its blocks, extensions, and antiquated structure, is particularly treacherous. In an attempt to be efficient, many C utilities employ a "Two-Pass" strategy. Pass 1: Read the file to count items and calculate the total size needed. Pass 2: Allocate exactly that much memory, re-read the file, and copy the data.
It sounds efficient on paper. You avoid the overhead of realloc or linked lists. But this strategy relies on a fatal assumption: that the file looks exactly the same during the second pass as it did during the first, and that your logic for 'counting' is identical to your logic for 'storing'.
Enter gif2apng, a tool designed to convert animated GIFs into the sleeker APNG format. In version 1.9, the developers fell right into the Two-Pass Trap. They counted the frames, allocated a buffer, and then blindly trusted that the number of delay values found in the second pass would match the number of frames found in the first. Spoiler alert: It didn't.
The Flaw: Trust Issues on the Heap
The vulnerability lies deep within gif2apng.cpp's main function. The program iterates through the GIF blocks specifically looking for Image Descriptors (0x2C) to increment a frames counter. Based on this count, it allocates a heap buffer called delays.
delays = (unsigned short *)malloc(frames * 2);So far, so good. If we have 10 frames, we get 20 bytes. But then comes the second pass. The code resets the file pointer and starts over. This time, it looks for Graphic Control Extensions (0xF9), which contain the frame delay information.
Here is the logic failure: The loop uses an index n to track which frame it is currently processing. When it finds a 0xF9 block, it writes the delay to delays[n]. Crucially, it does not check if n is still within the bounds of frames. If a malicious GIF is crafted with more Graphic Control Extensions than Image Descriptors, n keeps incrementing, and the program happily writes 2 bytes of data past the end of the allocated buffer.
The Code: The Smoking Gun
Let's look at the specific lines responsible for this memory corruption. In the vulnerable version 1.9, the code blindly assigns the delay value to the array index.
Vulnerable Code (v1.9):
if (val == 0xF9) {
// ... reading delay bytes ...
if (delay > 1)
delays[n] = delay; // <--- OOB Write here
}
// ... later ...
if (id == 0x2C) {
n++; // Increment index
}The fix is almost embarrassingly simple. The patch introduces a sanity check to ensure we haven't exceeded the buffer size we calculated in pass one.
Fixed Code:
if (val == 0xF9) {
// ... reading delay bytes ...
// The fix: Ensure n is strictly less than frames
if (delay > 1 && n < frames) {
delays[n] = delay;
}
}This simple && n < frames clause closes the vulnerability completely. It's the digital equivalent of checking if the elevator is actually there before stepping into the shaft.
The Exploit: Crafting the Poisoned GIF
To exploit this, we need to desynchronize the two passes. We want frames (Pass 1) to be small, but n (Pass 2) to get large.
- The Setup: Create a valid GIF header.
- The Decoy: Include a single valid Image Descriptor (
0x2C). Pass 1 sees this and setsframes = 1. The program allocatesmalloc(2)(2 bytes). - The Payload: Insert a sequence of Graphic Control Extensions (
0xF9). These blocks tell the renderer how long to wait before the next frame. Crucially,gif2apngprocesses these blocks and assigns their values to thedelaysarray. - The Trigger: The first
0xF9writes todelays[0]. Valid. Then, we insert another0xF9without an intervening Image Descriptor, or imply a second frame that doesn't exist. If the parser logic incrementsnor simply processes the second0xF9as belonging to the next index, it writes todelays[1](offset 2 bytes). This is strictly out-of-bounds for a 2-byte buffer.
By controlling the delay value (the data written) and the number of extra blocks (the offset), an attacker can perform a precise heap overwrite. In the heap implementation used by standard glibc, overwriting the next chunk's size or flags field is the first step toward malloc consolidation attacks that lead to RCE.
The Impact: Why Panic?
While gif2apng isn't exactly the kernel of your operating system, it is a tool likely to be used in automated processing pipelines. Imagine a web service that allows users to upload GIFs and automatically converts them to APNGs for optimization. That is a remote attack surface.
- Denial of Service (DoS): The most likely outcome. Corrupting heap metadata usually results in a
SIGABRTorSIGSEGVwhen the allocator tries to touch that memory again. - Remote Code Execution (RCE): Difficult, but possible. Because the attacker controls the value being written (the 2-byte delay), they have a "write-what-where" primitive, albeit constrained. If they can groom the heap layout effectively, they could overwrite a function pointer or a C++ vtable residing in an adjacent object.
The Fix: Measuring Twice
The remediation is straightforward: Apply the vendor patches. The patch doesn't change the architecture of the tool—it's still a two-pass parser—but it enforces consistency.
If you are maintaining a legacy fork of gif2apng or similar tools, the lesson is clear: Never trust your first pass. If you allocate memory based on a count, you must explicitly guard every write access against that count during processing. Assertions are nice for debug builds; explicit bounds checks are mandatory for production.
Technical Appendix
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
gif2apng gif2apng Project | <= 1.9 | 1.9+srconly-3+deb11u1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-122 (Heap-based Buffer Overflow) |
| CVSS v3.1 | 7.8 (High) |
| Vector | CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| Attack Vector | Local (User Interaction Required) |
| EPSS Score | 0.16% |
| Exploit Status | PoC Available |
MITRE ATT&CK Mapping
A heap-based buffer overflow occurs when a program writes to a memory address on the heap that is outside the bounds of the allocated buffer.
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.