Skip to content
Merged
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
20 changes: 14 additions & 6 deletions .clusterfuzzlite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,21 @@ cleanly and still die on its first import if a dynamically-imported module was n
why `build.sh` names `paramiko`, `eventlet`, `eventlet.tpool` and `config` as `--hidden-import` —
the harnesses pull them in via `importlib.import_module("...")`, a string PyInstaller cannot see.

## Not enabled: batch fuzzing, corpus pruning, coverage
## Batch fuzzing, pruning and coverage — written, dormant

Those three modes want somewhere to keep a corpus between runs, which means a **separate storage
repo** and a personal access token. Without one, every run starts from the seeds in
`tests/fuzz/corpus/` and learns nothing from the last run. To turn them on: create an empty repo,
add a PAT with write access to it as a secret, then pass `storage-repo` to both actions and add
workflows with `mode: batch`, `mode: prune` and `mode: coverage`. See
`cflite_batch.yml` (nightly, all targets, 15 min) and `cflite_cron.yml` (weekly prune + coverage)
are committed and wired, but every job is guarded by `if: env.CFL_STORAGE_REPO != ''` and so does
nothing until that secret exists. They stay dormant rather than red.

They need somewhere to keep the corpus between runs — without it each run restarts from the seeds
in `tests/fuzz/corpus/` and learns nothing from the last one. To switch them on:

1. create an empty repo, e.g. `linuxgsm-panel-fuzz-corpus`
2. create a PAT that can write to it
3. add `CFL_STORAGE_REPO` as a repository secret here (the https URL with the token in it, per the
ClusterFuzzLite docs)

Nothing else changes; the next scheduled run picks it up. See
<https://google.github.io/clusterfuzzlite/running-clusterfuzzlite/github-actions/>.

## Upstream OSS-Fuzz
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/cflite_batch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: ClusterFuzzLite batch

# The long-running counterpart to cflite_pr.yml. That one fuzzes what a PR changed, for three
# minutes; this fuzzes EVERYTHING on a schedule and — the point of it — keeps the corpus it builds,
# so each run starts smarter than the last instead of from the committed seeds.
#
# Keeping a corpus needs somewhere to put it: a separate git repo plus a token that can write to it.
# Both jobs below self-disable when the CFL_STORAGE_REPO secret is absent, so this file is inert
# until you set one up and starts working by itself the moment you do:
#
# 1. create an empty repo, e.g. FMSMITH91/linuxgsm-panel-fuzz-corpus
# 2. create a PAT with write access to it
# 3. add two repository secrets here: CFL_STORAGE_REPO (the https URL with the token in it, per
# the ClusterFuzzLite docs) and nothing else — the actions read the rest from the workflow.
#
# See https://google.github.io/clusterfuzzlite/running-clusterfuzzlite/github-actions/
on:
schedule:
- cron: '17 3 * * *' # daily, off the hour so it does not collide with everything else
workflow_dispatch:

permissions:
contents: read

env:
CFL_STORAGE_REPO: ${{ secrets.CFL_STORAGE_REPO }}

jobs:
batch:
name: batch fuzz (${{ matrix.sanitizer }})
runs-on: ubuntu-latest
if: github.repository == 'FMSMITH91/linuxgsm-panel' # never run in a fork
strategy:
fail-fast: false
matrix:
sanitizer: [ address ]
steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
id: build
if: env.CFL_STORAGE_REPO != ''
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: python
sanitizer: ${{ matrix.sanitizer }}

- name: Fuzz everything (${{ matrix.sanitizer }})
if: env.CFL_STORAGE_REPO != '' && steps.build.outcome == 'success'
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: python # run_fuzzers defaults to c++ and does not inherit the build's setting
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 900
mode: batch
sanitizer: ${{ matrix.sanitizer }}
storage-repo: ${{ secrets.CFL_STORAGE_REPO }}
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages
69 changes: 69 additions & 0 deletions .github/workflows/cflite_cron.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: ClusterFuzzLite prune and coverage

# Corpus pruning and a coverage report for the fuzz targets. Both need the corpus that
# cflite_batch.yml accumulates, so both self-disable without CFL_STORAGE_REPO — see that file for
# what to create. Inert until then; live the moment the secret exists.
on:
schedule:
- cron: '43 4 * * 0' # weekly, after a few nightly batch runs have fed the corpus
workflow_dispatch:

permissions:
contents: read

env:
CFL_STORAGE_REPO: ${{ secrets.CFL_STORAGE_REPO }}

jobs:
prune:
name: prune the corpus
runs-on: ubuntu-latest
if: github.repository == 'FMSMITH91/linuxgsm-panel'
steps:
- name: Build fuzzers
id: build
if: env.CFL_STORAGE_REPO != ''
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: python

- name: Prune
# Drops inputs that no longer reach anything new, so the corpus stays fast to replay.
if: env.CFL_STORAGE_REPO != '' && steps.build.outcome == 'success'
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: python
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 600
mode: prune
storage-repo: ${{ secrets.CFL_STORAGE_REPO }}
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages

coverage:
name: fuzzing coverage report
runs-on: ubuntu-latest
if: github.repository == 'FMSMITH91/linuxgsm-panel'
steps:
- name: Build fuzzers (coverage)
id: build
if: env.CFL_STORAGE_REPO != ''
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: python
sanitizer: coverage

- name: Report
# Which lines the fuzzers actually reach — a different question from tests/ coverage, and
# the honest way to tell whether a target is exercising the parser or bouncing off a guard.
if: env.CFL_STORAGE_REPO != '' && steps.build.outcome == 'success'
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: python
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 600
mode: coverage
sanitizer: coverage
storage-repo: ${{ secrets.CFL_STORAGE_REPO }}
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
# --parallel-mode + combine: each suite is its own process, and smoke/rbac fork threads.
# || true on the suites themselves — the gate job decides pass/fail, this one only measures.
run: |
for suite in unit template_actions smoke rbac; do
for suite in unit template_actions smoke rbac manage; do
rm -f data/panel.db data/panel.db-shm data/panel.db-wal data/panel.db.backup
python -m coverage run --parallel-mode --source=. \
--omit="./tests/*,./tools/*,./.venv/*" "tests/${suite}_test.py" >/dev/null 2>&1 || true
Expand Down
Loading