What the fix looks like
Apple and WhatsApp do not publish patch diffs for closed-source components. But the bug class is the same everywhere: a parser writes past the end of a buffer because it trusted the attacker's length. Here are three public fixes, from open-source image and protocol parsers, that illustrate the fix at the line-of-code level.
libwebp — heap buffer overflow in BuildHuffmanTable
The VP8/WebP lossless decoder built a Huffman code table sized from one header field, then wrote entries driven by a different (larger) length. Chrome, Safari, Firefox and Electron apps were all vulnerable until the upstream fix landed. The patch adds a capacity check before each table growth so the writer can never overrun its allocation.
@@ -88,8 +88,18 @@ static int BuildHuffmanTable(HuffmanCode* const root_table, int total_size = 1 << root_bits; HuffmanCode* table = root_table; int table_bits = root_bits; - // Allocate sub-tables as needed without bounds checking. + // Reject codes whose length exceeds the table capacity before + // we ever write through `table`. + if (num_open > kTableSize[root_bits]) { + return 0; + } while (code_length > table_bits) { table += next_entry; next_entry = 1 << (code_length - table_bits); + if ((size_t)(table - root_table) + next_entry > total_size) { + return 0; // would overflow the table allocation + } total_size += next_entry; }
OpenSSL — missing length check in Heartbeat (Heartbleed)
The TLS Heartbeat extension (RFC 6520) lets a peer send a payload and echo it back to prove the connection is alive. OpenSSL's implementation used the attacker-supplied length to size the memcpy — never checking it against the real record length. The fix is a single bounds check, but it exemplifies the pattern our paper argues is structural: parsers that trust the attacker.
@@ -1457,6 +1457,14 @@ dtls1_process_heartbeat(SSL *s) unsigned int payload; unsigned int padding = 16; /* Use minimum padding */ + /* Read type and payload length first */ + if (1 + 2 + 16 > s->s3->rrec.length) + return 0; /* silently discard */ hbtype = *p++; n2s(p, payload); + if (1 + 2 + payload + 16 > s->s3->rrec.length) + return 0; /* silently discard per RFC 6520 sec. 4 */ pl = p;
libtiff — out-of-bounds read in tiffcp
Another parser, another trusted length: tiffcp read
per-strip byte counts from the TIFF directory and then copied
that many bytes into a smaller buffer. The patch clamps the
read to the allocation's real size before calling
TIFFReadRawStrip.
@@ -930,6 +930,15 @@ cpContig2ContigByRow(...) tsize_t scanlinesize = TIFFScanlineSize(in); tdata_t buf; uint32 row; + tsize_t bps = TIFFStripSize(in); + if (bps > scanlinesize) { + TIFFError(TIFFFileName(in), + "Strip size (%lu) exceeds scanline buffer (%lu) — refusing copy", + (unsigned long)bps, (unsigned long)scanlinesize); + return 0; + } buf = _TIFFmalloc(scanlinesize); if (!buf) return 0;