There are millions of tiny bugs all around us, in everything from our desktop applications to the appliances in the kitchen. Hidden, arbitrary conditions that cause unintended outputs and behaviors. There are many ways to find these bugs, but one way we don’t hear about very often is finding a bug in your own code, only to realize someone else made the same mistake. For example, [David Buchanan] found a bug in his multi-threaded PNG decoder and realized that the Apple PNG decoder had the same bug.
PNG (Portable Network Graphics) is an image format just like JPEG, WEBP, or TIFF designed to replace GIFs. After a header, the rest of the file is entirely chunks. Each chunk is prepended by a four-letter identifier, with a few chunks being critical chunks. The essential sections are IHDR (the header), IDAT (actual image data), PLTE (the palette information), and IEND (the last chunk in the file). Compression is via the DEFLATE method used in zlib, which is inherently serial. If you’re interested, there’s a convenient poster about the format from a great resource we covered a while back.
Given that DEFLATE is inherently serial, it’s tricky to format the data apropriately. [David] added special sections called pLLD sections (the lowercase first letter means that it can be safely ignored by decoders that don’t support it). These sections let the decoder know that a given IDAT chunk can safely be deserialized concurrently into x pieces. Apple uses a similar trick with its iDOT chunks. However, there is an issue here. It is possible for decompress(a+b) != decompress(a) + decompress(b) if a ends halfway through a non-compressed block. Since the DEFLATE method uses a window, concating two sections together can produce different results.
Since there are now two possible interpretations of a given PNG, you can craft a PNG so that when decoded serially, it shows one image, and when decoded in parallel, it shows another. Additionally, [David] found a race condition in desktop safari that results in a slightly different image decoded every time. Here’s the PNG being decoded every frame:
[David] wrote a little tool on GitHub to pack two images into one PNG. It reminds us of the old trick Steganography, where secret data was stashed inside an image.
(Header image courtesy of [PawelDecowski]).