CVE-2025-31344: giflib Heap-based Buffer Overflow
Root cause analysis of CVE-2025-31344 (giflib DumpScreen2RBG heap-based buffer overflow -> OOB read)
Summary
A malformed GIF using invalid palette indexes can drive giflib's DumpScreen2RGB function into an out-of-bounds heap access during RGB expansion.
CVE: CVE-2025-31344
Product: giflib (image decoding library and utilities)
Vulnerability: Heap-based buffer overflow (out-of-bounds read) in DumpScreen2RGB
Affected Versions: giflib 5.2.2 (and below)
Fixed In: 5.2.2-r1 (distro backports; no new upstream release)
CVSS Severity: 7.3 (high)
CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H
Required Privilege: None (local file parsing)
NVD Published: April 14, 2025
A crafted GIF can embed out-of-range palette indices that cause DumpScreen2RGB to read beyond the bounds of the colour map. This causes an out-of-bounds heap access during RGB frame expansion and can result in process crashes, or minor information disclosure if the out-of-bounds bytes propagate into RGB output.
Introduction
In the last two posts I focused on web-facing vulnerabilities where patch diffing was mostly source-level: first a missing authentication middleware in Hoverfly and then a more complex file-handling bug in Tomcat's Partial PUT logic. For this writeup I wanted to switch to a native C target but keep things simple before moving on to something that requires binary reversing.
giflib is a small and widely packaged image library. CVE-2025-31344 is a straight-forward heap out-of-bounds bug caused by a missing bounds check in DumpScreen2RGB. The patch is tiny, but it still makes a good warm-up for C code review and basic patch analysis before jumping into a more complex binary diffing target in the next post.
CVE-2025-31344: Heap-based Buffer Overflow
This vulnerability is a heap-based buffer overflow that manifests as an out-of-bounds read when giflib processes a frame whose pixel data references colour-map entries outside the declared palette size. DumpScreen2RGB trusts the frame's colour indexes and uses them directly when expanding indexed pixels into 24-bit RGB output. If the GIF declares a global or local colour map of size N but uses an index ≥ N, giflib reads beyond the end of the allocated GifColorType array, resulting in a heap out-of-bounds access on the colour map buffer.
Patch Review
giflib has no upstream fixed release for this issue, so there is no version jump like 5.2.2 -> 5.2.3 to diff directly. Instead, vendors have backported a small patch that adds a missing bounds check inside DumpScreen2RGB.
Interestingly, this exact bug was already patched once before (CVE-2022-28506), but only in the OneFileFlag branch (true) that writes via a single BufferP++ pointer. The multi-buffer (Buffers[3]) branch (false) remained vulnerable, which is what this new CVE addresses.
Root Cause Analysis
At a low level, the bug is just a missing bounds check in the palette lookup inside DumpScreen2RGB. The function walks the logical screen buffer, takes each pixel index from GifRow[j], looks it up in the active colour map, and writes the RGB components into one of three output buffers.
ColorMapObject tracks the current palette in two fields:
ColorCount– number of entries in the paletteColors– heap-allocated array ofGifColorType[ColorCount]
In the multi-buffer branch of DumpScreen2RGB (the else case that uses Buffers[3]), the core loop in 5.2.2 looks like this:
There is no validation that GifRow[j] is less than ColorMap->ColorCount before using it as an index into Colors. If a GIF declares a palette with N entries but the compressed image data contains a value >= N, ColorMapEntry ends up pointing past the end of the heap buffer. Reading ColorMapEntry->Red, Green and Blue at that address causes a heap out-of-bounds read. Built with ASan (AddressSanitizer), a vulnerable gif2rgb build reports this as a heap-buffer-overflow in DumpScreen2RGB at the ColorMap->Colors[GifRow[j]] access.
This pattern was partially fixed in CVE-2022-28506 by adding a bounds check in the OneFileFlag branch that writes via *BufferP++. The multi-buffer path shown above stayed unchanged and could still index past the end of the palette. CVE-2025-31344 closes that gap by adding the same GifRow[j] >= ColorMap->ColorCount guard in this branch before the ColorMapEntry lookup.
Exploitability
Although this is classified as a heap-based buffer overflow, the practical impact is limited by how the code behaves. The out-of-bounds pointer is only used for reading Red, Green and Blue values from ColorMapEntry. The destination writes (Buffers[0][j], Buffers[1][j], Buffers[2][j]) remain in-bounds and do not corrupt adjacent heap metadata.
In practice this leads to:
Crashes when
gif2rgbprocesses a malicious GIFMinor information disclosure through crash logs or sanitiser output
Denial-of-service for automation pipelines that batch-convert GIFs
Many Linux distributions also note that they do not ship gif2rgb at all, or treat this as a low-risk local issue. It would only become a more serious vulnerability if the same code path appeared in a long-running, network-facing application that processes untrusted GIFs. In the context of the standalone gif2rgb utility, the realistic impact is restricted to process termination on malformed input.
PoC
First, grab a copy of gif2rgb from the vulnerable giflib package.
Make sure it's working as expected.
Here's a PoC that builds a minimal 1×1 GIF with a 2-entry global colour table, then crafts the compressed image data so that gif2rgb eventually sees a palette index that is out of range. The header and logical screen descriptor declare a 1×1 image and set the global colour table flag with a size of 2 entries; we then emit exactly two RGB triplets (black and white), so ColorMap->ColorCount == 2 and valid indices are 0 and 1. The image descriptor that follows covers the full 1×1 frame, but the LZW payload (codes_bits packed into b0/b1) is chosen so that the decompressor reconstructs a pixel value of 3 at decode time. That value is stored into the screen buffer and later used as GifRow[j] inside DumpScreen2RGB. Because the vulnerable build never checks GifRow[j] < ColorMap->ColorCount in the multi-buffer branch, it blindly computes ColorMap->Colors[3] on a palette of size 2, stepping 1 byte past the allocated GifColorType[2] array and triggering the ASan heap-buffer-overflow.
Demo
Run the PoC against the vulnerable library and see the ASan complain about the heap buffer overflow.
Let's apply the patch and retest. Open gif2rgb.c and insert the bounds check in DumpScreen2RGB() and recompile.
After applying the patch, the crafted GIF is still detected as malformed, but this time the new bounds check in DumpScreen2RGB bails out cleanly instead of walking past the end of the palette.
Remediation
Upgrade to a distro build that includes the backported fix e.g. giflib 5.2.2-r1 on OpenMandriva, openEuler's patched build, or any vendor package released after April 2025.
If you maintain your own build of giflib, apply a vendor patch and rebuild the library.
If you consume giflib indirectly (image viewers, converters, thumbnail generators), update those packages so they link against a patched giflib.
Avoid processing untrusted GIF files with unpatched giflib utilities (
gif2rgb,giftool) until your environment has been upgraded.
Conclusion
This is a small but clear example of how a missing bounds check in a tight image-processing loop can lead to a heap out-of-bounds access. The patch itself is small, but the bug highlights long-standing inconsistencies in how giflib validates palette indexes across its different output paths. It's also an interesting example of a patch gone wrong, i.e. the original "fix" was only applied to one of the conditions, while the other remained vulnerable for many years.
Although the practical impact is limited to the giflib utilities that invoke DumpScreen2RGB (most commonly the gif2rgb tool), it is still useful as a warm-up C target for vulnerability analysis. The next writeup will focus on a target where patch diffing and binary reversing are actually required to understand the change, rather than a simple one-line guard being the entire fix.
References
Last updated