CVE-2026-11299
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/woff2/src/buffer.h |
modified | |
forthird_party/woff2/src/font.cc |
modified |
Files Changed
third_party/woff2/README.chromiumthird_party/woff2/src/buffer.hthird_party/woff2/src/font.cc
Patch
From 71076877928f699ea4a28da27335d3d279833c72 Mon Sep 17 00:00:00 2001 From: Ben Wagner <[email protected]> Date: Fri, 17 Apr 2026 07:50:05 -0700 Subject: [PATCH] Roll woff2 https://github.com/google/woff2/compare/f83a17739086a433be544eb16258b1c4f64a35f9..1c69169e9e1811dccd6c54c532fedda300233968 Bug: 502598424 Change-Id: Ia62c093d60420e8a8922f80c1514edb21988b9b4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7773047 Reviewed-by: Ben Wagner <[email protected]> Reviewed-by: Dominik Röttsches <[email protected]> Auto-Submit: Ben Wagner <[email protected]> Commit-Queue: Ben Wagner <[email protected]> Cr-Commit-Position: refs/heads/main@{#1616560} --- diff --git a/third_party/woff2/README.chromium b/third_party/woff2/README.chromium index a1b605e..9f00ca1 100644 --- a/third_party/woff2/README.chromium +++ b/third_party/woff2/README.chromium @@ -1,7 +1,7 @@ Name: woff2 URL: https://github.com/google/woff2 -Version: f83a17739086a433be544eb16258b1c4f64a35f9 -Revision: f83a17739086a433be544eb16258b1c4f64a35f9 +Version: 1c69169e9e1811dccd6c54c532fedda300233968 +Revision: 1c69169e9e1811dccd6c54c532fedda300233968 Update Mechanism: Manual License: MIT License File: LICENSE @@ -15,5 +15,3 @@ Local Modifications: - BUILD.gn: Added. -- Add `#include <stdint.h>` to `include/woff2/output.h`. - This should be removed if https://github.com/google/woff2/pull/176 is merged. diff --git a/third_party/woff2/src/buffer.h b/third_party/woff2/src/buffer.h index 7240e51..f9199c744 100644 --- a/third_party/woff2/src/buffer.h +++ b/third_party/woff2/src/buffer.h @@ -82,7 +82,7 @@ } inline bool ReadU8(uint8_t *value) { - if (offset_ + 1 > length_) { + if (length_ < 1 || offset_ > length_ - 1) { return FONT_COMPRESSION_FAILURE(); } *value = buffer_[offset_]; @@ -91,7 +91,7 @@ } bool ReadU16(uint16_t *value) { - if (offset_ + 2 > length_) { + if (length_ < 2 || offset_ > length_ - 2) { return FONT_COMPRESSION_FAILURE(); } std::memcpy(value, buffer_ + offset_, sizeof(uint16_t)); @@ -105,7 +105,7 @@ } bool ReadU24(uint32_t *value) { - if (offset_ + 3 > length_) { + if (length_ < 3 || offset_ > length_ - 3) { return FONT_COMPRESSION_FAILURE(); } *value = static_cast<uint32_t>(buffer_[offset_]) << 16 | @@ -116,7 +116,7 @@ } bool ReadU32(uint32_t *value) { - if (offset_ + 4 > length_) { + if (length_ < 4 || offset_ > length_ - 4) { return FONT_COMPRESSION_FAILURE(); } std::memcpy(value, buffer_ + offset_, sizeof(uint32_t)); @@ -130,7 +130,7 @@ } bool ReadTag(uint32_t *value) { - if (offset_ + 4 > length_) { + if (length_ < 4 || offset_ > length_ - 4) { return FONT_COMPRESSION_FAILURE(); } std::memcpy(value, buffer_ + offset_, sizeof(uint32_t)); @@ -139,7 +139,7 @@ } bool ReadR64(uint64_t *value) { - if (offset_ + 8 > length_) { + if (length_ < 8 || offset_ > length_ - 8) { return FONT_COMPRESSION_FAILURE(); } std::memcpy(value, buffer_ + offset_, sizeof(uint64_t)); @@ -151,7 +151,13 @@ size_t offset() const { return offset_; } size_t length() const { return length_; } - void set_offset(size_t newoffset) { offset_ = newoffset; } + bool set_offset(size_t newoffset) { + if (newoffset > length_) { + return FONT_COMPRESSION_FAILURE(); + } + offset_ = newoffset; + return true; + } private: const uint8_t * const buffer_; diff --git a/third_party/woff2/src/font.cc b/third_party/woff2/src/font.cc index a45153e..dcef9846 100644 --- a/third_party/woff2/src/font.cc +++ b/third_party/woff2/src/font.cc @@ -155,7 +155,9 @@ std::map<uint32_t, Font::Table*> all_tables; for (const auto offset : offsets) { - file->set_offset(offset); + if (!file->set_offset(offset)) { + return FONT_COMPRESSION_FAILURE(); + } Font& font = *font_it++; if (!ReadCollectionFont(file, data, len, &font, &all_tables)) { return FONT_COMPRESSION_FAILURE();
Original Bug Report
Heap OOB read on 32-bit via integer wraparound in Buffer::ReadU8 bounds check with crafted TTC font offset.
Summary: Heap OOB read on 32-bit via integer wraparound in Buffer::ReadU8 bounds check with crafted TTC font offset.
Program: OSS VRP
URL: https://github.com/google/woff2
Vulnerability type: Memory Corruption (in a sandboxed process)
Details
BUG LOCATION
File: src/buffer.h Function: Buffer::ReadU8() Line: 85
The vulnerable bounds check is: if (offset_ + 1 > length_) { return FONT_COMPRESSION_FAILURE(); }
Combined with the unchecked offset setter at line 154: void set_offset(size_t newoffset) { offset_ = newoffset; }
And the call site in src/font.cc line 158: file->set_offset(offset);
Where offset comes from untrusted TTC font header data.
Technical Details:
The Buffer class (buffer.h) stores its read position as offset_ of type size_t. The set_offset method at line 154 allows setting this to any value without bounds validation: void set_offset(size_t newoffset) { offset_ = newoffset; }
The ReadTrueTypeCollection function in font.cc line 158 reads uint32_t offset values from the TTC header (attacker-controlled) and passes them directly to set_offset.
On 32-bit platforms where size_t is 32 bits, the attacker sets the TTC offset to 0xFFFFFFFF. When ReadU8 is subsequently called, its bounds check at buffer.h line 85 evaluates: offset_ + 1 > length_. With offset_ = 0xFFFFFFFF, the addition 0xFFFFFFFF + 1 wraps to 0 in unsigned 32-bit arithmetic. The comparison becomes 0 > length_, which is false for any non-empty buffer. The bounds check passes.
The function then executes buffer_[0xFFFFFFFF], reading 1 byte at an address 4 GB past the start of the font data. The same wraparound affects ReadU16 (offset_ + 2), ReadU32 (offset_ + 4), ReadU24 (offset_ + 3), ReadR64 (offset_ + 8), and ReadTag (offset_ + 4).
The Buffer::Read method at line 73-74 has a secondary check that is immune to wraparound: (offset_ > length_ - n_bytes). But all the typed Read functions use their own simpler check that is vulnerable.
Attack scenario
The woff2 library was used in Chrome for rendering web fonts and was deployed on billions of 32-bit Android devices. A malicious TTC font served from a web page or embedded in a document could trigger the OOB read in the font loading path, leaking process memory contents.
Since font data is typically loaded early in the rendering pipeline, the heap layout is relatively deterministic, making heap grooming feasible. The leaked memory can reveal ASLR base addresses, heap metadata, or adjacent object contents. With ASLR defeated, a second- stage font table with crafted glyph data can exploit the signed integer overflow in glyph coordinate accumulation (glyph.cc line 154) to achieve an arbitrary write, completing the RCE chain.
EXPLOIT CHAIN TO REMOTE CODE EXECUTION
-
Attacker crafts a TrueType Collection (.ttc) file containing a TTC header with an offset table entry set to 0xFFFFFFFF.
-
Victim opens the file on a 32-bit platform. This includes 32-bit Android devices (which were common when woff2 was actively deployed in Chrome), 32-bit Windows systems, and any 32-bit Linux system using a woff2-based font renderer.
-
The ReadTrueTypeCollection function reads the offset from the TTC header and calls file->set_offset(0xFFFFFFFF).
-
The subsequent ReadTrueTypeFont call invokes ReadU16 to read the font version. ReadU16 checks offset_ + 2 > length_, which is 0xFFFFFFFF + 2 = 1 > length_. For any font larger than 1 byte, this check passes.
-
ReadU16 reads 2 bytes from buffer_[0xFFFFFFFF], which is 4 GB past the buffer start. This reads from whatever is mapped at that address in the process, typically heap metadata, stack data, or shared library contents.
-
The read data is interpreted as a font version number. If it happens to match a valid version (0x00010000 or “OTTO”), parsing continues with the corrupted offset, causing further reads at addresses past the buffer.
-
For RCE, the attacker chains this with the font processing pipeline. The reads at controlled offsets can leak heap metadata (exposing ASLR base addresses). With the leaked addresses, a second-stage crafted font table causes the glyph reconstruction code to write to a controlled address, achieving arbitrary write.
-
The signed integer overflow in glyph coordinate accumulation (glyph.cc line 154) provides an additional primitive where prev_x wraps via undefined behavior, causing miscomputed glyph coordinates that can corrupt font rendering data structures.