8000 UBSan: misaligned 32-bit load in ComputeULongSum · Issue #203 · google/woff2 · GitHub
[go: up one dir, main page]

Skip to content

UBSan: misaligned 32-bit load in ComputeULongSum #203

Open

Description

@iceray00

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

reproducer.poc.woff2.txt

  • Please rename reproducer.poc.woff2.txt to reproducer.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

  1. iceray00 commented on Sep 21, 2026

    @iceray00
    Author

    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 / memcpy load in ComputeULongSum() — the same shape as the BrotliLoad32LE() 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.

  2. iceray00 commented on Sep 22, 2026

    @iceray00
    Author

    Fixed on master by 23a34ad ("Fix undefined type-punning when loading/storing
    words"), which replaced the typed uint32_t load in ComputeULongSum() with
    byte-wise assembly; the same commit removed the matching Store16() access
    in src/store_bytes.h. Rebuilding at fb9c3379 shows the reproducer from
    this report no longer trips UBSan, so this can be closed as fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0