mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-14 11:08:06 +00:00
libfuzzer-dotnet integration (#535)
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -339,6 +339,10 @@ jobs:
|
|||||||
(cd trivial-crash ; make )
|
(cd trivial-crash ; make )
|
||||||
cp -r trivial-crash/fuzz.exe trivial-crash/seeds artifacts/linux-trivial-crash
|
cp -r trivial-crash/fuzz.exe trivial-crash/seeds artifacts/linux-trivial-crash
|
||||||
|
|
||||||
|
mkdir -p artifacts/linux-libfuzzer-dotnet
|
||||||
|
(cd libfuzzer-dotnet; make)
|
||||||
|
cp -r libfuzzer-dotnet/my-fuzzer libfuzzer-dotnet/inputs artifacts/linux-libfuzzer-dotnet/
|
||||||
|
|
||||||
mkdir -p artifacts/linux-trivial-crash-asan
|
mkdir -p artifacts/linux-trivial-crash-asan
|
||||||
(cd trivial-crash ; make clean; make CFLAGS='-fsanitize=address -fno-omit-frame-pointer')
|
(cd trivial-crash ; make clean; make CFLAGS='-fsanitize=address -fno-omit-frame-pointer')
|
||||||
cp -r trivial-crash/fuzz.exe trivial-crash/seeds artifacts/linux-trivial-crash-asan
|
cp -r trivial-crash/fuzz.exe trivial-crash/seeds artifacts/linux-trivial-crash-asan
|
||||||
|
228
docs/how-to/fuzzing-dotnet-with-libfuzzer.md
Normal file
228
docs/how-to/fuzzing-dotnet-with-libfuzzer.md
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
# Fuzzing .Net using libfuzzer-dotnet
|
||||||
|
|
||||||
|
Fuzzing .Net using libFuzzer makes use of three Open Source capabilities.
|
||||||
|
|
||||||
|
1. [SharpFuzz](https://github.com/Metalnem/sharpfuzz), which injects coverage tracking into .Net assembly
|
||||||
|
2. [libFuzzer](https://www.llvm.org/docs/LibFuzzer.html), which performs the fuzzing
|
||||||
|
3. [libfuzzer-dotnet](https://github.com/Metalnem/libfuzzer-dotnet), which bridges the SharpFuzz instrumentation into libFuzzer
|
||||||
|
|
||||||
|
When using libFuzzer in C, developers provide a function
|
||||||
|
`LLVMFuzzerTestOneInput` which takes a pointer to a read-only buffer of bytes,
|
||||||
|
and the length of said buffer. ([Tutorial using libFuzzer in
|
||||||
|
C](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md))
|
||||||
|
|
||||||
|
With libfuzzer-dotnet, developers provide an application that within `Main` calls the method `Fuzzer.LibFuzzer.Run`, with a callback that passes a read only byte-stream their function of interest.
|
||||||
|
|
||||||
|
> NOTE: libfuzzer-dotnet only works on Linux at this time.
|
||||||
|
|
||||||
|
TL/DR: check out our [libfuzzer-dotnet example](../../src/integration-tests/libfuzzer-dotnet/)
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
OneFuzz supports net45 framework or any version that support least
|
||||||
|
netstandard1.6. Refer to [.Net
|
||||||
|
Standard](https://dotnet.microsoft.com/platform/dotnet-standard) check if your
|
||||||
|
framework version is supported.
|
||||||
|
|
||||||
|
## Issues using libfuzzer-dotnet in OneFuzz
|
||||||
|
* The `libfuzzer_coverage` task does not support the coverage features used by libfuzzer-dotnet. (Work item: [#536](https://github.com/microsoft/onefuzz/issues/536))
|
||||||
|
* The `libfuzzer_crash_report` does not support extracting unique output during analysis, making the crash de-duplication and reporting ineffective. (Work item: [#538]https://github.com/microsoft/onefuzz/issues/538))
|
||||||
|
|
||||||
|
As such, a libfuzzer-dotnet template is available, which only uses the `libfuzzer_fuzz` tasks. As these issues are resolve, the template will be updated to include the additional tasks.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Let's fuzz the `Func` function of our example library named [problems](../../src/integration-tests/libfuzzer-dotnet/problems/).
|
||||||
|
|
||||||
|
1. Make sure sharpfuzz and a recent version of clang are installed. We'll need these later.
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet tool install --global SharpFuzz.CommandLine
|
||||||
|
sudo apt-get install -y clang
|
||||||
|
```
|
||||||
|
|
||||||
|
2. We need to build an application that uses `Fuzzer.LibFuzzer.Run` that calls our function `Func`. For this example, let's call this [wrapper](../../src/integration-tests/libfuzzer-dotnet/wrapper/)
|
||||||
|
|
||||||
|
The [wrapper/wrapper.csproj](../../src/integration-tests/libfuzzer-dotnet/wrapper/wrapper.csproj) project file uses SharpFuzz 1.6.1 and refers to our [problems](../../src/integration-tests/libfuzzer-dotnet/problems/) library locally.
|
||||||
|
```xml
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\problems\problems.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SharpFuzz" Version="1.6.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
```
|
||||||
|
|
||||||
|
For our example [problems](../../src/integration-tests/libfuzzer-dotnet/problems/) library, our callback for `Fuzzer.LibFuzzer.Run` is straight forwards. `Func` already takes a `ReadOnlySpan<byte>`. If your functions takes strings, this would be the place to convert the span of bytes to strings.
|
||||||
|
[wrapper/program.cs](../../src/integration-tests/libfuzzer-dotnet/wrapper/program.cs)
|
||||||
|
```C#
|
||||||
|
using SharpFuzz;
|
||||||
|
namespace Wrapper {
|
||||||
|
public class Program {
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
Fuzzer.LibFuzzer.Run(stream => { Problems.Problems.Func(stream); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build our [wrapper](../../src/integration-tests/libfuzzer-dotnet/wrapper/)
|
||||||
|
```
|
||||||
|
dotnet publish ./wrapper/wrapper.csproj -c release -r linux-x64 -o my-fuzzer
|
||||||
|
```
|
||||||
|
> NOTE: Specifying the runtime `linux-x64` is important such that we make a self-contained deployment.
|
||||||
|
|
||||||
|
4. Then we need to ensure our [problems](../../src/integration-tests/libfuzzer-dotnet/problems/) library is instrumented:
|
||||||
|
```
|
||||||
|
sharpfuzz ./my-fuzzer/problems.dll
|
||||||
|
```
|
||||||
|
|
||||||
|
5. The last thing we need to build before we can start fuzzing is the [libfuzzer-dotnet](https://github.com/Metalnem/libfuzzer-dotnet) harness.
|
||||||
|
```
|
||||||
|
curl -o libfuzzer-dotnet.cc https://raw.githubusercontent.com/Metalnem/libfuzzer-dotnet/master/libfuzzer-dotnet.cc
|
||||||
|
clang -fsanitize=fuzzer libfuzzer-dotnet.cc -o my-fuzzer/libfuzzer-dotnet
|
||||||
|
```
|
||||||
|
|
||||||
|
6. We should provide some sample inputs for our fuzzing. For this example, a basic file will do. However, this should include reasonable known-good inputs for your function. If you're fuzzing PNGs, use a selection of valid PNGs.
|
||||||
|
```
|
||||||
|
mkdir -p inputs
|
||||||
|
echo hi > inputs/hi.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Now we can fuzz!
|
||||||
|
Execute `libfuzzer-dotnet` with our `wrapper` program and our `inputs` directory:
|
||||||
|
```
|
||||||
|
./my-fuzzer/libfuzzer-dotnet --target_path=./my-fuzzer/wrapper ./inputs/
|
||||||
|
```
|
||||||
|
|
||||||
|
In a few seconds, you'll see output that looks something like this:
|
||||||
|
```
|
||||||
|
INFO: libFuzzer ignores flags that start with '--'
|
||||||
|
INFO: Seed: 2909502334
|
||||||
|
INFO: Loaded 1 modules (58 inline 8-bit counters): 58 [0x4f9090, 0x4f90ca),
|
||||||
|
INFO: Loaded 1 PC tables (58 PCs): 58 [0x4bfae8,0x4bfe88),
|
||||||
|
INFO: 65536 Extra Counters
|
||||||
|
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
|
||||||
|
INFO: A corpus is not provided, starting from an empty corpus
|
||||||
|
#2 INITED cov: 8 ft: 10 corp: 1/1b exec/s: 0 rss: 24Mb
|
||||||
|
#3 NEW cov: 8 ft: 14 corp: 2/5b lim: 4 exec/s: 0 rss: 24Mb L: 4/4 MS: 1 CrossOver-
|
||||||
|
#36 NEW cov: 8 ft: 16 corp: 3/9b lim: 4 exec/s: 0 rss: 24Mb L: 4/4 MS: 3 EraseBytes-CopyPart-CrossOver-
|
||||||
|
#337 NEW cov: 8 ft: 18 corp: 4/13b lim: 6 exec/s: 0 rss: 24Mb L: 4/4 MS: 1 CMP- DE: "\x01\x00"-
|
||||||
|
System.Exception: this is bad
|
||||||
|
at Problems.Problems.Func(ReadOnlySpan`1 data)
|
||||||
|
at Wrapper.Program.<>c.<Main>b__0_0(ReadOnlySpan`1 stream) in /home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/wrapper/program.cs:line 5
|
||||||
|
at SharpFuzz.Fuzzer.LibFuzzer.Run(ReadOnlySpanAction action)
|
||||||
|
==7346== ERROR: libFuzzer: deadly signal
|
||||||
|
#0 0x4adf50 in __sanitizer_print_stack_trace (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x4adf50)
|
||||||
|
#1 0x45a258 in fuzzer::PrintStackTrace() (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x45a258)
|
||||||
|
#2 0x43f3a3 in fuzzer::Fuzzer::CrashCallback() (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x43f3a3)
|
||||||
|
#3 0x7fd6c323f3bf (/lib/x86_64-linux-gnu/libpthread.so.0+0x153bf)
|
||||||
|
#4 0x4aef35 in LLVMFuzzerTestOneInput (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x4aef35)
|
||||||
|
#5 0x440a61 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x440a61)
|
||||||
|
#6 0x4401a5 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x4401a5)
|
||||||
|
#7 0x442447 in fuzzer::Fuzzer::MutateAndTestOne() (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x442447)
|
||||||
|
#8 0x443145 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x443145)
|
||||||
|
#9 0x431afe in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x431afe)
|
||||||
|
#10 0x45a942 in main (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x45a942)
|
||||||
|
#11 0x7fd6c2ee20b2 in __libc_start_main /build/glibc-eX1tMB/glibc-2.31/csu/../csu/libc-start.c:308:16
|
||||||
|
#12 0x40689d in _start (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x40689d)
|
||||||
|
|
||||||
|
NOTE: libFuzzer has rudimentary signal handlers.
|
||||||
|
Combine libFuzzer with AddressSanitizer or similar for better crash reports.
|
||||||
|
SUMMARY: libFuzzer: deadly signal
|
||||||
|
MS: 4 ChangeBit-CopyPart-ShuffleBytes-PersAutoDict- DE: "\x01\x00"-; base unit: ae8444de02705346dae4f4c67d0c710b833c14e1
|
||||||
|
0x0,0x1,0x0,0x0,0xe,0x0,
|
||||||
|
\x00\x01\x00\x00\x0e\x00
|
||||||
|
artifact_prefix='./'; Test unit written to ./crash-ad81c382bc24cb4edb13f5ab12ce1ee454600a69
|
||||||
|
Base64: AAEAAA4A
|
||||||
|
```
|
||||||
|
|
||||||
|
As shown in the output, our fuzzing run generated the file `crash-ad81c382bc24cb4edb13f5ab12ce1ee454600a69`. If we provide this file on the command line, we can reproduce the identified crash:
|
||||||
|
```
|
||||||
|
$ ./my-fuzzer/libfuzzer-dotnet --target_path=./my-fuzzer/wrapper ./crash-ad81c382bc24cb4edb13f5ab12ce1ee454600a69
|
||||||
|
INFO: libFuzzer ignores flags that start with '--'
|
||||||
|
INFO: Seed: 3044788143
|
||||||
|
INFO: Loaded 1 modules (58 inline 8-bit counters): 58 [0x4f9090, 0x4f90ca),
|
||||||
|
INFO: Loaded 1 PC tables (58 PCs): 58 [0x4bfae8,0x4bfe88),
|
||||||
|
INFO: 65536 Extra Counters
|
||||||
|
./my-fuzzer/libfuzzer-dotnet: Running 1 inputs 1 time(s) each.
|
||||||
|
Running: ./crash-ad81c382bc24cb4edb13f5ab12ce1ee454600a69
|
||||||
|
System.Exception: this is bad
|
||||||
|
at Problems.Problems.Func(ReadOnlySpan`1 data)
|
||||||
|
at Wrapper.Program.<>c.<Main>b__0_0(ReadOnlySpan`1 stream) in /home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/wrapper/program.cs:line 5
|
||||||
|
at SharpFuzz.Fuzzer.LibFuzzer.Run(ReadOnlySpanAction action)
|
||||||
|
==7882== ERROR: libFuzzer: deadly signal
|
||||||
|
#0 0x4adf50 in __sanitizer_print_stack_trace (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x4adf50)
|
||||||
|
#1 0x45a258 in fuzzer::PrintStackTrace() (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x45a258)
|
||||||
|
#2 0x43f3a3 in fuzzer::Fuzzer::CrashCallback() (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x43f3a3)
|
||||||
|
#3 0x7f1681d223bf (/lib/x86_64-linux-gnu/libpthread.so.0+0x153bf)
|
||||||
|
#4 0x4aef35 in LLVMFuzzerTestOneInput (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x4aef35)
|
||||||
|
#5 0x440a61 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x440a61)
|
||||||
|
#6 0x42c1d2 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x42c1d2)
|
||||||
|
#7 0x431c86 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x431c86)
|
||||||
|
#8 0x45a942 in main (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x45a942)
|
||||||
|
#9 0x7f16819c50b2 in __libc_start_main /build/glibc-eX1tMB/glibc-2.31/csu/../csu/libc-start.c:308:16
|
||||||
|
#10 0x40689d in _start (/home/bcaswell/projects/onefuzz/onefuzz/src/integration-tests/libfuzzer-dotnet/my-fuzzer/libfuzzer-dotnet+0x40689d)
|
||||||
|
|
||||||
|
NOTE: libFuzzer has rudimentary signal handlers.
|
||||||
|
Combine libFuzzer with AddressSanitizer or similar for better crash reports.
|
||||||
|
SUMMARY: libFuzzer: deadly signal
|
||||||
|
```
|
||||||
|
> NOTE: The stack shown here is from `libfuzzer-dotnet`, which isn't useful in figuring out the bug in `Problems`. However, fuzzing found a reproducable crash in our `Problems` library. We can use this crashing input file using traditional debug tooling to figure out the underlying problem.
|
||||||
|
|
||||||
|
|
||||||
|
## Launching our example in OneFuzz
|
||||||
|
|
||||||
|
These commands launches the a libfuzzer-dotnet focused fuzzing task in OneFuzz. Note, we've added the arguments `--wait_for_running --wait_for_files inputs` such that we can monitor our job until we've seen at least one new input found via fuzzing.
|
||||||
|
```bash
|
||||||
|
TARGET_PROJECT=Problems
|
||||||
|
TARGET_NAME=Func
|
||||||
|
TARGET_BUILD=1
|
||||||
|
FUZZ_POOL=linux
|
||||||
|
onefuzz template libfuzzer dotnet ${TARGET_PROJECT} ${TARGET_NAME} ${TARGET_BUILD} ${FUZZ_POOL} ./my-fuzzer/ wrapper --wait_for_running --wait_for_files inputs
|
||||||
|
```
|
||||||
|
|
||||||
|
When we run this, we'll see output similar to:
|
||||||
|
```
|
||||||
|
INFO:onefuzz:creating job (runtime: 24 hours)
|
||||||
|
INFO:onefuzz:created job: 62d666d3-3373-4094-adbe-e705d722d698
|
||||||
|
INFO:onefuzz:using container: oft-setup-b8c2890353235ab497b14913fe2ee204
|
||||||
|
INFO:onefuzz:using container: oft-inputs-68e4cb24b37855f0b0abab9b533d5fc1
|
||||||
|
INFO:onefuzz:using container: oft-crashes-68e4cb24b37855f0b0abab9b533d5fc1
|
||||||
|
INFO:onefuzz:uploading setup dir `./my-fuzzer/`
|
||||||
|
INFO:onefuzz:done creating tasks
|
||||||
|
\ waiting on: libfuzzer_fuzz:init
|
||||||
|
| waiting on: libfuzzer_fuzz:scheduled
|
||||||
|
- waiting on: libfuzzer_fuzz:setting_up
|
||||||
|
INFO:onefuzz:tasks started
|
||||||
|
\ waiting for new files: oft-inputs-68e4cb24b37855f0b0abab9b533d5fc1
|
||||||
|
INFO:onefuzz:new files found
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"build": "1",
|
||||||
|
"duration": 24,
|
||||||
|
"name": "Func",
|
||||||
|
"project": "Problems"
|
||||||
|
},
|
||||||
|
"end_time": "2021-02-12T01:52:45+00:00",
|
||||||
|
"job_id": "62d666d3-3373-4094-adbe-e705d722d698",
|
||||||
|
"state": "enabled",
|
||||||
|
"task_info": [
|
||||||
|
{
|
||||||
|
"state": "running",
|
||||||
|
"task_id": "e951e3ee-5097-46be-832d-08fff05507e4",
|
||||||
|
"type": "libfuzzer_fuzz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"user_info": {
|
||||||
|
"application_id": "5e400594-28e9-4e26-97d8-8ea5bc2ff306",
|
||||||
|
"object_id": "33041cd0-ad34-4168-a98d-c8e6b4b666cb",
|
||||||
|
"upn": "example@contoso.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -3,9 +3,10 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
import os
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from onefuzztypes.enums import ContainerType, TaskDebugFlag, TaskType
|
from onefuzztypes.enums import OS, ContainerType, TaskDebugFlag, TaskType
|
||||||
from onefuzztypes.models import Job, NotificationConfig
|
from onefuzztypes.models import Job, NotificationConfig
|
||||||
from onefuzztypes.primitives import Container, Directory, File, PoolName
|
from onefuzztypes.primitives import Container, Directory, File, PoolName
|
||||||
|
|
||||||
@ -376,3 +377,114 @@ class Libfuzzer(Command):
|
|||||||
self.logger.info("done creating tasks")
|
self.logger.info("done creating tasks")
|
||||||
helper.wait()
|
helper.wait()
|
||||||
return helper.job
|
return helper.job
|
||||||
|
|
||||||
|
def dotnet(
|
||||||
|
self,
|
||||||
|
project: str,
|
||||||
|
name: str,
|
||||||
|
build: str,
|
||||||
|
pool_name: PoolName,
|
||||||
|
*,
|
||||||
|
setup_dir: Directory,
|
||||||
|
target_harness: str,
|
||||||
|
vm_count: int = 1,
|
||||||
|
inputs: Optional[Directory] = None,
|
||||||
|
reboot_after_setup: bool = False,
|
||||||
|
duration: int = 24,
|
||||||
|
target_workers: Optional[int] = None,
|
||||||
|
target_options: Optional[List[str]] = None,
|
||||||
|
target_env: Optional[Dict[str, str]] = None,
|
||||||
|
tags: Optional[Dict[str, str]] = None,
|
||||||
|
wait_for_running: bool = False,
|
||||||
|
wait_for_files: Optional[List[ContainerType]] = None,
|
||||||
|
existing_inputs: Optional[Container] = None,
|
||||||
|
debug: Optional[List[TaskDebugFlag]] = None,
|
||||||
|
ensemble_sync_delay: Optional[int] = None,
|
||||||
|
check_fuzzer_help: bool = True,
|
||||||
|
expect_crash_on_failure: bool = True,
|
||||||
|
) -> Optional[Job]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
libfuzzer-dotnet task
|
||||||
|
"""
|
||||||
|
|
||||||
|
harness = "libfuzzer-dotnet"
|
||||||
|
|
||||||
|
pool = self.onefuzz.pools.get(pool_name)
|
||||||
|
if pool.os != OS.linux:
|
||||||
|
raise Exception("libfuzzer-dotnet jobs are only compatable on linux")
|
||||||
|
|
||||||
|
target_exe = File(os.path.join(setup_dir, harness))
|
||||||
|
if not os.path.exists(target_exe):
|
||||||
|
raise Exception(f"missing harness: {target_exe}")
|
||||||
|
|
||||||
|
assembly_path = os.path.join(setup_dir, target_harness)
|
||||||
|
if not os.path.exists(assembly_path):
|
||||||
|
raise Exception(f"missing assembly: {assembly_path}")
|
||||||
|
|
||||||
|
self._check_is_libfuzzer(target_exe)
|
||||||
|
if target_options is None:
|
||||||
|
target_options = []
|
||||||
|
target_options = [
|
||||||
|
"--target_path={setup_dir}/" f"{target_harness}"
|
||||||
|
] + target_options
|
||||||
|
|
||||||
|
helper = JobHelper(
|
||||||
|
self.onefuzz,
|
||||||
|
self.logger,
|
||||||
|
project,
|
||||||
|
name,
|
||||||
|
build,
|
||||||
|
duration,
|
||||||
|
pool_name=pool_name,
|
||||||
|
target_exe=target_exe,
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.add_tags(tags)
|
||||||
|
helper.define_containers(
|
||||||
|
ContainerType.setup,
|
||||||
|
ContainerType.inputs,
|
||||||
|
ContainerType.crashes,
|
||||||
|
)
|
||||||
|
|
||||||
|
if existing_inputs:
|
||||||
|
self.onefuzz.containers.get(existing_inputs)
|
||||||
|
helper.containers[ContainerType.inputs] = existing_inputs
|
||||||
|
else:
|
||||||
|
helper.define_containers(ContainerType.inputs)
|
||||||
|
|
||||||
|
fuzzer_containers = [
|
||||||
|
(ContainerType.setup, helper.containers[ContainerType.setup]),
|
||||||
|
(ContainerType.crashes, helper.containers[ContainerType.crashes]),
|
||||||
|
(ContainerType.inputs, helper.containers[ContainerType.inputs]),
|
||||||
|
]
|
||||||
|
|
||||||
|
helper.create_containers()
|
||||||
|
|
||||||
|
helper.upload_setup(setup_dir, target_exe)
|
||||||
|
if inputs:
|
||||||
|
helper.upload_inputs(inputs)
|
||||||
|
helper.wait_on(wait_for_files, wait_for_running)
|
||||||
|
|
||||||
|
self.onefuzz.tasks.create(
|
||||||
|
helper.job.job_id,
|
||||||
|
TaskType.libfuzzer_fuzz,
|
||||||
|
harness,
|
||||||
|
fuzzer_containers,
|
||||||
|
pool_name=pool_name,
|
||||||
|
reboot_after_setup=reboot_after_setup,
|
||||||
|
duration=duration,
|
||||||
|
vm_count=vm_count,
|
||||||
|
target_options=target_options,
|
||||||
|
target_env=target_env,
|
||||||
|
target_workers=target_workers,
|
||||||
|
tags=tags,
|
||||||
|
debug=debug,
|
||||||
|
ensemble_sync_delay=ensemble_sync_delay,
|
||||||
|
check_fuzzer_help=check_fuzzer_help,
|
||||||
|
expect_crash_on_failure=expect_crash_on_failure,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info("done creating tasks")
|
||||||
|
helper.wait()
|
||||||
|
return helper.job
|
||||||
|
@ -31,7 +31,7 @@ from onefuzz.backend import ContainerWrapper, wait
|
|||||||
from onefuzz.cli import execute_api
|
from onefuzz.cli import execute_api
|
||||||
from onefuzztypes.enums import OS, ContainerType, TaskState, VmState
|
from onefuzztypes.enums import OS, ContainerType, TaskState, VmState
|
||||||
from onefuzztypes.models import Job, Pool, Repro, Scaleset
|
from onefuzztypes.models import Job, Pool, Repro, Scaleset
|
||||||
from onefuzztypes.primitives import Directory, File
|
from onefuzztypes.primitives import Container, Directory, File, PoolName, Region
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
LINUX_POOL = "linux-test"
|
LINUX_POOL = "linux-test"
|
||||||
@ -41,6 +41,7 @@ BUILD = "0"
|
|||||||
|
|
||||||
class TemplateType(Enum):
|
class TemplateType(Enum):
|
||||||
libfuzzer = "libfuzzer"
|
libfuzzer = "libfuzzer"
|
||||||
|
libfuzzer_dotnet = "libfuzzer_dotnet"
|
||||||
afl = "afl"
|
afl = "afl"
|
||||||
radamsa = "radamsa"
|
radamsa = "radamsa"
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ class Integration(BaseModel):
|
|||||||
target_exe: str
|
target_exe: str
|
||||||
inputs: Optional[str]
|
inputs: Optional[str]
|
||||||
use_setup: bool = Field(default=False)
|
use_setup: bool = Field(default=False)
|
||||||
|
nested_setup_dir: Optional[str]
|
||||||
wait_for_files: List[ContainerType]
|
wait_for_files: List[ContainerType]
|
||||||
check_asan_log: Optional[bool] = Field(default=False)
|
check_asan_log: Optional[bool] = Field(default=False)
|
||||||
disable_check_debugger: Optional[bool] = Field(default=False)
|
disable_check_debugger: Optional[bool] = Field(default=False)
|
||||||
@ -73,6 +75,15 @@ TARGETS: Dict[str, Integration] = {
|
|||||||
wait_for_files=[ContainerType.unique_reports, ContainerType.coverage],
|
wait_for_files=[ContainerType.unique_reports, ContainerType.coverage],
|
||||||
reboot_after_setup=True,
|
reboot_after_setup=True,
|
||||||
),
|
),
|
||||||
|
"linux-libfuzzer-dotnet": Integration(
|
||||||
|
template=TemplateType.libfuzzer_dotnet,
|
||||||
|
os=OS.linux,
|
||||||
|
target_exe="wrapper",
|
||||||
|
nested_setup_dir="my-fuzzer",
|
||||||
|
inputs="inputs",
|
||||||
|
use_setup=True,
|
||||||
|
wait_for_files=[ContainerType.inputs, ContainerType.crashes],
|
||||||
|
),
|
||||||
"linux-libfuzzer-rust": Integration(
|
"linux-libfuzzer-rust": Integration(
|
||||||
template=TemplateType.libfuzzer,
|
template=TemplateType.libfuzzer,
|
||||||
os=OS.linux,
|
os=OS.linux,
|
||||||
@ -159,7 +170,7 @@ class TestOnefuzz:
|
|||||||
def setup(
|
def setup(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
region: Optional[str] = None,
|
region: Optional[Region] = None,
|
||||||
user_pools: Optional[Dict[str, str]] = None,
|
user_pools: Optional[Dict[str, str]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
for entry in self.os:
|
for entry in self.os:
|
||||||
@ -169,7 +180,7 @@ class TestOnefuzz:
|
|||||||
)
|
)
|
||||||
self.pools[entry] = self.of.pools.get(user_pools[entry.name])
|
self.pools[entry] = self.of.pools.get(user_pools[entry.name])
|
||||||
else:
|
else:
|
||||||
name = "pool-%s-%s" % (self.project, entry.name)
|
name = PoolName("pool-%s-%s" % (self.project, entry.name))
|
||||||
self.logger.info("creating pool: %s:%s", entry.name, name)
|
self.logger.info("creating pool: %s:%s", entry.name, name)
|
||||||
self.pools[entry] = self.of.pools.create(name, entry)
|
self.pools[entry] = self.of.pools.create(name, entry)
|
||||||
self.logger.info("creating scaleset for pool: %s", name)
|
self.logger.info("creating scaleset for pool: %s", name)
|
||||||
@ -195,6 +206,9 @@ class TestOnefuzz:
|
|||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if setup and config.nested_setup_dir:
|
||||||
|
setup = Directory(os.path.join(setup, config.nested_setup_dir))
|
||||||
|
|
||||||
job: Optional[Job] = None
|
job: Optional[Job] = None
|
||||||
if config.template == TemplateType.libfuzzer:
|
if config.template == TemplateType.libfuzzer:
|
||||||
job = self.of.template.libfuzzer.basic(
|
job = self.of.template.libfuzzer.basic(
|
||||||
@ -207,7 +221,21 @@ class TestOnefuzz:
|
|||||||
setup_dir=setup,
|
setup_dir=setup,
|
||||||
duration=1,
|
duration=1,
|
||||||
vm_count=1,
|
vm_count=1,
|
||||||
reboot_after_setup=config.reboot_after_setup,
|
reboot_after_setup=config.reboot_after_setup or False,
|
||||||
|
)
|
||||||
|
elif config.template == TemplateType.libfuzzer_dotnet:
|
||||||
|
if setup is None:
|
||||||
|
raise Exception("setup required for libfuzzer_dotnet")
|
||||||
|
job = self.of.template.libfuzzer.dotnet(
|
||||||
|
self.project,
|
||||||
|
target,
|
||||||
|
BUILD,
|
||||||
|
self.pools[config.os].name,
|
||||||
|
target_harness=config.target_exe,
|
||||||
|
inputs=inputs,
|
||||||
|
setup_dir=setup,
|
||||||
|
duration=1,
|
||||||
|
vm_count=1,
|
||||||
)
|
)
|
||||||
elif config.template == TemplateType.radamsa:
|
elif config.template == TemplateType.radamsa:
|
||||||
job = self.of.template.radamsa.basic(
|
job = self.of.template.radamsa.basic(
|
||||||
@ -362,7 +390,7 @@ class TestOnefuzz:
|
|||||||
self.logger.info("checking jobs")
|
self.logger.info("checking jobs")
|
||||||
return wait(self.check_jobs_impl)
|
return wait(self.check_jobs_impl)
|
||||||
|
|
||||||
def get_job_crash(self, job_id: UUID) -> Optional[Tuple[str, str]]:
|
def get_job_crash(self, job_id: UUID) -> Optional[Tuple[Container, str]]:
|
||||||
# get the crash container for a given job
|
# get the crash container for a given job
|
||||||
|
|
||||||
for task in self.of.tasks.list(job_id=job_id, state=None):
|
for task in self.of.tasks.list(job_id=job_id, state=None):
|
||||||
@ -396,6 +424,7 @@ class TestOnefuzz:
|
|||||||
self.logger.info("launching repro: %s", self.target_jobs[job_id])
|
self.logger.info("launching repro: %s", self.target_jobs[job_id])
|
||||||
report = self.get_job_crash(job_id)
|
report = self.get_job_crash(job_id)
|
||||||
if report is None:
|
if report is None:
|
||||||
|
self.logger.warning("target does not include crash reports: %s", self.target_jobs[job_id])
|
||||||
return
|
return
|
||||||
(container, path) = report
|
(container, path) = report
|
||||||
self.repros[job_id] = self.of.repro.create(container, path, duration=1)
|
self.repros[job_id] = self.of.repro.create(container, path, duration=1)
|
||||||
@ -555,7 +584,7 @@ class Run(Command):
|
|||||||
endpoint: Optional[str] = None,
|
endpoint: Optional[str] = None,
|
||||||
user_pools: Optional[Dict[str, str]] = None,
|
user_pools: Optional[Dict[str, str]] = None,
|
||||||
pool_size: int = 10,
|
pool_size: int = 10,
|
||||||
region: Optional[str] = None,
|
region: Optional[Region] = None,
|
||||||
os_list: List[OS] = [OS.linux, OS.windows],
|
os_list: List[OS] = [OS.linux, OS.windows],
|
||||||
targets: List[str] = list(TARGETS.keys()),
|
targets: List[str] = list(TARGETS.keys()),
|
||||||
skip_repro: bool = False,
|
skip_repro: bool = False,
|
||||||
@ -580,6 +609,7 @@ class Run(Command):
|
|||||||
if skip_repro:
|
if skip_repro:
|
||||||
self.logger.warning("not testing crash repro")
|
self.logger.warning("not testing crash repro")
|
||||||
else:
|
else:
|
||||||
|
self.logger.info("launching crash repro tests")
|
||||||
tester.launch_repro()
|
tester.launch_repro()
|
||||||
tester.check_repro()
|
tester.check_repro()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
24
src/integration-tests/libfuzzer-dotnet/Makefile
Normal file
24
src/integration-tests/libfuzzer-dotnet/Makefile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
all: check
|
||||||
|
|
||||||
|
libfuzzer-dotnet:
|
||||||
|
mkdir -p my-fuzzer
|
||||||
|
# direct url to a known-good instance of libfuzzer-dotnet.cc
|
||||||
|
curl -o libfuzzer-dotnet.cc https://raw.githubusercontent.com/Metalnem/libfuzzer-dotnet/543b170a67bdd39e9ba260fe54bc93c77b877c24/libfuzzer-dotnet.cc
|
||||||
|
clang -fsanitize=fuzzer libfuzzer-dotnet.cc -o my-fuzzer/libfuzzer-dotnet
|
||||||
|
rm -f libfuzzer-dotnet.cc
|
||||||
|
|
||||||
|
build-harness: libfuzzer-dotnet
|
||||||
|
dotnet tool install --global SharpFuzz.CommandLine || echo already installed
|
||||||
|
dotnet publish ./wrapper/wrapper.csproj -c release -o my-fuzzer -r linux-x64
|
||||||
|
sharpfuzz my-fuzzer/problems.dll || echo already instrumented
|
||||||
|
|
||||||
|
check: build-harness
|
||||||
|
./my-fuzzer/libfuzzer-dotnet --target_path=./my-fuzzer/wrapper -runs=1
|
||||||
|
|
||||||
|
fuzz: check
|
||||||
|
./my-fuzzer/libfuzzer-dotnet --target_path=./my-fuzzer/wrapper
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf fuzz.exe libfuzzer-dotnet my-fuzzer wrapper/bin wrapper/obj problems/bin problems/obj
|
1
src/integration-tests/libfuzzer-dotnet/inputs/hi.txt
Normal file
1
src/integration-tests/libfuzzer-dotnet/inputs/hi.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
hi
|
15
src/integration-tests/libfuzzer-dotnet/problems/problems.cs
Normal file
15
src/integration-tests/libfuzzer-dotnet/problems/problems.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
namespace Problems {
|
||||||
|
public static class Problems {
|
||||||
|
public static void Func(ReadOnlySpan<byte> data) {
|
||||||
|
var count = 0;
|
||||||
|
if (data.Length < 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data[0] == 0) { count++; }
|
||||||
|
if (data[1] == 1) { count++; }
|
||||||
|
if (data[2] == 2) { count++; }
|
||||||
|
if (count >= 3) { throw new Exception("this is bad"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,8 @@
|
|||||||
|
using SharpFuzz;
|
||||||
|
namespace Wrapper {
|
||||||
|
public class Program {
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
Fuzzer.LibFuzzer.Run(stream => { Problems.Problems.Func(stream); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\problems\problems.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SharpFuzz" Version="1.6.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
Reference in New Issue
Block a user