├── .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 | --------------------------------------------------------------------------------