Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions .github/workflows/build-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ on:
oiio_python_bindings_backend:
type: string
default: ''
fuzz_format:
type: string
default: ''
description: "If non-empty, run fuzz tests for this format after the build"
fuzz_max_time:
type: string
default: '3600'
description: "Max seconds for the fuzz run (-max_total_time)"
fuzz_corpus_lint:
type: boolean
default: false
description: "If true, verify every compiled-in format has a corpus directory"
secrets:
PASSED_GITHUB_TOKEN:
required: false
Expand Down Expand Up @@ -260,6 +272,52 @@ jobs:
if: inputs.clang_format == '1'
shell: bash
run: src/build-scripts/run-clang-format.bash
- name: Check ABI
if: inputs.abi_check != ''
shell: bash
run: |
src/build-scripts/ci-abicheck.bash ./build abi_standard/build libOpenImageIO libOpenImageIO_Util

- name: Checkout oiio-images for fuzz seeding
if: inputs.fuzz_format != ''
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: AcademySoftwareFoundation/OpenImageIO-images
path: oiio-images
fetch-depth: 1
- name: Restore fuzz corpus cache
if: inputs.fuzz_format != ''
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
key: fuzz-corpus-${{ inputs.fuzz_format }}-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
fuzz-corpus-${{ inputs.fuzz_format }}-${{ github.ref_name }}-
fuzz-corpus-${{ inputs.fuzz_format }}-
path: corpus/${{ inputs.fuzz_format }}
- name: Fuzz
id: fuzz
if: inputs.fuzz_format != '' || inputs.fuzz_corpus_lint
shell: bash
env:
OIIO_FUZZ_FORMAT: ${{ inputs.fuzz_format }}
OIIO_FUZZ_MAX_TIME: ${{ inputs.fuzz_max_time }}
OIIO_FUZZ_CORPUS_LINT: ${{ inputs.fuzz_corpus_lint }}
run: src/build-scripts/ci-fuzztest.bash
- name: Save fuzz corpus cache
if: always() && inputs.fuzz_format != ''
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
key: fuzz-corpus-${{ inputs.fuzz_format }}-${{ github.ref_name }}-${{ github.run_id }}
path: corpus/${{ inputs.fuzz_format }}
- name: Upload fuzz crash artifacts
if: always() && inputs.fuzz_format != '' && steps.fuzz.outcome == 'failure'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: fuzz-crashes-${{ inputs.fuzz_format }}-${{ github.run_id }}
path: crash_${{ inputs.fuzz_format }}_*
if-no-files-found: ignore
retention-days: 30

- name: Code coverage
if: inputs.coverage == '1'
run: src/build-scripts/ci-coverage.bash
Expand All @@ -276,11 +334,6 @@ jobs:
# sonar-scanner --define sonar.cfamily.compile-commands="${BUILD_WRAPPER_OUT_DIR}/compile_commands.json"
time sonar-scanner --define sonar.host.url="${SONAR_SERVER_URL}" --define sonar.cfamily.compile-commands="$BUILD_WRAPPER_OUT_DIR/compile_commands.json" --define sonar.cfamily.gcov.reportsPath="_coverage" --define sonar.cfamily.threads="$PARALLEL"
# Consult https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-cli/ for more information and options
- name: Check ABI
if: inputs.abi_check != ''
shell: bash
run: |
src/build-scripts/ci-abicheck.bash ./build abi_standard/build libOpenImageIO libOpenImageIO_Util
- name: Build Docs
if: inputs.build_docs == '1'
shell: bash
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
# Linux Tests using ASWF-docker containers
#
linux-aswf:
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }}
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') && ! contains(github.ref, 'fuzz-only') }}
name: "${{matrix.desc}}"
uses: ./.github/workflows/build-steps.yml
with:
Expand Down Expand Up @@ -256,7 +256,7 @@ jobs:
# Linux Tests using GHA Ubuntu runners directly
#
linux-ubuntu:
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }}
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') && ! contains(github.ref, 'fuzz-only') }}
name: "${{matrix.desc}}"
uses: ./.github/workflows/build-steps.yml
with:
Expand Down Expand Up @@ -572,7 +572,7 @@ jobs:
# MacOS Tests
#
macos:
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'linux-only') }}
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'fuzz-only') }}
name: "${{matrix.desc}}"
uses: ./.github/workflows/build-steps.yml
with:
Expand Down Expand Up @@ -657,7 +657,7 @@ jobs:
# Windows Tests
#
windows:
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }}
if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') && ! contains(github.ref, 'fuzz-only') }}
name: "${{matrix.desc}}"
uses: ./.github/workflows/build-steps.yml
with:
Expand Down
123 changes: 123 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Copyright Contributors to the OpenImageIO project.
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO

