Implementing C++ and Rust wrappers packages for native bindings with parsing and reading #63
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Fuzz | |
| on: | |
| pull_request: | |
| branches: [master, develop] | |
| schedule: | |
| # Nightly. Coverage-guided fuzzing is only useful with sustained runtime, so this is where the | |
| # real budget goes; the `check` job below is what gates pull requests. | |
| - cron: '0 3 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| seconds: | |
| description: Seconds to fuzz each target | |
| required: false | |
| default: '600' | |
| concurrency: | |
| group: fuzz-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| libfuzzer: | |
| name: libFuzzer (${{ matrix.target }}) | |
| # Too slow to gate a pull request; PRs get the `check` job instead. | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [xlsx, xlsx-memory, xlsb, xlsb-memory, xls, csv, csv-sniff] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v6 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Install SharpFuzz CLI | |
| run: dotnet tool install --global SharpFuzz.CommandLine | |
| - name: Build libfuzzer-dotnet | |
| run: | | |
| wget -q https://raw.githubusercontent.com/Metalnem/libfuzzer-dotnet/master/libfuzzer-dotnet.cc | |
| clang -fsanitize=fuzzer libfuzzer-dotnet.cc -o libfuzzer-dotnet | |
| chmod +x libfuzzer-dotnet | |
| - name: Publish harness | |
| run: | | |
| dotnet publish tests/ExcelReader.Fuzz/ExcelReader.Fuzz.csproj -c Release -o fuzz-out | |
| chmod +x fuzz-out/ExcelReader.Fuzz | |
| # MUST run before instrumentation. Seed generation drives the writers through the ordinary | |
| # runtime, and an instrumented assembly writes coverage into a shared-memory region that only | |
| # exists when libfuzzer-dotnet is the one launching the process — running it standalone kills | |
| # the process with an AccessViolationException. Do not reorder these two steps. | |
| - name: Generate seed corpus | |
| run: | | |
| mkdir -p corpus | |
| dotnet fuzz-out/ExcelReader.Fuzz.dll seeds corpus | |
| # Excel-authored files make far better seeds than anything we can synthesise. | |
| cp RealExcel.xlsb RealExcel.xlsx corpus/ 2>/dev/null || true | |
| cp tests/ExcelReader.Tests/data/*.xlsx corpus/ 2>/dev/null || true | |
| # Inputs that previously crashed a target: keeping them in the corpus guards those paths. | |
| cp tests/ExcelReader.Fuzz/corpus/* corpus/ 2>/dev/null || true | |
| ls -la corpus | |
| # SharpFuzz rewrites the assembly under test to emit coverage. Only ExcelReader.Core is | |
| # instrumented: the harness itself is not the code under test, and instrumenting it would | |
| # dilute the coverage signal the engine steers by. Everything after this step must run under | |
| # libfuzzer-dotnet. | |
| - name: Instrument ExcelReader.Core | |
| run: sharpfuzz fuzz-out/ExcelReader.Core.dll | |
| # Two things this invocation gets right, both of which fail confusingly otherwise: | |
| # * target_path is the published apphost, not `dotnet`. libfuzzer-dotnet passes --target_args | |
| # through as a SINGLE argv entry rather than splitting on spaces, so | |
| # `--target_path=dotnet --target_args="Foo.dll csv"` hands `dotnet` one bogus path; it | |
| # prints usage and exits, surfacing as "short read: expected 4 bytes, got 0 bytes". | |
| # * the target is selected via FUZZ_TARGET, not --target_args. SharpFuzz's | |
| # Fuzzer.LibFuzzer.Run parses argv itself and treats a lone argument as a single input file | |
| # to replay, so our own argument would be swallowed as a corpus path. | |
| - name: Fuzz | |
| env: | |
| FUZZ_TARGET: ${{ matrix.target }} | |
| run: | | |
| ./libfuzzer-dotnet \ | |
| --target_path=fuzz-out/ExcelReader.Fuzz \ | |
| corpus \ | |
| -max_total_time=${{ github.event.inputs.seconds || '600' }} \ | |
| -max_len=131072 \ | |
| -timeout=25 \ | |
| -rss_limit_mb=2048 \ | |
| -print_final_stats=1 \ | |
| -artifact_prefix=crash- | |
| - name: Upload crashes | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: fuzz-crashes-${{ matrix.target }} | |
| path: | | |
| crash-* | |
| timeout-* | |
| oom-* | |
| if-no-files-found: ignore | |
| check: | |
| name: Harness smoke check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v6 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Build | |
| run: dotnet build tests/ExcelReader.Fuzz/ExcelReader.Fuzz.csproj -c Release | |
| - name: Generate corpus | |
| run: | | |
| mkdir -p corpus | |
| dotnet run --project tests/ExcelReader.Fuzz -c Release --no-build -- seeds corpus | |
| cp RealExcel.xlsb RealExcel.xlsx corpus/ 2>/dev/null || true | |
| cp tests/ExcelReader.Tests/data/*.xlsx corpus/ 2>/dev/null || true | |
| cp tests/ExcelReader.Fuzz/corpus/* corpus/ 2>/dev/null || true | |
| # No engine, no native tooling: runs every target over the corpus plus deterministic mutations, | |
| # and self-checks the oracle first so a green run cannot mean "the oracle accepts everything". | |
| - name: Run every target over mutated corpus | |
| run: dotnet run --project tests/ExcelReader.Fuzz -c Release --no-build -- check corpus 200 1 |