# CFITSIO Fuzzing: Memory Corruptions and a Codex-Assisted Pipeline

Operator-precedence bug in CFITSIO's EFS row-filter length check disables the bounds check, letting crafted filenames strncat past a stack buffer.

- Payout: Research
- Program: CFITSIO
- Write-up by: Doyensec
- Disclosed: 2026-04-19
- Write-up quality: 7/10
- Original: https://blog.doyensec.com/2026/04/20/cfitsio-fuzzing.html
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2026-04-20-cfitsio-fuzzing-html
- Tags: area:oss domain:oss impact:dos kind:research platform:research tech:linux technique:fuzzing technique:source-review vuln:dos vuln:memory-corruption

**Target:** fits_open_file EFS filename parser (ffifile2 row filter handling)
**Root cause:** In ffifile2, the ternary ?: binds looser than +, so the overflow check evaluates as (strlen(rowfilterx) + (ptr2-ptr1 + (*rowfilterx)) ? 4 : 0) > FLEN_FILENAME - 1, always false; the guard never triggers and strncat overflows rowfilterx.
**Steps:**
1. Build CFITSIO 4.6.3 with AFL++/ASAN and a harness that reads a filename string and calls `fits_open_file` read-only.
2. Craft a FITS filename with nested row-filter brackets, e.g. `file.fits[2:f[R:f...]`, so ffifile2 concatenates chunks into rowfilterx.
3. Because the length check is dead code, strncat writes past the stack buffer rowfilterx, producing an ASAN stack-buffer-overflow.
4. Fix: `if (strlen(rowfilterx) + (ptr2 - ptr1 - 1) + ((*rowfilterx) ? 4 : 0) > FLEN_FILENAME - 1)`.
**How found:** Focused fuzzing on the EFS mini-language after generic AFL++ rounds; Codex generated the harness and an EFS token dictionary, AFLtriage clustered crashes, and Codex root-caused the precedence error from the ASAN trace and source.
**Insight:** EFS filenames are a parser mini-language reachable via fits_open_file; audit length guards for ?: precedence traps where the ternary swallows the whole sum, and fuzz filename strings, not just file bytes.

## Proof of concept (copied exactly from the original report)

**Vulnerable rowfilterx overflow check with precedence bug**

```
if (strlen(rowfilterx) + (ptr2-ptr1 + (*rowfilterx)?4:0) > FLEN_FILENAME - 1) {
 free(infile);
 return(*status = URL_PARSE_ERROR);
}
```

**Fixed overflow check with parentheses**

```
if (strlen(rowfilterx) + (ptr2 - ptr1 - 1) + ((*rowfilterx) ? 4 : 0) > FLEN_FILENAME - 1) {
```