name: Fuzz

on:
schedule:
# Nightly at 10:00 UTC — main fork only (filtered via job condition below)
- cron: "0 10 * * *"
push:
# Run on any branch whose name contains "fuzz"
branches:
- '*fuzz*'
paths:
- 'src/**'
- '.github/workflows/fuzz.yml'
- '.github/workflows/build-steps.yml'
workflow_dispatch:
inputs:
format:
description: "Format name to fuzz (leave empty for all)"
required: false
default: ""

permissions: read-all

# Allow subsequent pushes to the same PR or REF to cancel any previous jobs.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true


jobs:
fuzz:
name: "Fuzz ${{ matrix.format }} (tier ${{ matrix.tier }})"
# Scheduled runs only on the canonical fork; push runs anywhere (branch filter above ensures "fuzz" substring)
if: github.event_name != 'schedule' || github.repository == 'AcademySoftwareFoundation/OpenImageIO'
strategy:
fail-fast: false
matrix:
include:
# Tier 1: complex/high-risk formats — 60 min fuzz window each
- { format: openexr, tier: 1, max_total_time: 3600 }
- { format: tiff, tier: 1, max_total_time: 3600 }
- { format: jpeg, tier: 1, max_total_time: 3600 }
- { format: png, tier: 1, max_total_time: 3600 }
- { format: dpx, tier: 1, max_total_time: 3600 }
- { format: psd, tier: 1, max_total_time: 3600 }
- { format: heif, tier: 1, max_total_time: 3600 }
- { format: jpegxl, tier: 1, max_total_time: 3600 }
- { format: jpeg2000, tier: 1, max_total_time: 3600 }
- { format: raw, tier: 1, max_total_time: 3600 }
# Tier 2: simpler formats — 30 min fuzz window each
- { format: bmp, tier: 2, max_total_time: 1800 }
- { format: cineon, tier: 2, max_total_time: 1800 }
- { format: dds, tier: 2, max_total_time: 1800 }
- { format: dicom, tier: 2, max_total_time: 1800 }
- { format: fits, tier: 2, max_total_time: 1800 }
- { format: gif, tier: 2, max_total_time: 1800 }
- { format: hdr, tier: 2, max_total_time: 1800 }
- { format: ico, tier: 2, max_total_time: 1800 }
- { format: iff, tier: 2, max_total_time: 1800 }
- { format: pnm, tier: 2, max_total_time: 1800 }
- { format: rla, tier: 2, max_total_time: 1800 }
- { format: sgi, tier: 2, max_total_time: 1800 }
- { format: softimage, tier: 2, max_total_time: 1800 }
- { format: targa, tier: 2, max_total_time: 1800 }
- { format: ffmpeg, tier: 2, max_total_time: 1800 }
- { format: webp, tier: 2, max_total_time: 1800 }
- { format: zfile, tier: 2, max_total_time: 1800 }
- { format: openvdb, tier: 2, max_total_time: 1800 }
- { format: ptex, tier: 2, max_total_time: 1800 }
uses: ./.github/workflows/build-steps.yml
with:
runner: ubuntu-latest
nametag: fuzz-linux-clang
container: aswf/ci-oiio:2027
cc_compiler: clang
cxx_compiler: clang++
build_type: Release
cxx_std: 20
python_ver: "3.13"
simd: "avx2,f16c"
pybind11_ver: v3.0.0
skip_tests: '1'
fuzz_format: ${{ matrix.format }}
fuzz_max_time: ${{ matrix.max_total_time }}
setenvs: export SANITIZE=address,undefined
USE_PYTHON=0
OIIO_BUILD_FUZZ_TARGETS=ON
INSTALL_DOCS=OFF
OIIO_BUILD_TOOLS=OFF
OIIO_BUILD_TESTS=OFF
OpenImageIO_BUILD_LOCAL_DEPS="TIFF,libdeflate"

