diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de857124d..bc5241e31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -371,7 +371,7 @@ jobs: - uses: actions/cache@v3 id: cache-radamsa-build-linux with: - # key on the shell script only: this script fixes the + # key on the shell script only: this script fixes the # version to a particular commit, so if it changes we need to rebuild key: radamsa-linux-${{ hashFiles('src/ci/radamsa-linux.sh') }} path: artifacts @@ -388,7 +388,7 @@ jobs: - uses: actions/cache@v3 id: cache-radamsa-build-windows with: - # key on the shell script only: this script fixes the + # key on the shell script only: this script fixes the # version to a particular commit, so if it changes we need to rebuild key: radamsa-windows-${{ hashFiles('src/ci/radamsa-windows.sh') }} path: artifacts @@ -524,6 +524,11 @@ jobs: (cd libfuzzer-aarch64-crosscompile; make) cp -r libfuzzer-aarch64-crosscompile/fuzz.exe libfuzzer-aarch64-crosscompile/inputs artifacts/linux-libfuzzer-aarch64-crosscompile + # C# target. + sudo apt-get install -y dotnet-sdk-6.0 + (cd GoodBad; dotnet publish -c Release -o out) + mv GoodBad/out artifacts/GoodBadDotnet + - uses: actions/upload-artifact@v2.2.2 with: name: integration-test-artifacts @@ -603,3 +608,36 @@ jobs: with: name: integration-test-artifacts path: src/integration-tests/artifacts + integration-tests-linux: + runs-on: ubuntu-18.04 + needs: + - build-integration-tests-linux + - dotnet-fuzzing-tools-linux + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v2.0.8 + with: + name: build-artifacts + path: build-artifacts + - uses: actions/download-artifact@v2.0.8 + with: + name: integration-test-artifacts + path: integration-test-artifacts + - name: test + shell: bash + run: | + set -ex -o pipefail + + # Must be absolute paths. + export GOODBAD_DOTNET="${GITHUB_WORKSPACE}/integration-test-artifacts/GoodBadDotnet" + + export LIBFUZZER_DOTNET="${GITHUB_WORKSPACE}/build-artifacts/third-party/dotnet-fuzzing-linux/libfuzzer-dotnet/libfuzzer-dotnet" + chmod +x $LIBFUZZER_DOTNET + + export LIBFUZZER_DOTNET_LOADER="${GITHUB_WORKSPACE}/build-artifacts/third-party/dotnet-fuzzing-linux/LibFuzzerDotnetLoader/LibFuzzerDotnetLoader" + chmod +x $LIBFUZZER_DOTNET_LOADER + + export SHARPFUZZ="${GITHUB_WORKSPACE}/build-artifacts/third-party/dotnet-fuzzing-linux/sharpfuzz/SharpFuzz.CommandLine" + chmod +x $SHARPFUZZ + + ./src/ci/test-libfuzzer-dotnet.sh diff --git a/src/ci/test-libfuzzer-dotnet.sh b/src/ci/test-libfuzzer-dotnet.sh new file mode 100755 index 000000000..cc1fa4c5a --- /dev/null +++ b/src/ci/test-libfuzzer-dotnet.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -ex -o pipefail + +# Required environment variables: +# - GOODBAD_DOTNET +# - LIBFUZZER_DOTNET +# - LIBFUZZER_DOTNET_LOADER +# - SHARPFUZZ + +export GOODBAD_DLL='GoodBad/GoodBad.dll' + +TMP=$(mktemp -d) +cd $TMP + +cp -r ${GOODBAD_DOTNET} GoodBad + +# Instrument DLL under test. +${SHARPFUZZ} GoodBad/GoodBad.dll + +# Create seed and crash inputs. +printf 'good' > good.txt +printf 'bad!' > bad.txt + +# Test individual env vars. +export LIBFUZZER_DOTNET_TARGET_ASSEMBLY="${GOODBAD_DLL}" +export LIBFUZZER_DOTNET_TARGET_CLASS='GoodBad.Fuzzer' +export LIBFUZZER_DOTNET_TARGET_METHOD='TestInput' + +${LIBFUZZER_DOTNET} --target_path=${LIBFUZZER_DOTNET_LOADER} good.txt + +# Expect nonzero exit. +! ${LIBFUZZER_DOTNET} --target_path=${LIBFUZZER_DOTNET_LOADER} bad.txt + +# Test delimited env var. +export LIBFUZZER_DOTNET_TARGET="${LIBFUZZER_DOTNET_TARGET_ASSEMBLY}:${LIBFUZZER_DOTNET_TARGET_CLASS}:${LIBFUZZER_DOTNET_TARGET_METHOD}" +unset LIBFUZZER_DOTNET_TARGET_ASSEMBLY +unset LIBFUZZER_DOTNET_TARGET_CLASS +unset LIBFUZZER_DOTNET_TARGET_METHOD + +${LIBFUZZER_DOTNET} --target_path=${LIBFUZZER_DOTNET_LOADER} good.txt + +# Expect nonzero exit. +! ${LIBFUZZER_DOTNET} --target_path=${LIBFUZZER_DOTNET_LOADER} bad.txt + +rm -rf $TMP diff --git a/src/integration-tests/GoodBad/GoodBad.cs b/src/integration-tests/GoodBad/GoodBad.cs new file mode 100644 index 000000000..f40160e56 --- /dev/null +++ b/src/integration-tests/GoodBad/GoodBad.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GoodBad; + +public class BinaryParser +{ + int count = 0; + + public void ProcessInput(ReadOnlySpan data) { + if (data.Length < 4) { + return; + } + + if (data[0] == 'b') { count++; } + if (data[1] == 'a') { count++; } + if (data[2] == 'd') { count++; } + if (data[3] == '!') { count++; } + + // Simulate an out-of-bounds access while parsing. + if (count >= 4) { + var _ = data[0xdead]; + } + } +} + +public class Fuzzer { + /// Preferred test method. + public static void TestInput(ReadOnlySpan data) { + var parser = new BinaryParser(); + parser.ProcessInput(data); + } + + /// Backwards-compatible test method for legacy code that can't use `Span` types. + /// + /// Incurs an extra copy of `data` per fuzzing iteration. + public static void TestInputCompat(byte[] data) { + var parser = new BinaryParser(); + parser.ProcessInput(data); + } + + /// Invalid static method that has a fuzzing-incompatible signature. + public static void BadSignature(ReadOnlySpan data) { + return; + } +} diff --git a/src/integration-tests/GoodBad/GoodBad.csproj b/src/integration-tests/GoodBad/GoodBad.csproj new file mode 100644 index 000000000..fd23d0099 --- /dev/null +++ b/src/integration-tests/GoodBad/GoodBad.csproj @@ -0,0 +1,10 @@ + + + + 10.0 + net6.0 + enable + enable + + +