llvm / llvm/llvm-project

Bug Report: NULL Dereference and Function Fallthrough in libFuzzer Fork Mode

Open
#201,274 1 comment 0 reactions 0 assignees View on GitHub
compiler-rt:fuzzer
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Overview
A critical regression exists in the libFuzzer runtime provided with recent LLVM/Clang 22 snapshots. When running in fork mode (`-fork=N`), the parent fuzzer process can encounter a `SEGV` due to a NULL pointer dereference. This is caused by a machine code generation defect in `fuzzer::GlobalEnv::RunOneMergeJob` where a code path lacks a termination instruction, leading to an illegal fallthrough into the next function in memory.

## Environment
- **Compiler:** Clang 22 Snapshot (e.g., `libclang-rt-22-dev_22~++20251026052801`)
- **Library:** `libclang_rt.fuzzer-x86_64.a`
- **Object File:** `FuzzerFork.cpp.o`
- **Platform:** Linux x86_64

## Root Cause Analysis
The issue resides in the pre-compiled `RunOneMergeJob` function. The compiler uses information from an `assert` to perform aggressive optimization on a code path handling small input files, but fails to emit a valid return/jump instruction for that branch.

### 1. Source Context (`FuzzerFork.cpp`)
In `RunOneMergeJob`, feature files are read and processed:
```cpp
auto FeatureBytes = FileToVector(FeatureFile, 0, false);
assert((FeatureBytes.size() % sizeof(uint32_t)) == 0);
std::vector NewFeatures(FeatureBytes.size() / sizeof(uint32_t));
memcpy(NewFeatures.data(), FeatureBytes.data(), FeatureBytes.size());
```

### 2. Defective Machine Code (`RunOneMergeJob`)
The compiler generates a specialized branch for `size <= 3`. Based on the `assert`, it concludes that if `size <= 3`, the only valid value is `0`. It then hardcodes the destination to NULL (`xor edi, edi`) but omits the function termination:

```assembly
.text._ZN6fuzzer9GlobalEnv14RunOneMergeJobEPNS_7FuzzJobE:0000000000006296 xor edi, edi ; dest = NULL (NewFeatures.data())
.text._ZN6fuzzer9GlobalEnv14RunOneMergeJobEPNS_7FuzzJobE:0000000000006298 mov rdx, r14 ; n = size
.text._ZN6fuzzer9GlobalEnv14RunOneMergeJobEPNS_7FuzzJobE:000000000000629B call memcpy ; Call interceptor
; --- MISSING RET/JMP ---
; Function ends here without a return instruction.
```

### 3. The Crash (`secondsSinceProcessStartUp`)
When `memcpy` returns (which it does safely if `size == 0`), execution falls through directly into the first instruction of the next function in memory, `fuzzer::GlobalEnv::secondsSinceProcessStartUp`. This function expects a valid `this` pointer in `rdi`, but `rdi` is `0` from the previous `xor`.

```assembly
.text._ZNK6fuzzer9GlobalEnv26secondsSinceProcessStartUpEv:00000000000062A0 push rbp
...
.text._ZNK6fuzzer9GlobalEnv26secondsSinceProcessStartUpEv:00000000000062A6 mov rbx, rdi ; rbx = 0
.text._ZNK6fuzzer9GlobalEnv26secondsSinceProcessStartUpEv:00000000000062A9 call system_clock::now
.text:00000000007AC48E 48 2B 83 78 01 00 00 sub rax, [rbx+178h] ; CRASH: [0 + 0x178]
```

## Trigger Mechanism
The crash is triggered by an **empty feature file (size 0)** on disk. Such files can be left behind if a libFuzzer child process is killed (e.g., via SIGKILL or OOM) after creating a file but before writing to it. While the source code intends to handle size 0 (via `memcpy(NULL, src, 0)`), the missing `ret` instruction in the binary makes this case fatal.

## Reproduction Steps
1. Compile any fuzzer using the buggy Clang 22 runtime.
2. Run in fork mode: `./fuzzer -fork=16 corpus_dir`.
3. Simulate a child failure by creating an empty feature file in the temporary directory used for merging:
`touch /tmp/libFuzzerTemp.../Features/some_input_name`
4. The parent process will crash with a `SEGV` on address `0x178` when it attempts to merge that job.

## Recommended Fix
Ensure that the compiler-generated paths for small/zero-size buffers in `RunOneMergeJob` are properly terminated with a return to the caller or a jump to the loop continuation point.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.