Skip to content

feat(fuzz): add libFuzzer-based fuzzing infrastructure#5314

Open
lgritz wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
lgritz:lg-fuzz
Open

feat(fuzz): add libFuzzer-based fuzzing infrastructure#5314
lgritz wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
lgritz:lg-fuzz

Conversation

@lgritz

@lgritz lgritz commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Add continuous fuzz testing for OpenImageIO's image format readers, so malformed/adversarial inputs get caught by CI instead of by users.

  • src/fuzz/: a single dynamic-dispatch harness, oiio_fuzz_image, covers all format plugins by discovering formats at runtime and targeting one per process (env var, argv[0], or --format=). New format plugins get fuzz coverage automatically -- no harness to write, just a seed corpus directory, enforced by a CI lint job.

  • The per-input read loop (chunked scanline/tile reads, subimage/MIP iteration, OOM-bounded) lives in the library itself as OIIO::pvt::test_read_image()/test_read_all_images(), shared with a new oiiotool --testread debugging flag. This oiiotool --testread command is useful in its own right for testing whether a file can be fully read without needing the pixels stored.

  • .github/workflows/fuzz.yml runs a 29-format parallel matrix nightly (and on pushes to "fuzz"-named branches), tiered by risk: 1hr/job for the 10 most important formats, 30min for the rest, with per-format corpus caching across runs and crash-artifact upload.

  • Seed corpora: a handful of formats with no other source commit a synthetic seed; everything else is pulled from testsuite/ and companion image repos (including oiio-images) at fuzz time rather than committed wholesale. So the fuzzing seeds will also grow as we check in more test cases.

  • Gated behind OIIO_BUILD_FUZZ_TARGETS=OFF; requires upstream clang (gcc and Apple's clang are skipped with a warning, not a hard build failure).

  • docs/dev/fuzzing.md covers the local build/run/reproduce/minimize workflow, and docs/dev/specs/001-image-fuzzing archives all the spec-kit design history and rationale.

OSS-Fuzz onboarding (project.yaml/Dockerfile/build.sh) is scoped but not yet built -- the harness's format-dispatch and $LIB_FUZZING_ENGINE linkage are already in place for whenever that's picked up.

Assisted-by: Claude Code / Claude Sonnet 5

@lgritz

lgritz commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator Author

BTW, the reason some fuzz tests are failing is that there are still several PRs still in flight (and a couple fixes for which I haven't yet submitted PRs at all) that fix things being spotted by the fuzzing. When I do a test push of the fuzzing mechanism AND all the fixes I've implemented to far, it tends to pass. So the tests will gradually all go green as we continue to merge fixes.

@jessey-git

Copy link
Copy Markdown
Contributor

Out of curiosity, do you know how many of your fixed issues these past weeks would've been caught in a 30 minute session? Just want to get a sense if some of the issues were quickly discovered or took multiple hours to uncover etc. which also put in perspective the current CI limits

@lgritz

lgritz commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Out of curiosity, do you know how many of your fixed issues these past weeks would've been caught in a 30 minute session? Just want to get a sense if some of the issues were quickly discovered or took multiple hours to uncover etc. which also put in perspective the current CI limits

Yeah, so in the last ~3 weeks, we've had 17 confidentially reported vulnerabilities (3 of which are published and in the releases we did at the beginning of July, 9 of which are still not "published" because we haven't done releases that contain the patches, and 5 more that I closed after convincing the reporter that they weren't really exploitable).

In that time, the patches containing fixes include: 42 PRs merged (holy cow!), 8 PRs still currently in review, and probably another 4 or so that I still haven't submitted PRs for yet because they need to be "on top of" other PRs still in flight.

So that's around 54 or so PRs, of which, as I said, 17 were the direct result of the external reporting, leaving 37 found as a result of my own fuzzing harness that is the subject of this PR we are discussing right now. In other words, despite having a heavy period of external reporting, most of the activity you've been seeing has been the result of our own fuzzing. I was trying to fix all I could before it was externally found, when right in the middle of that there was a big bunch of (totally different!) external ones.

I think that one or two of the externally reported problems were ones where I had just found the same bug with my fuzzing and already had a patch. Nice coincidence. But that was a small minority. And once I patched the other externally reported ones, of course they were no longer available for the fuzzer to stumble across, so it's hard to evaluate whether I would have found those very same bugs in fairly short order with my own fuzzer.

As for the time... as soon as I got this harness written, I started with very short runs -- like 3 minutes per format at first, so it would run fast. Even that was turning up bugs immediately, on every single run, across multiple formats. As I got more and more patches written, and would re-push the collection of all my patches (PR'd and not yet PR'd, plus the fuzzer), the crashes would be less frequent, so then I would throttle up the amount of time on the runs: 5 mins, 10 mins, etc. Eventually I got up to 60/30 minutes per format (longer for the "more important" ones) with it mostly passing every time, so I think I have found most of the low-hanging fruit now, though I expect the nightly runs to continue exploring the space and turning up occasional things to fix (but hopefully at a pace that's easier for me [and you!] to keep up with).

60 minutes is around the practical limit, because I don't think GHA is super happy with any individual job running much more than an hour, plus we have almost 20 jobs launched to cover all the formats.

@lgritz

lgritz commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

That was a very wordy answer!

The short and more direct answer to your question is: it took only a few minutes of fuzzing to turn up the first couple of bugs for each file format!

Add continuous fuzz testing for OpenImageIO's image format readers, so
malformed/adversarial inputs get caught by CI instead of by users.

- src/fuzz/: a single dynamic-dispatch harness, oiio_fuzz_image,
  covers all format plugins by discovering formats at runtime and
  targeting one per process (env var, argv[0], or --format=). New
  format plugins get fuzz coverage automatically -- no harness to
  write, just a seed corpus directory, enforced by a CI lint job.

- The per-input read loop (chunked scanline/tile reads, subimage/MIP
  iteration, OOM-bounded) lives in the library itself as
  OIIO::pvt::test_read_image()/test_read_all_images(), shared with a
  new `oiiotool --testread` debugging flag. This `oiiotool --testread`
  command is useful in its own right for testing whether a file can be
  fully read without needing the pixels stored.

- .github/workflows/fuzz.yml runs a 29-format parallel matrix nightly
  (and on pushes to "fuzz"-named branches), tiered by risk: 1hr/job
  for the 10 most important formats, 30min for the rest, with
  per-format corpus caching across runs and crash-artifact upload.

- Seed corpora: a handful of formats with no other source commit a
  synthetic seed; everything else is pulled from testsuite/ and
  companion image repos (including oiio-images) at fuzz time rather
  than committed wholesale. So the fuzzing seeds will also grow as we
  check in more test cases.

- Gated behind OIIO_BUILD_FUZZ_TARGETS=OFF; requires upstream clang
  (gcc and Apple's clang are skipped with a warning, not a hard build
  failure).

- docs/dev/fuzzing.md covers the local build/run/reproduce/minimize
  workflow, and docs/dev/specs/001-image-fuzzing archives all the
  spec-kit design history and rationale.

OSS-Fuzz onboarding (project.yaml/Dockerfile/build.sh) is scoped but
not yet built -- the harness's format-dispatch and $LIB_FUZZING_ENGINE
linkage are already in place for whenever that's picked up.

Assisted-by: Claude Code / Claude Sonnet 5

Signed-off-by: Larry Gritz <lg@larrygritz.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants