├── .clang-format
├── .gitignore
├── tests
├── Library
│ ├── Library.csproj
│ └── Parser.cs
└── Library.Fuzz
│ ├── Program.cs
│ └── Library.Fuzz.csproj
├── README.md
├── test.ps1
├── LICENSE
├── .github
└── workflows
│ ├── release.yml
│ └── build.yml
├── libfuzzer-dotnet.cc
└── libfuzzer-dotnet-windows.cc
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: Microsoft
2 | IndentWidth: 4
3 | UseCRLF: false
4 | ColumnLimit: 0
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Visual Studio Code
2 | .vscode
3 |
4 | # Build results
5 | [Bb]in/
6 | [Oo]bj/
7 |
8 | # BenchmarkDotNet
9 | **/BenchmarkDotNet.Artifacts/*
10 |
--------------------------------------------------------------------------------
/tests/Library/Library.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | disable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/Library.Fuzz/Program.cs:
--------------------------------------------------------------------------------
1 | using SharpFuzz;
2 |
3 | namespace Library.Fuzz;
4 |
5 | public class Program
6 | {
7 | public static void Main(string[] args)
8 | {
9 | Fuzzer.LibFuzzer.Run(span =>
10 | {
11 | Parser.Parse(span);
12 | });
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/Library.Fuzz/Library.Fuzz.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | enable
7 | disable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # libfuzzer-dotnet
2 |
3 | [![Build Status][build-shield]][build-link]
4 | [![License][license-shield]][license-link]
5 |
6 | [build-shield]: https://github.com/metalnem/libfuzzer-dotnet/actions/workflows/release.yml/badge.svg
7 | [build-link]: https://github.com/metalnem/libfuzzer-dotnet/actions/workflows/release.yml
8 | [license-shield]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat
9 | [license-link]: https://github.com/metalnem/libfuzzer-dotnet/blob/master/LICENSE
10 |
11 | libFuzzer driver for SharpFuzz. Read [Using libFuzzer with SharpFuzz] for usage instructions.
12 |
13 | [Using libFuzzer with SharpFuzz]: https://github.com/Metalnem/sharpfuzz/blob/master/docs/libFuzzer.md
14 |
--------------------------------------------------------------------------------
/test.ps1:
--------------------------------------------------------------------------------
1 | dotnet tool install --global SharpFuzz.CommandLine
2 |
3 | dotnet publish tests/Library.Fuzz -c release -o out
4 | sharpfuzz out/Library.dll
5 |
6 | mkdir corpus
7 | $outputPath = "output.txt"
8 |
9 | .\libfuzzer-dotnet --target_path=dotnet --target_arg=out/Library.Fuzz.dll corpus 2>&1 `
10 | | Tee-Object -FilePath $outputPath
11 |
12 | $output = Get-Content -Path $outputPath -Raw
13 | $crasher = "Cooking MC's"
14 | $exception = "Like a pound of bacon"
15 |
16 | if (-not $output.Contains($crasher)) {
17 | Write-Error "Crasher is missing from the libFuzzer output"
18 | exit 1
19 | }
20 |
21 | if (-not $output.Contains($exception)) {
22 | Write-Error "Exception is missing from the libFuzzer output"
23 | exit 1
24 | }
25 |
26 | Write-Host "$crasher $($exception.ToLower())"
27 | exit 0
28 |
--------------------------------------------------------------------------------
/tests/Library/Parser.cs:
--------------------------------------------------------------------------------
1 | namespace Library;
2 |
3 | public static class Parser
4 | {
5 | public static void Parse(ReadOnlySpan span)
6 | {
7 | if (span.Length > 0 && span[0] == 'C')
8 | if (span.Length > 1 && span[1] == 'o')
9 | if (span.Length > 2 && span[2] == 'o')
10 | if (span.Length > 3 && span[3] == 'k')
11 | if (span.Length > 4 && span[4] == 'i')
12 | if (span.Length > 5 && span[5] == 'n')
13 | if (span.Length > 6 && span[6] == 'g')
14 | if (span.Length > 7 && span[7] == ' ')
15 | if (span.Length > 8 && span[8] == 'M')
16 | if (span.Length > 9 && span[9] == 'C')
17 | if (span.Length > 10 && span[10] == '\'')
18 | if (span.Length > 11 && span[11] == 's')
19 | {
20 | throw new Exception("Like a pound of bacon");
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Nemanja Mijailovic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release libfuzzer-dotnet
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: ["master"]
7 | paths:
8 | - 'libfuzzer-dotnet-windows.cc'
9 | - 'libfuzzer-dotnet.cc'
10 |
11 | jobs:
12 | build:
13 | uses: ./.github/workflows/build.yml
14 |
15 | release:
16 | runs-on: windows-latest
17 | needs: build
18 |
19 | permissions:
20 | contents: write
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | - uses: actions/download-artifact@v4
25 | with:
26 | pattern: libfuzzer-dotnet-*
27 | merge-multiple: true
28 | - name: Tag
29 | id: tag
30 | run: |
31 | $tag = "v" + (Get-Date).ToString("yyyy.MM.dd.HHmm")
32 | Add-Content -Path $env:GITHUB_OUTPUT -Value "tag=$tag"
33 |
34 | git config user.name "${{ github.repository_owner }}"
35 | git config user.email "${{ github.repository_owner }}@users.noreply.github.com"
36 |
37 | git tag -a $tag -m $tag ${{ github.sha }}
38 | git push origin $tag
39 | - uses: softprops/action-gh-release@v2
40 | with:
41 | files: |
42 | libfuzzer-dotnet-windows.exe
43 | libfuzzer-dotnet-ubuntu
44 | libfuzzer-dotnet-debian
45 | tag_name: ${{ steps.tag.outputs.tag }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build libfuzzer-dotnet
2 |
3 | on:
4 | workflow_dispatch:
5 | workflow_call:
6 | pull_request:
7 | branches: ["master"]
8 |
9 | env:
10 | dotnet-version: 9.0
11 |
12 | jobs:
13 | build-windows:
14 | runs-on: windows-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Build
19 | run: clang -Werror -O2 -fsanitize=fuzzer libfuzzer-dotnet-windows.cc -o libfuzzer-dotnet-windows.exe
20 | - uses: actions/setup-dotnet@v4
21 | with:
22 | dotnet-version: ${{ env.dotnet-version }}
23 | - name: Run libFuzzer tests
24 | run: cp libfuzzer-dotnet-windows.exe libfuzzer-dotnet.exe && .\test.ps1
25 | - uses: actions/upload-artifact@v4
26 | with:
27 | name: libfuzzer-dotnet-windows
28 | path: libfuzzer-dotnet-windows.exe
29 |
30 | build-ubuntu:
31 | runs-on: ubuntu-latest
32 |
33 | steps:
34 | - uses: actions/checkout@v4
35 | - name: Build
36 | run: clang -Werror -O2 -fsanitize=fuzzer libfuzzer-dotnet.cc -o libfuzzer-dotnet-ubuntu
37 | - uses: actions/setup-dotnet@v4
38 | with:
39 | dotnet-version: ${{ env.dotnet-version }}
40 | - name: Run libFuzzer tests
41 | shell: pwsh
42 | run: cp libfuzzer-dotnet-ubuntu libfuzzer-dotnet && .\test.ps1
43 | - uses: actions/upload-artifact@v4
44 | with:
45 | name: libfuzzer-dotnet-ubuntu
46 | path: libfuzzer-dotnet-ubuntu
47 |
48 | build-debian:
49 | runs-on: ubuntu-latest
50 | container: debian:bookworm
51 |
52 | steps:
53 | - uses: actions/checkout@v4
54 | - name: Download LLVM
55 | run: |
56 | apt-get update && apt-get install -y lsb-release wget software-properties-common gnupg
57 | echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot-keyring.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" \
58 | | tee /etc/apt/sources.list.d/llvm.list
59 | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \
60 | | gpg --dearmor -o /usr/share/keyrings/llvm-snapshot-keyring.gpg
61 | apt-get update && apt-get install -y clang-18 libfuzzer-18-dev llvm-18 llvm-18-dev
62 | - name: Build
63 | run: clang-18 -Werror -O2 -fsanitize=fuzzer libfuzzer-dotnet.cc -o libfuzzer-dotnet-debian
64 | - uses: actions/upload-artifact@v4
65 | with:
66 | name: libfuzzer-dotnet-debian
67 | path: libfuzzer-dotnet-debian
68 |
--------------------------------------------------------------------------------
/libfuzzer-dotnet.cc:
--------------------------------------------------------------------------------
1 | #include "errno.h"
2 | #include "stddef.h"
3 | #include "stdint.h"
4 | #include "stdio.h"
5 | #include "stdlib.h"
6 | #include "string.h"
7 | #include "unistd.h"
8 | #include
9 |
10 | #define _STR(x) #x
11 | #define STR(x) _STR(x)
12 |
13 | #define MAP_SIZE (1 << 16)
14 | #define DATA_SIZE (1 << 20)
15 |
16 | #define CTL_FD 198
17 | #define ST_FD 199
18 | #define LEN_FLD_SIZE 4
19 |
20 | #define SHM_ID_VAR "__LIBFUZZER_SHM_ID"
21 | #define CTL_FD_VAR "__LIBFUZZER_CONTROL_PIPE_ID"
22 | #define ST_FD_VAR "__LIBFUZZER_STATUS_PIPE_ID"
23 |
24 | __attribute__((weak, section("__libfuzzer_extra_counters")))
25 | uint8_t extra_counters[MAP_SIZE];
26 |
27 | static const char *target_path_name = "--target_path";
28 | static const char *target_arg_name = "--target_arg";
29 |
30 | static const char *target_path;
31 | static const char *target_arg;
32 |
33 | static int ctl_fd;
34 | static int st_fd;
35 | static int shm_id;
36 | static uint8_t *trace_bits;
37 | static pid_t child_pid;
38 |
39 | static void die(const char *msg)
40 | {
41 | printf("%s\n", msg);
42 | exit(1);
43 | }
44 |
45 | static void die_sys(const char *msg)
46 | {
47 | printf("%s: %s\n", msg, strerror(errno));
48 | exit(1);
49 | }
50 |
51 | static void remove_shm()
52 | {
53 | shmctl(shm_id, IPC_RMID, NULL);
54 | }
55 |
56 | // Read the flag value from the single command line parameter. For example,
57 | // read_flag_value("--target_path=binary", "--target-path") will return "binary".
58 | static const char *read_flag_value(const char *param, const char *name)
59 | {
60 | size_t len = strlen(name);
61 |
62 | if (strstr(param, name) == param && param[len] == '=' && param[len + 1])
63 | {
64 | return ¶m[len + 1];
65 | }
66 |
67 | return NULL;
68 | }
69 |
70 | // Read target_path (the path to .NET executable) and target_arg (optional command
71 | // line argument that can be passed to .NET executable) from the command line parameters.
72 | static void parse_flags(int argc, char **argv)
73 | {
74 | for (int i = 0; i < argc; ++i)
75 | {
76 | char *param = argv[i];
77 |
78 | if (!target_path)
79 | {
80 | target_path = read_flag_value(param, target_path_name);
81 | }
82 |
83 | if (!target_arg)
84 | {
85 | target_arg = read_flag_value(param, target_arg_name);
86 | }
87 | }
88 | }
89 |
90 | // Start the .NET child process and initialize two pipes and one shared
91 | // memory segment for the communication between the parent and the child.
92 | extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv)
93 | {
94 | parse_flags(*argc, *argv);
95 |
96 | if (!target_path)
97 | {
98 | die("You must specify the target path by using the --target_path command line flag.");
99 | }
100 |
101 | int ctl_pipe[2];
102 | int st_pipe[2];
103 |
104 | if (pipe(ctl_pipe) || pipe(st_pipe))
105 | {
106 | die_sys("pipe() failed");
107 | }
108 |
109 | shm_id = shmget(IPC_PRIVATE, MAP_SIZE + DATA_SIZE, IPC_CREAT | IPC_EXCL | 0600);
110 |
111 | if (shm_id < 0)
112 | {
113 | die_sys("shmget() failed");
114 | }
115 |
116 | atexit(remove_shm);
117 |
118 | trace_bits = static_cast(shmat(shm_id, NULL, 0));
119 |
120 | if (trace_bits == (void *)-1)
121 | {
122 | die_sys("shmat() failed");
123 | }
124 |
125 | child_pid = fork();
126 |
127 | if (child_pid < 0)
128 | {
129 | die_sys("fork() failed");
130 | }
131 |
132 | if (!child_pid)
133 | {
134 | if (dup2(ctl_pipe[0], CTL_FD) < 0 || dup2(st_pipe[1], ST_FD) < 0)
135 | {
136 | die_sys("dup() failed");
137 | }
138 |
139 | close(ctl_pipe[0]);
140 | close(ctl_pipe[1]);
141 | close(st_pipe[0]);
142 | close(st_pipe[1]);
143 |
144 | char shm_str[12];
145 | sprintf(shm_str, "%d", shm_id);
146 |
147 | if (setenv(SHM_ID_VAR, shm_str, 1))
148 | {
149 | die_sys("setenv() failed setting shared memory ID");
150 | }
151 |
152 | if (setenv(CTL_FD_VAR, STR(CTL_FD), 1))
153 | {
154 | die_sys("setenv() failed setting control pipe ID");
155 | }
156 |
157 | if (setenv(ST_FD_VAR, STR(ST_FD), 1))
158 | {
159 | die_sys("setenv() failed setting status pipe ID");
160 | }
161 |
162 | if (target_arg)
163 | {
164 | execlp(target_path, "", target_arg, NULL);
165 | }
166 | else
167 | {
168 | execlp(target_path, "", NULL);
169 | }
170 |
171 | die_sys("execlp() failed");
172 | }
173 | else
174 | {
175 | close(ctl_pipe[0]);
176 | close(st_pipe[1]);
177 |
178 | ctl_fd = ctl_pipe[1];
179 | st_fd = st_pipe[0];
180 |
181 | ssize_t result;
182 | int32_t status;
183 |
184 | while ((result = read(st_fd, &status, LEN_FLD_SIZE)) == -1 && errno == EINTR)
185 | {
186 | continue;
187 | }
188 |
189 | if (result == -1)
190 | {
191 | die_sys("read() failed");
192 | }
193 |
194 | if (result != LEN_FLD_SIZE)
195 | {
196 | printf("short read: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result);
197 | exit(1);
198 | }
199 | }
200 |
201 | return 0;
202 | }
203 |
204 | // Fuzz the data by writing it to the shared memory segment, sending
205 | // the size of the data to the .NET process (which will then run
206 | // its own fuzzing function on the shared memory data), and receiving
207 | // the status of the executed operation.
208 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
209 | {
210 | if (size > DATA_SIZE)
211 | {
212 | die("Size of the input data must not exceed 1 MiB.");
213 | }
214 |
215 | memset(trace_bits, 0, MAP_SIZE);
216 | memcpy(trace_bits + MAP_SIZE, data, size);
217 |
218 | ssize_t result;
219 |
220 | while ((result = write(ctl_fd, &size, LEN_FLD_SIZE)) == -1 && errno == EINTR)
221 | {
222 | continue;
223 | }
224 |
225 | if (result == -1)
226 | {
227 | die_sys("write() failed");
228 | }
229 |
230 | if (result != LEN_FLD_SIZE)
231 | {
232 | printf("short write: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result);
233 | exit(1);
234 | }
235 |
236 | int32_t status;
237 |
238 | while ((result = read(st_fd, &status, LEN_FLD_SIZE)) == -1 && errno == EINTR)
239 | {
240 | continue;
241 | }
242 |
243 | memcpy(extra_counters, trace_bits, MAP_SIZE);
244 |
245 | if (result == -1)
246 | {
247 | die_sys("read() failed");
248 | }
249 |
250 | if (result == 0)
251 | {
252 | die("The child process terminated unexpectedly.");
253 | }
254 |
255 | if (result != LEN_FLD_SIZE)
256 | {
257 | printf("short read: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result);
258 | exit(1);
259 | }
260 |
261 | if (status)
262 | {
263 | __builtin_trap();
264 | }
265 |
266 | return 0;
267 | }
268 |
--------------------------------------------------------------------------------
/libfuzzer-dotnet-windows.cc:
--------------------------------------------------------------------------------
1 | #include "errno.h"
2 | #include "stddef.h"
3 | #include "stdint.h"
4 | #include "stdio.h"
5 | #include "stdlib.h"
6 | #include "strsafe.h"
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #pragma comment(lib, "rpcrt4.lib")
16 |
17 | #ifdef __cplusplus
18 | #define FUZZ_EXPORT extern "C" __declspec(dllexport)
19 | #else
20 | #define FUZZ_EXPORT __declspec(dllexport)
21 | #endif
22 |
23 | // Used for shared memory segment size
24 | #define MAP_SIZE (1 << 16)
25 | #define DATA_SIZE (1 << 20)
26 |
27 | #define LEN_FLD_SIZE 4
28 |
29 | // Shared memory and pipe designators
30 | #define SHM_ID_VAR "__LIBFUZZER_SHM_ID"
31 | #define PIPE_HANDLE_ST_WR_ID "__LIBFUZZER_STATUS_PIPE_ID"
32 | #define PIPE_HANDLE_CTL_RD_ID "__LIBFUZZER_CONTROL_PIPE_ID"
33 |
34 | // Use extra_counters for coverage
35 | #pragma section(".data$__libfuzzer_extra_counters")
36 | __declspec(allocate(".data$__libfuzzer_extra_counters")) uint8_t __libfuzzer_extra_counters[64 * 1024];
37 |
38 | static const char *target_path_name = "--target_path";
39 | static const char *target_arg_name = "--target_arg";
40 |
41 | static const char *target_path;
42 | static const char *target_arg;
43 | static const char *target_for_process;
44 |
45 | static HANDLE hMemFile;
46 | static PVOID pBuf;
47 |
48 | // Handles for pipes
49 | HANDLE hst_Rd = NULL;
50 | HANDLE hctl_Wr = NULL;
51 |
52 | // Information about the child process
53 | PROCESS_INFORMATION child_pi = {};
54 |
55 | static void die(const char *msg)
56 | {
57 | printf("%s\n", msg);
58 | exit(1);
59 | }
60 |
61 | static void die_lasterror(const char *msg)
62 | {
63 | LPSTR buffer = NULL;
64 | DWORD lasterror = GetLastError();
65 | DWORD chars = FormatMessageA(
66 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
67 | NULL,
68 | lasterror,
69 | 0, // default language
70 | (LPSTR)&buffer, // this looks wrong but FORMAT_MESSAGE_ALLOCATE_BUFFER changes the interpretation of this parameter
71 | 0,
72 | NULL);
73 |
74 | if (chars == 0) {
75 | printf("%s [%#lx]\n", msg, lasterror);
76 | } else {
77 | printf("%s [%#lx]: %s\n", msg, lasterror, buffer);
78 | }
79 |
80 | exit(1);
81 | }
82 |
83 | static void close_shm()
84 | {
85 | UnmapViewOfFile(pBuf);
86 | CloseHandle(hMemFile);
87 | }
88 |
89 | // Read the flag value from the single command line parameter. For example,
90 | // read_flag_value("--target_path=binary", "--target-path") will return "binary".
91 | static const char *read_flag_value(const char *param, const char *name)
92 | {
93 | size_t len = strlen(name);
94 |
95 | if (strstr(param, name) == param && param[len] == '=' && param[len + 1])
96 | {
97 | return ¶m[len + 1];
98 | }
99 |
100 | return NULL;
101 | }
102 |
103 | // Read target_path (the path to .NET executable) and target_arg (optional command
104 | // line argument that can be passed to .NET executable) from the command line parameters.
105 | static void parse_flags(int argc, char **argv)
106 | {
107 | for (int i = 0; i < argc; ++i)
108 | {
109 | char *param = argv[i];
110 |
111 | if (!target_path)
112 | {
113 | target_path = read_flag_value(param, target_path_name);
114 | }
115 |
116 | if (!target_arg)
117 | {
118 | target_arg = read_flag_value(param, target_arg_name);
119 | }
120 | }
121 | }
122 |
123 | // Start the .NET child process and initialize two pipes and one shared
124 | // memory segment for the communication between the parent and the child.
125 | FUZZ_EXPORT int __cdecl LLVMFuzzerInitialize(int *argc, char ***argv)
126 | {
127 | int32_t status = 0;
128 | DWORD dwRead = 0;
129 | DWORD dwWrite = 0;
130 | BOOL rSuccess = FALSE;
131 | BOOL bSuccess = FALSE;
132 |
133 | // security attributes for pipes to have inheritable handles
134 | SECURITY_ATTRIBUTES saAttr;
135 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
136 | saAttr.bInheritHandle = TRUE;
137 | saAttr.lpSecurityDescriptor = NULL;
138 |
139 | parse_flags(*argc, *argv);
140 |
141 | if (!target_path)
142 | {
143 | die("You must specify the target path by using the --target_path command line flag.");
144 | }
145 |
146 | HANDLE hst_Wr = NULL;
147 | HANDLE hctl_Rd = NULL;
148 |
149 | // Create pipes to read status and write size to managed code (`Fuzzer.Libfuzzer.Run`)
150 | if (!CreatePipe(&hst_Rd, &hst_Wr, &saAttr, 0))
151 | {
152 | die_lasterror("CreatePipe() failed");
153 | }
154 |
155 | if (!CreatePipe(&hctl_Rd, &hctl_Wr, &saAttr, 0))
156 | {
157 | die_lasterror("CreatePipe() failed");
158 | }
159 |
160 | UUID uuid;
161 | if (RPC_S_OK != UuidCreate(&uuid))
162 | {
163 | die("Could not create an UUID");
164 | }
165 | TCHAR *sharedMemName;
166 | if (RPC_S_OK != UuidToStringA(&uuid, (unsigned char **)&sharedMemName))
167 | {
168 | die("Could not convert UUID to a string\n");
169 | }
170 |
171 | hMemFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MAP_SIZE + DATA_SIZE, sharedMemName);
172 | if (hMemFile == NULL)
173 | {
174 | die_lasterror("CreateFileMapping() failed");
175 | }
176 |
177 | atexit(close_shm);
178 | pBuf = MapViewOfFile(hMemFile, FILE_MAP_ALL_ACCESS, 0, 0, MAP_SIZE + DATA_SIZE);
179 | if (pBuf == NULL)
180 | {
181 | die_lasterror("MapViewOfFile() failed");
182 | }
183 |
184 | // Create environment variables for pipes and shared memory to be read by sharpfuzz
185 | TCHAR ctl_rd_Id[10] = {0};
186 | TCHAR st_wr_Id[10] = {0};
187 | if (FAILED(StringCchPrintfA(ctl_rd_Id, sizeof(ctl_rd_Id) - 1, "%d", (UINT_PTR)hctl_Rd)))
188 | {
189 | die("StringCchPrintfA() failed");
190 | }
191 | if (FAILED(StringCchPrintfA(st_wr_Id, sizeof(st_wr_Id) - 1, "%d", (UINT_PTR)hst_Wr)))
192 | {
193 | die("StringCchPrintfA() failed");
194 | }
195 |
196 | if (!SetEnvironmentVariable(SHM_ID_VAR, sharedMemName))
197 | {
198 | die_lasterror("SetEnvironmentVariable() failed setting shared memory ID");
199 | }
200 | if (!SetEnvironmentVariable(PIPE_HANDLE_CTL_RD_ID, ctl_rd_Id))
201 | {
202 | die_lasterror("SetEnvironmentVariable() failed setting control pipe ID");
203 | }
204 | if (!SetEnvironmentVariable(PIPE_HANDLE_ST_WR_ID, st_wr_Id))
205 | {
206 | die_lasterror("SetEnvironmentVariable() failed setting status pipe ID");
207 | }
208 |
209 | // Create a job object to manage the fuzzer process tree.
210 | //
211 | // We will configure it to do the following:
212 | // - Automatically add new processes to the job on calls to `CreateProcess()`
213 | // - Kill all job processes when the last job handle is closed
214 | //
215 | // Since this process will hold the only job handle, the target child process
216 | // will be terminated even on abnormal exit of the parent harness.
217 |
218 | // Disable child inheritance of the job handle. This ensures that the current process
219 | // holds the _only_ handle, so on exit, all job processes will be killed.
220 | BOOL inherit_job_handle = FALSE;
221 |
222 | SECURITY_ATTRIBUTES job_attrs = {
223 | sizeof(SECURITY_ATTRIBUTES),
224 | NULL,
225 | inherit_job_handle,
226 | };
227 | HANDLE job = CreateJobObjectA(&job_attrs, NULL);
228 |
229 | // Terminate other (child) processes when all job handles are closed.
230 | JOBOBJECT_BASIC_LIMIT_INFORMATION li = {0};
231 | li.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
232 |
233 | // Setting `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` requires the use of an enclosing
234 | // `JOBOBJECT_EXTENDED_LIMIT_INFORMATION` struct.
235 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION eli = {0};
236 | eli.BasicLimitInformation = li;
237 |
238 | if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &eli, sizeof(eli)))
239 | {
240 | die_lasterror("failed to set `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`");
241 | }
242 |
243 | // Assign the current process to the job. Later calls to `CreateProcess()` will automatically
244 | // assign the created child processes to the job object.
245 | if (!AssignProcessToJobObject(job, GetCurrentProcess()))
246 | {
247 | die_lasterror("failed to assign `libfuzzer-dotnet` process to job");
248 | }
249 |
250 | if (target_arg)
251 | {
252 | size_t target_path_len = strlen(target_path);
253 | size_t target_arg_len = strlen(target_arg);
254 |
255 | char *temp_target = new char[target_path_len + 1 + target_arg_len + 1];
256 | strcpy_s(temp_target, target_path_len + 1, target_path);
257 | temp_target[target_path_len] = ' ';
258 | strcpy_s(temp_target + target_path_len + 1, target_arg_len + 1, target_arg);
259 | target_for_process = temp_target;
260 | }
261 | else
262 | {
263 | target_for_process = target_path;
264 | }
265 | PROCESS_INFORMATION pi;
266 | STARTUPINFO si;
267 | ZeroMemory(&si, sizeof(si));
268 | si.cb = sizeof(si);
269 | ZeroMemory(&pi, sizeof(pi));
270 | if (!CreateProcess(NULL, (LPSTR)target_for_process, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
271 | {
272 | die_lasterror("CreateProcess() failed");
273 | }
274 |
275 | // read status for intialization
276 | if (!ReadFile(hst_Rd, &status, LEN_FLD_SIZE, &dwRead, NULL))
277 | {
278 | die_lasterror("ReadFile() failed");
279 | }
280 |
281 | if (dwRead != LEN_FLD_SIZE)
282 | {
283 | printf("Short read");
284 | exit(1);
285 | }
286 |
287 | // now we're initialized and the process is running
288 | child_pi = pi;
289 |
290 | // Close our handles to the pipes we passed to the child process.
291 | //
292 | // Otherwise, if the child process exits without writing status
293 | // we will still be blocked on pipe read and never exit.
294 | //
295 | // This happens e.g. if the child process throws an exception which
296 | // cannot be handled by catch clauses (e.g. AccessViolationException),
297 | // or dies for some other reason.
298 | CloseHandle(hctl_Rd);
299 | CloseHandle(hst_Wr);
300 |
301 | return 0;
302 | }
303 |
304 | // Fuzz with `data` by writing it to the shared memory segment, sending
305 | // the size of the data to the .NET process (which will then run
306 | // its own fuzzing function on the shared memory data), and receiving
307 | // the status of the executed operation.
308 | FUZZ_EXPORT int __cdecl LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
309 | {
310 | if (size > DATA_SIZE)
311 | {
312 | die("Size of the input data must not exceed 1 MiB.");
313 | }
314 |
315 | ZeroMemory((PVOID)pBuf, MAP_SIZE);
316 | CopyMemory((PVOID)((PUCHAR)pBuf + MAP_SIZE), data, size);
317 |
318 | DWORD dwRead;
319 | DWORD dwWrite;
320 | BOOL writeSuccess = FALSE;
321 | BOOL readSuccess = FALSE;
322 |
323 | // write size, read status
324 | if (!WriteFile(hctl_Wr, &size, LEN_FLD_SIZE, &dwWrite, NULL))
325 | {
326 | die_lasterror("WriteFile() failed");
327 | }
328 |
329 | if (dwWrite != LEN_FLD_SIZE)
330 | {
331 | printf("Short write");
332 | exit(1);
333 | }
334 |
335 | int32_t status;
336 |
337 | if (!ReadFile(hst_Rd, &status, LEN_FLD_SIZE, &dwRead, NULL))
338 | {
339 | if (GetLastError() == ERROR_BROKEN_PIPE)
340 | {
341 | DWORD exitCode = 0;
342 | if (GetExitCodeProcess(child_pi.hProcess, &exitCode))
343 | {
344 | printf("SharpFuzz process exited with code %#lx, aborting\n", exitCode);
345 | }
346 |
347 | abort(); // child process presumably crashed
348 | }
349 | else
350 | {
351 | die_lasterror("ReadFile() failed");
352 | }
353 | }
354 |
355 | if (dwRead != LEN_FLD_SIZE)
356 | {
357 | printf("Short read");
358 | exit(1);
359 | }
360 |
361 | CopyMemory(__libfuzzer_extra_counters, (PVOID)pBuf, MAP_SIZE);
362 |
363 | if (status)
364 | {
365 | abort();
366 | }
367 |
368 | return 0;
369 | }
370 |
--------------------------------------------------------------------------------