diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..c10c2fe --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,9 @@ +FROM gcr.io/oss-fuzz-base/base-builder-python + +WORKDIR $SRC/movi-organizer +COPY . $SRC/movi-organizer +COPY .clusterfuzzlite/build.sh $SRC/build.sh + +RUN python3 -m pip install --disable-pip-version-check --upgrade pip==26.0.1 \ + && python3 -m pip install --disable-pip-version-check atheris \ + && python3 -m pip install --disable-pip-version-check . diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 0000000..1bfa70f --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -euo pipefail + +cd "$SRC/movi-organizer" + +python3 -m pip install --disable-pip-version-check atheris +python3 -m pip install --disable-pip-version-check . + +compile_python_fuzzer tests/fuzz/fuzz_safe_join.py diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000..c1bbdb2 --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1,4 @@ +homepage: https://github.com/xiaojiou176-open/movi-organizer +language: python +primary_contact: 125581657+xiaojiou176@users.noreply.github.com +main_repo: https://github.com/xiaojiou176-open/movi-organizer diff --git a/.github/workflows/cflite_batch.yml b/.github/workflows/cflite_batch.yml new file mode 100644 index 0000000..0041377 --- /dev/null +++ b/.github/workflows/cflite_batch.yml @@ -0,0 +1,39 @@ +name: cflite-batch + +on: + schedule: + - cron: "20 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: cflite-batch-${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + run-fuzzers: + timeout-minutes: 30 + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + clean: true + persist-credentials: false + - name: Build fuzzers + uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + language: python + sanitizer: address + - name: Run batch fuzzing + uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + language: python + sanitizer: address + mode: batch + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 diff --git a/.github/workflows/cflite_pr.yml b/.github/workflows/cflite_pr.yml new file mode 100644 index 0000000..1340bc5 --- /dev/null +++ b/.github/workflows/cflite_pr.yml @@ -0,0 +1,39 @@ +name: cflite-pr + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: cflite-pr-${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + run-fuzzers: + timeout-minutes: 20 + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + clean: true + persist-credentials: false + - name: Build fuzzers + uses: google/clusterfuzzlite/actions/build_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + language: python + sanitizer: address + - name: Run PR fuzzing + uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05 # v1 + with: + language: python + sanitizer: address + mode: code-change + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 300 diff --git a/tests/fuzz/fuzz_safe_join.py b/tests/fuzz/fuzz_safe_join.py new file mode 100644 index 0000000..b792786 --- /dev/null +++ b/tests/fuzz/fuzz_safe_join.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +import atheris + +with atheris.instrument_imports(): + from packages.domain.normalization import safe_join + + +ROOT = Path("/tmp/movi-organizer-fuzz-root") + + +def TestOneInput(data: bytes) -> None: + provider = atheris.FuzzedDataProvider(data) + part_count = provider.ConsumeIntInRange(0, 4) + parts = [provider.ConsumeUnicodeNoSurrogates(32) for _ in range(part_count)] + try: + joined = safe_join(ROOT, *parts) + except ValueError: + return + + resolved_root = ROOT.resolve() + assert joined == resolved_root or resolved_root in joined.parents + + +def main() -> None: + atheris.Setup(sys.argv, TestOneInput) + atheris.Fuzz() + + +if __name__ == "__main__": + main()