-
Notifications
You must be signed in to change notification settings - Fork 9
/
libfuzzer-dotnet-windows.cc
executable file
·324 lines (272 loc) · 8.99 KB
/
libfuzzer-dotnet-windows.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#include "errno.h"
#include "stddef.h"
#include "stdint.h"
#include "stdio.h"
#include "stdlib.h"
#include "strsafe.h"
#include <fcntl.h>
#include <math.h>
#include <process.h>
#include <string>
#include <tchar.h>
#include <windows.h>
#include <rpcdce.h>
#pragma comment(lib, "rpcrt4.lib")
#ifdef __cplusplus
#define FUZZ_EXPORT extern "C" __declspec(dllexport)
#else
#define FUZZ_EXPORT __declspec(dllexport)
#endif
// Used for shared memory segment size
#define MAP_SIZE (1 << 16)
#define DATA_SIZE (1 << 20)
#define LEN_FLD_SIZE 4
// Shared memory and pipe designators
#define SHM_ID_VAR "__LIBFUZZER_SHM_ID"
#define PIPE_HANDLE_ST_WR_ID "__LIBFUZZER_STATUS_PIPE_ID"
#define PIPE_HANDLE_CTL_RD_ID "__LIBFUZZER_CONTROL_PIPE_ID"
// Use extra_counters for coverage
#pragma section(".data$__libfuzzer_extra_counters")
__declspec(allocate(".data$__libfuzzer_extra_counters")) uint8_t __libfuzzer_extra_counters[64 * 1024];
static const char *target_path_name = "--target_path";
static const char *target_arg_name = "--target_arg";
static const char *target_path;
static const char *target_arg;
static const char *target_for_process;
static HANDLE hMemFile;
static PVOID pBuf;
// Handles for pipes
HANDLE hst_Rd = NULL;
HANDLE hst_Wr = NULL;
HANDLE hctl_Rd = NULL;
HANDLE hctl_Wr = NULL;
static void die(const char *msg)
{
printf("%s\n", msg);
exit(1);
}
static void die_sys(const char *msg)
{
char error_msg[256];
strerror_s(error_msg, sizeof(error_msg), errno);
printf("%s: %s\n", msg, error_msg);
exit(1);
}
static void close_shm()
{
UnmapViewOfFile(pBuf);
CloseHandle(hMemFile);
}
// Read the flag value from the single command line parameter. For example,
// read_flag_value("--target_path=binary", "--target-path") will return "binary".
static const char *read_flag_value(const char *param, const char *name)
{
size_t len = strlen(name);
if (strstr(param, name) == param && param[len] == '=' && param[len + 1])
{
return ¶m[len + 1];
}
return NULL;
}
// Read target_path (the path to .NET executable) and target_arg (optional command
// line argument that can be passed to .NET executable) from the command line parameters.
static void parse_flags(int argc, char **argv)
{
for (int i = 0; i < argc; ++i)
{
char *param = argv[i];
if (!target_path)
{
target_path = read_flag_value(param, target_path_name);
}
if (!target_arg)
{
target_arg = read_flag_value(param, target_arg_name);
}
}
}
// Start the .NET child process and initialize two pipes and one shared
// memory segment for the communication between the parent and the child.
FUZZ_EXPORT int __cdecl LLVMFuzzerInitialize(int *argc, char ***argv)
{
int32_t status = 0;
DWORD dwRead = 0;
DWORD dwWrite = 0;
BOOL rSuccess = FALSE;
BOOL bSuccess = FALSE;
// security attributes for pipes to have inheritable handles
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
parse_flags(*argc, *argv);
if (!target_path)
{
die("You must specify the target path by using the --target_path command line flag.");
}
// Create pipes to read status and write size to managed code (`Fuzzer.Libfuzzer.Run`)
if (!CreatePipe(&hst_Rd, &hst_Wr, &saAttr, 0))
{
die_sys("CreatePipe() failed");
}
if (!CreatePipe(&hctl_Rd, &hctl_Wr, &saAttr, 0))
{
die_sys("CreatePipe() failed");
}
UUID uuid;
if (RPC_S_OK != UuidCreate(&uuid))
{
die("Could not create an UUID");
}
TCHAR *sharedMemName;
if (RPC_S_OK != UuidToStringA(&uuid, (unsigned char **)&sharedMemName))
{
die("Could not convert UUID to a string\n");
}
hMemFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MAP_SIZE + DATA_SIZE, sharedMemName);
if (hMemFile == NULL)
{
die_sys("CreateFileMapping() failed");
}
atexit(close_shm);
pBuf = MapViewOfFile(hMemFile, FILE_MAP_ALL_ACCESS, 0, 0, MAP_SIZE + DATA_SIZE);
if (pBuf == NULL)
{
CloseHandle(hMemFile);
die_sys("MapViewOfFile() failed");
}
// Create environment variables for pipes and shared memory to be read by sharpfuzz
TCHAR ctl_rd_Id[10] = {0};
TCHAR st_wr_Id[10] = {0};
if (FAILED(StringCchPrintfA(ctl_rd_Id, sizeof(ctl_rd_Id) - 1, "%d", (UINT_PTR)hctl_Rd)))
{
die_sys("StringCchPrintfA() failed");
}
if (FAILED(StringCchPrintfA(st_wr_Id, sizeof(st_wr_Id) - 1, "%d", (UINT_PTR)hst_Wr)))
{
die_sys("StringCchPrintfA() failed");
}
if (!SetEnvironmentVariable(SHM_ID_VAR, sharedMemName))
{
die_sys("SetEnvironmentVariable() failed setting shared memory ID");
}
if (!SetEnvironmentVariable(PIPE_HANDLE_CTL_RD_ID, ctl_rd_Id))
{
die_sys("SetEnvironmentVariable() failed setting control pipe ID");
}
if (!SetEnvironmentVariable(PIPE_HANDLE_ST_WR_ID, st_wr_Id))
{
die_sys("SetEnvironmentVariable() failed setting status pipe ID");
}
// Create a job object to manage the fuzzer process tree.
//
// We will configure it to do the following:
// - Automatically add new processes to the job on calls to `CreateProcess()`
// - Kill all job processes when the last job handle is closed
//
// Since this process will hold the only job handle, the target child process
// will be terminated even on abnormal exit of the parent harness.
// Disable child inheritance of the job handle. This ensures that the current process
// holds the _only_ handle, so on exit, all job processes will be killed.
BOOL inherit_job_handle = FALSE;
SECURITY_ATTRIBUTES job_attrs = {
sizeof(SECURITY_ATTRIBUTES),
NULL,
inherit_job_handle,
};
HANDLE job = CreateJobObjectA(&job_attrs, NULL);
// Terminate other (child) processes when all job handles are closed.
JOBOBJECT_BASIC_LIMIT_INFORMATION li = {0};
li.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
// Setting `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` requires the use of an enclosing
// `JOBOBJECT_EXTENDED_LIMIT_INFORMATION` struct.
JOBOBJECT_EXTENDED_LIMIT_INFORMATION eli = {0};
eli.BasicLimitInformation = li;
if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &eli, sizeof(eli)))
{
die_sys("failed to set `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`");
}
// Assign the current process to the job. Later calls to `CreateProcess()` will automatically
// assign the created child processes to the job object.
if (!AssignProcessToJobObject(job, GetCurrentProcess()))
{
die_sys("failed to assign `libfuzzer-dotnet` process to job");
}
if (target_arg)
{
size_t target_path_len = strlen(target_path);
size_t target_arg_len = strlen(target_arg);
char *temp_target = new char[target_path_len + 1 + target_arg_len + 1];
strcpy_s(temp_target, target_path_len + 1, target_path);
temp_target[target_path_len] = ' ';
strcpy_s(temp_target + target_path_len + 1, target_arg_len + 1, target_arg);
target_for_process = temp_target;
}
else
{
target_for_process = target_path;
}
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcess(NULL, (LPSTR)target_for_process, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
{
die_sys("CreateProcess() failed");
}
// read status for intialization
if (!ReadFile(hst_Rd, &status, LEN_FLD_SIZE, &dwRead, NULL))
{
die_sys("ReadFile() failed");
}
if (dwRead != LEN_FLD_SIZE)
{
printf("Short read");
exit(1);
}
return 0;
}
// Fuzz with `data` by writing it to the shared memory segment, sending
// the size of the data to the .NET process (which will then run
// its own fuzzing function on the shared memory data), and receiving
// the status of the executed operation.
FUZZ_EXPORT int __cdecl LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size > DATA_SIZE)
{
die("Size of the input data must not exceed 1 MiB.");
}
ZeroMemory((PVOID)pBuf, MAP_SIZE);
CopyMemory((PVOID)((PUCHAR)pBuf + MAP_SIZE), data, size);
DWORD dwRead;
DWORD dwWrite;
BOOL writeSuccess = FALSE;
BOOL readSuccess = FALSE;
// write size, read status
if (!WriteFile(hctl_Wr, &size, LEN_FLD_SIZE, &dwWrite, NULL))
{
die_sys("WriteFile() failed");
}
if (dwWrite != LEN_FLD_SIZE)
{
printf("Short write");
exit(1);
}
int32_t status;
if (!ReadFile(hst_Rd, &status, LEN_FLD_SIZE, &dwRead, NULL))
{
die_sys("ReadFile() failed");
}
if (dwRead != LEN_FLD_SIZE)
{
printf("Short read");
exit(1);
}
CopyMemory(__libfuzzer_extra_counters, (PVOID)pBuf, MAP_SIZE);
if (status)
{
abort();
}
return 0;
}