#
# Verify every format reported by oiio_fuzz_image --list-formats has a corpus
# directory in src/fuzz/corpora/. Fails with a clear message when a new
# format plugin is added without a corresponding corpus directory.
#
fuzz-corpus-lint:
name: "Fuzz corpus coverage lint"
if: github.event_name != 'schedule' || github.repository == 'AcademySoftwareFoundation/OpenImageIO'
uses: ./.github/workflows/build-steps.yml
with:
runner: ubuntu-latest
nametag: fuzz-corpus-lint
container: aswf/ci-oiio:2027
cc_compiler: clang
cxx_compiler: clang++
build_type: Release
cxx_std: 20
python_ver: "3.13"
simd: "avx2,f16c"
pybind11_ver: v3.0.0
skip_tests: '1'
fuzz_corpus_lint: true
setenvs: export USE_PYTHON=0
OIIO_BUILD_FUZZ_TARGETS=ON
INSTALL_DOCS=OFF
OIIO_BUILD_TOOLS=OFF
OIIO_BUILD_TESTS=OFF
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ http://github.com/AcademySoftwareFoundation/OpenImageIO
- `src/<FORMAT>.imageio/` : Per-format ImageInput/ImageOutput plugins.
- `src/python/` : Python bindings using pybind11
- `src/<TOOL>` : CLI tools (oiiotool, iinfo, iconvert, maketx, iv)
- `src/fuzz/` : libFuzzer harness (`oiio_fuzz_image`, gated by
`OIIO_BUILD_FUZZ_TARGETS`) and per-format seed corpora; see
`docs/dev/fuzzing.md`
- `testsuite/` : End-to-end/regression tests + reference outputs
- `src/cmake/`, `CMakeLists.txt` : Build system
- `.github/workflows/ci.yml` : GitHub Actions CI
- `.github/workflows/fuzz.yml` : Nightly/on-demand fuzz CI (per-format matrix)
- `src/build-scripts` Helper scripts used for build & CI
- `src/doc/` : User manual source (+ Doxygen comments in the public headers)
- `docs/dev/` : Developer documentation
Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@ if (NUKE_FOUND)
add_subdirectory (src/nuke/txWriter)
endif ()

set_option (OIIO_BUILD_FUZZ_TARGETS "Build libFuzzer fuzz targets (requires clang)" OFF)
if (OIIO_BUILD_FUZZ_TARGETS)
add_subdirectory (src/fuzz)
endif ()

# install pkgconfig file
if (NOT MSVC)
configure_file(src/build-scripts/OpenImageIO.pc.in "${CMAKE_BINARY_DIR}/OpenImageIO.pc" @ONLY)
Expand Down
12 changes: 12 additions & 0 deletions docs/dev/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ an image file), `iconvert` (which converts between different file formats),
and `maketx` (which generates tiled mipmaps in an efficient arrangement for
texture mapping in a renderer).

## Fuzzing

`src/fuzz/` holds a libFuzzer-based harness (`oiio_fuzz_image`) that exercises
every compiled-in format reader's `ImageInput` implementation against
malformed input, dispatching to a single format per process at runtime rather
than building one binary per format. It shares its per-input read loop
(`OIIO::pvt::test_read_image()` / `test_read_all_images()`, also reachable via
`oiiotool --testread`) with the rest of the library rather than duplicating
read logic in the harness. Gated behind the `OIIO_BUILD_FUZZ_TARGETS` CMake
option (off by default) and requires clang. See `docs/dev/fuzzing.md` for the
developer workflow.

## Language Bindings

The main APIs, and the underlying implementation, are in C++ (currently
Expand Down
Loading
Loading