How I Discovered a Libpng Vulnerability 11 Years After It Was Patched The article describes how the author accidentally rediscovered CVE-2014-9495, an 11-year-old integer overflow vulnerability in the libpng library, while practicing secure code review. The bug occurred when user-controlled values like width and bit_depth caused a memory calculation to overflow a 32-bit integer, potentially leading to a heap buffer overflow, though the patched version now detects and rejects such inputs with an error message. How I Discovered a Libpng Vulnerability 11 Years After It Was Patched Table of Contents Disclaimer: This is NOT a zero-day. This is a learning experience from my journey into secure code review, where I accidentally rediscovered a vulnerability that was patched back in 2014 CVE-2014-9495 . I’m sharing this to help others who are also learning and want to understand how vulnerabilities work in real-world code. Special thanks to Taym The Backstory⌗ I’m currently learning secure code review and wanted to pick a real-world open-source project to practice on. I chose libpng the widely-used C library for handling PNG images. While doign the code review, I found a few interesting things. But for this blog post will fous only on one. It seems like a serious security issue : the code calculate memory based on user-controlled values like width and bit depth , and there weren’t any obvious safety checks in the version I was reviewing. So I tried to crash it and it worked… Well, kind of. Instead of crashing, libpng stopped me in my tracks with an error. That’s when I realized this bug was already discovered and patched… back in 2014 But hey I still learned a lot, and now I’m sharing that story. The sample which Trigger the vulnerable code⌗ Crafted a special PNG file using Python. This PNG had: - A ridiculously large width 0x80000000 , or 2GB - A bit depth of 16 This combination was enough to overflow an internal memory calculation, which should lead to a heap buffer overflow. Here is the script I used to produce the test bad sample : import struct, zlib def chunk type str, data bytes : length = struct.pack " I", len data bytes type encoded = type str.encode "ascii" crc = struct.pack " I", zlib.crc32 type encoded + data bytes & 0xffffffff return length + type encoded + data bytes + crc png = b"\x89PNG\r\n\x1a\n" Dangerous values: 2GB width + 16-bit depth width = 0x80000000 height = 1 bit depth = 16 color type = 2 compression method = 0 filter method = 0 interlace method = 0 ihdr data = struct.pack " IIBBBBB", width, height, bit depth, color type, compression method, filter method, interlace method png += chunk "IHDR", ihdr data png += chunk "IEND", b"" with open "overflow width bitdepth.png", "wb" as f: f.write png print " Malicious PNG created." When I opened this PNG with libpng, here’s what I got: libpng error: PNG unsigned integer out of range libpng error during init io That’s not just a crash that’s libpng detecting something fishy and refusing to process it. Basically: “Not so 1337.” What Was the Actual Bug? Let us break it down. In older versions of libpng, the code responsible for calculating how much memory was needed per image row looked like this: Note: I was reviewing the latest version of libpng at the time of writing v1.6.48 released 2025-04-30 and I still found the same historical logic in the codebase. Specifically, the potentially vulnerable-looking calculation can be seen here: github.com/pnggroup/libpng/blob/libpng16/png.c L1984-L1990 While it is now guarded by validation checks earlier in the processing pipeline, this snippet remains useful to study and understand how such vulnerabilities arise. rowbytes = width bit depth channels + 7 / 8; This line multiplies user-supplied values like width and bit depth. Now imagine: width = 2,147,483,648 2GB bit depth = 16 The multiplication overflows a 32-bit unsigned integer. This silently wraps around to a smaller number, so libpng allocates less memory than needed. That’s a classic vulnerability: Integer overflow ➜ Miscalculated buffer size ➜ Heap buffer overflow The Patch CVE-2014-9495 In 2014, this exact issue was discovered and patched under CVE-2014-9495. Patch Summary: Added range checks before doing any memory calculations Ensured that values like width bit depth channels don’t overflow Returned an error if something looked suspicious Now, the fixed version throws an error like: libpng error: PNG unsigned integer out of range Simplified Patch Logic if width PNG UINT 31 MAX || width bit depth channels PNG SIZE MAX png error png ptr, "Image width is too large for this architecture" ; Patch for the bug is over here : patch My Learning/s: Always audit from source to sink, vulnerable looking code might be safe if it’s validated somewhere else. Integer overflows in C are sneaky No crash doesn’t mean it’s not worth exploring LibPNG or other similar highly audit code will not give 0-day like this Try Harder, lol Wrapping Up If you’re learning security or bug hunting like me, don’t be afraid to dig into old projects and experiment. Even if you don’t find new bugs, you’ll uncover real-world lessons just like I did with this 11-year-old vulnerability. Stay curious. Keep exploring.