Skip to content

UBSan: misaligned 32-bit load in ComputeULongSum #203

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

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