Repository navigation
Description
Summary
Malformed WOFF2 input can trigger a misaligned 32-bit load in woff2::ComputeULongSum() while reconstructing a font. The function iterates over a byte buffer and, on little-endian builds, directly casts buf + i to const uint32_t *. The buffer offset is derived from WOFF2 table metadata and is not guaranteed to be 4-byte aligned.
I reproduced this with the src/convert_woff2ttf_fuzzer.cc libFuzzer harness. The tested build identifies as woff2 v1.0.2. On x86_64 this is reported by UBSan; on strict-alignment architectures, the same pattern can be a runtime fault.
This is distinct from the other WOFF2 misaligned-load issue in BrotliLoad32LE(): this report is for the checksum helper reached while reconstructing table data.
Details
The relevant code in src/woff2_common.cc is:
uint32_t ComputeULongSum(const uint8_t* buf, size_t size) {
uint32_t checksum = 0;
size_t aligned_size = size & ~3;
for (size_t i = 0; i < aligned_size; i += 4) {
#if defined(WOFF_LITTLE_ENDIAN)
uint32_t v = *reinterpret_cast<const uint32_t*>(buf + i);
checksum += (((v & 0xFF) << 24) | ((v & 0xFF00) << 8) |
((v & 0xFF0000) >> 8) | ((v & 0xFF000000) >> 24));The decode path reaches this checksum helper from src/woff2_dec.cc:
checksum = ComputeULongSum(transformed_buf + table.src_offset,
table.src_length);aligned_size rounds the length down to a multiple of four, but it does not guarantee that the starting pointer buf is aligned. If table.src_offset is not 4-byte aligned, every typed uint32_t load in this loop is undefined behavior.
The observed project stack is:
woff2::ComputeULongSum()
woff2::ReconstructFont()
woff2::ConvertWOFF2ToTTF()
LLVMFuzzerTestOneInput()
PoC
- Please rename
reproducer.poc.woff2.txttoreproducer.poc.woff2
Build and reproduction
woff2 version tested: v1.0.2
OS: Linux x86_64
Compiler: clang
Harness: src/convert_woff2ttf_fuzzer.cc
One working sanitizer configuration is:
export CC=clang
export CXX=clang++
export CFLAGS="-fsanitize=address,undefined,fuzzer-no-link -fno-sanitize-recover=all -fno-omit-frame-pointer -O2 -g"
export CXXFLAGS="-fsanitize=address,undefined,fuzzer-no-link -fno-sanitize-recover=all -fno-omit-frame-pointer -O2 -g"
export ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:symbolize=1"
export UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1"Build woff2 with the fuzz harness and run:
./convert_woff2ttf_fuzzer <poc>Driver source
src/convert_woff2ttf_fuzzer.cc:
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <woff2/decode.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
std::string buf;
woff2::WOFF2StringOut out(&buf);
out.SetMaxSize(30 * 1024 * 1024);
woff2::ConvertWOFF2ToTTF(data, size, &out);
return 0;
}Sanitizer report
src/woff2_common.cc:23:18: runtime error: load of misaligned address 0x6030000000d7 for type 'const uint32_t' (aka 'const unsigned int'), which requires 4 byte alignment
#0 in woff2::ComputeULongSum src/woff2_common.cc:23:18
#1 in woff2::ReconstructFont src/woff2_dec.cc:956:20
#2 in woff2::ConvertWOFF2ToTTF src/woff2_dec.cc:1348:9
#3 in LLVMFuzzerTestOneInput src/convert_woff2ttf_fuzzer.cc
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src/woff2_common.cc:23:18 in
SUMMARY: libFuzzer: deadly signal
Activity
iceray00 commentedon Sep 21, 2026
Gentle ping on this one, @eustas — is there anything I can add to make it actionable? The report has the sanitizer trace, the affected line and a reproducer through
src/convert_woff2ttf_fuzzer.cc(v1.0.2).If a patch is more useful than a report, the fix is small: read the 32-bit word with a byte-wise /
memcpyload inComputeULongSum()— the same shape as theBrotliLoad32LE()case in #202 — which keeps the checksum byte-identical on little-endian hosts while removing the unaligned cast.Happy to send a PR if you would rather review a patch.
iceray00 commentedon Sep 22, 2026
Fixed on master by 23a34ad ("Fix undefined type-punning when loading/storing
words"), which replaced the typed
uint32_tload inComputeULongSum()withbyte-wise assembly; the same commit removed the matching
Store16()accessin
src/store_bytes.h. Rebuilding atfb9c3379shows the reproducer fromthis report no longer trips UBSan, so this can be closed as fixed.