Skip to content

Latest commit

 

History

History
 
 

Fuzzing

Fuzzing .NET libraries

This project contains fuzzing targets for various .NET libraries, as well as supporting code for generating OneFuzz deployments from them. Targets are instrumented using SharpFuzz, and ran using libFuzzer.

The runtime and fuzzing targets are periodically rebuilt and published to OneFuzz via deploy-to-onefuzz.yml.

Useful links:

Running locally

Note

The instructions assume you are running on Windows as that is what the continuous fuzzing runs currently use.

Prerequisites

Build the runtime with the following arguments:

./build.cmd clr+libs+packs+host -rc Checked -c Debug

and install the SharpFuzz command line tool:

dotnet tool install --global SharpFuzz.CommandLine

Tip

The project uses a checked runtime + debug libraries configuration by default. If you want to use a different configuration, make sure to also adjust the artifact paths in nuget.config.

Fuzzing locally

The prepare-onefuzz command will create separate directories for each fuzzing target, instrument the relevant assemblies, and generate a helper script for running them locally. Note that this command must be ran on the published artifacts (won't work with dotnet run).

cd src/libraries/Fuzzing/DotnetFuzzing

dotnet publish -o publish; publish/DotnetFuzzing.exe prepare-onefuzz deployment

You can now start fuzzing by running the local-run.bat script in the folder of the fuzzer you are interested in.

deployment/HttpHeadersFuzzer/local-run.bat

See the libFuzzer options documentation for more information on how to customize the fuzzing process. For example, here is how you can run the fuzzer against a header-inputs corpus directory for 10 minutes, running multiple instances in parallel:

deployment/HttpHeadersFuzzer/local-run.bat header-inputs -timeout=30 -max_total_time=600 -jobs=5

Creating a new fuzzing target

To create a new fuzzing target, you need to create a new class that implements the IFuzzer interface. See existing implementations in the Fuzzers directory for reference.

As an example, let's test that IPAddress.TryParse never throws on invalid input, and doesn't access any bytes after the end of bytes:

internal sealed class IPAddressFuzzer : IFuzzer
{
    public string[] TargetAssemblies => ["System.Net.Primitives"]; // Assembly where IPAddress lives
    public string[] TargetCoreLibPrefixes => [];

    public void FuzzTarget(ReadOnlySpan<byte> bytes)
    {
        // PooledBoundedMemory is a helper class that ensures reading past the end of the buffer will trigger an access violation.
        using var chars = PooledBoundedMemory<char>.Rent(MemoryMarshal.Cast<byte, char>(bytes), PoisonPagePlacement.After);

        _ = IPAddress.TryParse(chars.Span, out _);
    }
}
  • TargetAssemblies is a list of assemblies where the tested code lives and that must be instrumented.
  • TargetCoreLibPrefixes is the same, but for types/namespaces in System.Private.CoreLib.
  • FuzzTarget is the logic that the fuzzer will run for every test input. It should exercise code from the target assemblies.

Once you've created the new target, you can follow instructions above to run it locally. Targets are discovered via reflection, so they will automatically become available for local runs and continuous fuzzing in CI.

Running against a sample input

The program accepts two arguments: the name of the fuzzer and the path to a sample input file / directory. To run the HttpHeaders target against the inputs directory, use the following command:

cd src/libraries/Fuzzing/DotnetFuzzing

dotnet run HttpHeadersFuzzer inputs

This can be useful when debugging a crash, or running the fuzzer over existing inputs to collect code coverage.