UBSan: misaligned 32-bit load in ComputeULongSum
- Dominant language
- C++
- Stars
- 1.8k
- Forks
- 228
- PR merge metrics
- No merged PRs in 30d
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:
```c++
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(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`:
```c++
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:
```text
woff2::ComputeULongSum()
woff2::ReconstructFont()
woff2::ConvertWOFF2ToTTF()
LLVMFuzzerTestOneInput()
```
## PoC
[reproducer.poc.woff2.txt](https://github.com/user-attachments/files/29956384/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:
```sh
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:
```sh
./convert_woff2ttf_fuzzer
```
## Driver source
`src/convert_woff2ttf_fuzzer.cc`:
```c++
#include
#include
#include
#include
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
```text
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
```
Contributor guide
Research direction
Start in src/woff2_common.cc at ComputeULongSum and trace its call from src/woff2_dec.cc: ReconstructFont. Reproduce the UBSan report with the attached PoC and src/convert_woff2ttf_fuzzer.cc using the documented sanitizer configuration. Done means the PoC no longer triggers a misaligned-load report while conversion completes normally.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100