diff --git a/libfuzzer-dotnet-windows.cc b/libfuzzer-dotnet-windows.cc index ed62f95..1402051 100644 --- a/libfuzzer-dotnet-windows.cc +++ b/libfuzzer-dotnet-windows.cc @@ -33,10 +33,11 @@ __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_arg_name = "--target_arg"; +static const char *target_args_name = "--target_args"; static const char *target_path; -static const char *target_arg; +static const char *target_args; static const char *target_for_process; static HANDLE hMemFile; @@ -80,8 +81,8 @@ static const char *read_flag_value(const char *param, const char *name) 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. +// Read target_path (the path to .NET executable) and target_args (optional command +// line arguments 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) @@ -93,13 +94,31 @@ static void parse_flags(int argc, char **argv) target_path = read_flag_value(param, target_path_name); } - if (!target_arg) + if (!target_args) { - target_arg = read_flag_value(param, target_arg_name); + target_args = read_flag_value(param, target_args_name); + + if (!target_args) + { + target_args = read_flag_value(param, target_arg_name); + } } } } +static int replace_char(char *str, char orig, char replacement) +{ + int count = 0; + + while ((str = strchr(str, orig)) != NULL) + { + *str++ = replacement; + count++; + } + + return count; +} + // 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) @@ -173,14 +192,24 @@ FUZZ_EXPORT int __cdecl LLVMFuzzerInitialize(int *argc, char ***argv) { die_sys("SetEnvironmentVariable() failed setting status pipe ID"); } - if (target_arg) + if (target_args) { - char *temp_target = new char[strlen(target_path) + strlen(target_arg) + 1]; - strcpy(temp_target, target_path); - strcpy(temp_target + strlen(target_path), target_arg); + char* temp_args = new char[strlen(target_args) + 1]; + strcpy_s(temp_args, strlen(target_args) + 1, target_args); + replace_char(temp_args, ',', ' '); + + char* temp_target = new char[strlen(target_path) + 1 + strlen(temp_args) + 1]; + strcpy_s(temp_target, strlen(target_path) + 1, target_path); + temp_target[strlen(target_path)] = ' '; + strcpy_s(temp_target + strlen(target_path) + 1, strlen(temp_args) + 1, temp_args); target_for_process = temp_target; + + delete[] temp_args; + } + else + { + target_for_process = target_path; } - target_for_process = target_path; PROCESS_INFORMATION pi; STARTUPINFO si; ZeroMemory(&si, sizeof(si)); diff --git a/libfuzzer-dotnet.cc b/libfuzzer-dotnet.cc index 255cccf..bfe737b 100644 --- a/libfuzzer-dotnet.cc +++ b/libfuzzer-dotnet.cc @@ -5,6 +5,9 @@ #include "stdlib.h" #include "string.h" #include "unistd.h" +#include +#include +#include #include #define _STR(x) #x @@ -25,10 +28,12 @@ __attribute__((weak, section("__libfuzzer_extra_counters"))) uint8_t extra_counters[MAP_SIZE]; static const char *target_path_name = "--target_path"; -static const char *target_arg_name = "--target_arg"; +static const char *target_arg_name = "--target_arg"; +static const char *target_args_name = "--target_args"; static const char *target_path; static const char *target_arg; +static const char *target_args; static int ctl_fd; static int st_fd; @@ -38,167 +43,179 @@ static pid_t child_pid; static void die(const char *msg) { - printf("%s\n", msg); - exit(1); + printf("%s\n", msg); + exit(1); } static void die_sys(const char *msg) { - printf("%s: %s\n", msg, strerror(errno)); - exit(1); + printf("%s: %s\n", msg, strerror(errno)); + exit(1); } static void remove_shm() { - shmctl(shm_id, IPC_RMID, NULL); + shmctl(shm_id, IPC_RMID, NULL); } // 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); + size_t len = strlen(name); - if (strstr(param, name) == param && param[len] == '=' && param[len + 1]) - { - return ¶m[len + 1]; - } + if (strstr(param, name) == param && param[len] == '=' && param[len + 1]) + { + return ¶m[len + 1]; + } - return NULL; + 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. +// Read target_path (the path to .NET executable) and target_args (optional command +// line arguments 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); - } - } + 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); + } + + if (!target_args) + { + target_args = read_flag_value(param, target_args_name); + } + } } +static char **get_arguments(const char *str, const char delimiter = ','); + // Start the .NET child process and initialize two pipes and one shared // memory segment for the communication between the parent and the child. extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { - parse_flags(*argc, *argv); - - if (!target_path) - { - die("You must specify the target path by using the --target_path command line flag."); - } - - int ctl_pipe[2]; - int st_pipe[2]; - - if (pipe(ctl_pipe) || pipe(st_pipe)) - { - die_sys("pipe() failed"); - } - - shm_id = shmget(IPC_PRIVATE, MAP_SIZE + DATA_SIZE, IPC_CREAT | IPC_EXCL | 0600); - - if (shm_id < 0) - { - die_sys("shmget() failed"); - } - - atexit(remove_shm); - - trace_bits = static_cast(shmat(shm_id, NULL, 0)); - - if (trace_bits == (void *)-1) - { - die_sys("shmat() failed"); - } - - child_pid = fork(); - - if (child_pid < 0) - { - die_sys("fork() failed"); - } - - if (!child_pid) - { - if (dup2(ctl_pipe[0], CTL_FD) < 0 || dup2(st_pipe[1], ST_FD) < 0) - { - die_sys("dup() failed"); - } - - close(ctl_pipe[0]); - close(ctl_pipe[1]); - close(st_pipe[0]); - close(st_pipe[1]); - - char shm_str[12]; - sprintf(shm_str, "%d", shm_id); - - if (setenv(SHM_ID_VAR, shm_str, 1)) - { - die_sys("setenv() failed setting shared memory ID"); - } - - if (setenv(CTL_FD_VAR, STR(CTL_FD), 1)) - { - die_sys("setenv() failed setting control pipe ID"); - } - - if (setenv(ST_FD_VAR, STR(ST_FD), 1)) - { - die_sys("setenv() failed setting status pipe ID"); - } - - if (target_arg) - { - execlp(target_path, "", target_arg, NULL); - } - else - { - execlp(target_path, "", NULL); - } - - die_sys("execlp() failed"); - } - else - { - close(ctl_pipe[0]); - close(st_pipe[1]); - - ctl_fd = ctl_pipe[1]; - st_fd = st_pipe[0]; - - ssize_t result; - int32_t status; - - while ((result = read(st_fd, &status, LEN_FLD_SIZE)) == -1 && errno == EINTR) - { - continue; - } - - if (result == -1) - { - die_sys("read() failed"); - } - - if (result != LEN_FLD_SIZE) - { - printf("short read: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result); - exit(1); - } - } - - return 0; + parse_flags(*argc, *argv); + + if (!target_path) + { + die("You must specify the target path by using the --target_path command line flag."); + } + + int ctl_pipe[2]; + int st_pipe[2]; + + if (pipe(ctl_pipe) || pipe(st_pipe)) + { + die_sys("pipe() failed"); + } + + shm_id = shmget(IPC_PRIVATE, MAP_SIZE + DATA_SIZE, IPC_CREAT | IPC_EXCL | 0600); + + if (shm_id < 0) + { + die_sys("shmget() failed"); + } + + atexit(remove_shm); + + trace_bits = static_cast(shmat(shm_id, NULL, 0)); + + if (trace_bits == (void *)-1) + { + die_sys("shmat() failed"); + } + + child_pid = fork(); + + if (child_pid < 0) + { + die_sys("fork() failed"); + } + + if (!child_pid) + { + if (dup2(ctl_pipe[0], CTL_FD) < 0 || dup2(st_pipe[1], ST_FD) < 0) + { + die_sys("dup() failed"); + } + + close(ctl_pipe[0]); + close(ctl_pipe[1]); + close(st_pipe[0]); + close(st_pipe[1]); + + char shm_str[12]; + sprintf(shm_str, "%d", shm_id); + + if (setenv(SHM_ID_VAR, shm_str, 1)) + { + die_sys("setenv() failed setting shared memory ID"); + } + + if (setenv(CTL_FD_VAR, STR(CTL_FD), 1)) + { + die_sys("setenv() failed setting control pipe ID"); + } + + if (setenv(ST_FD_VAR, STR(ST_FD), 1)) + { + die_sys("setenv() failed setting status pipe ID"); + } + + if (target_args) + { + char** args = get_arguments(target_args); + execvp(target_path, args); + } + else if (target_arg) + { + execlp(target_path, "", target_arg, NULL); + } + else + { + execlp(target_path, "", NULL); + } + + die_sys("execlp() failed"); + } + else + { + close(ctl_pipe[0]); + close(st_pipe[1]); + + ctl_fd = ctl_pipe[1]; + st_fd = st_pipe[0]; + + ssize_t result; + int32_t status; + + while ((result = read(st_fd, &status, LEN_FLD_SIZE)) == -1 && errno == EINTR) + { + continue; + } + + if (result == -1) + { + die_sys("read() failed"); + } + + if (result != LEN_FLD_SIZE) + { + printf("short read: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result); + exit(1); + } + } + + return 0; } // Fuzz the data by writing it to the shared memory segment, sending @@ -207,61 +224,93 @@ extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) // the status of the executed operation. extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - if (size > DATA_SIZE) - { - die("Size of the input data must not exceed 1 MiB."); - } - - memset(trace_bits, 0, MAP_SIZE); - memcpy(trace_bits + MAP_SIZE, data, size); - - ssize_t result; - - while ((result = write(ctl_fd, &size, LEN_FLD_SIZE)) == -1 && errno == EINTR) - { - continue; - } - - if (result == -1) - { - die_sys("write() failed"); - } - - if (result != LEN_FLD_SIZE) - { - printf("short write: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result); - exit(1); - } - - int32_t status; - - while ((result = read(st_fd, &status, LEN_FLD_SIZE)) == -1 && errno == EINTR) - { - continue; - } - - memcpy(extra_counters, trace_bits, MAP_SIZE); - - if (result == -1) - { - die_sys("read() failed"); - } - - if (result == 0) - { - die("The child process terminated unexpectedly."); - } - - if (result != LEN_FLD_SIZE) - { - printf("short read: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result); - exit(1); - } - - if (status) - { - __builtin_trap(); - } - - return 0; + if (size > DATA_SIZE) + { + die("Size of the input data must not exceed 1 MiB."); + } + + memset(trace_bits, 0, MAP_SIZE); + memcpy(trace_bits + MAP_SIZE, data, size); + + ssize_t result; + + while ((result = write(ctl_fd, &size, LEN_FLD_SIZE)) == -1 && errno == EINTR) + { + continue; + } + + if (result == -1) + { + die_sys("write() failed"); + } + + if (result != LEN_FLD_SIZE) + { + printf("short write: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result); + exit(1); + } + + int32_t status; + + while ((result = read(st_fd, &status, LEN_FLD_SIZE)) == -1 && errno == EINTR) + { + continue; + } + + memcpy(extra_counters, trace_bits, MAP_SIZE); + + if (result == -1) + { + die_sys("read() failed"); + } + + if (result == 0) + { + die("The child process terminated unexpectedly."); + } + + if (result != LEN_FLD_SIZE) + { + printf("short read: expected %d bytes, got %zd bytes\n", LEN_FLD_SIZE, result); + exit(1); + } + + if (status) + { + __builtin_trap(); + } + + return 0; +} + +static char **get_arguments(const char *str, const char delimiter) +{ + using namespace std; + + stringstream ss(str); + vector tokens; + + while (ss.good()) + { + string token; + getline(ss, token, delimiter); + tokens.push_back(token); + } + + size_t size = tokens.size(); + size_t tokenCount = size + 2; + char **args = new char *[tokenCount]; + args[0] = (char*)""; + args[tokenCount - 1] = NULL; // must be NULL-terminated, cf. https://linux.die.net/man/3/execlp + + for (size_t i = 0; i < size; ++i) + { + string tmp = tokens[i]; + char *arg = new char[tmp.length() + 1]; + tmp.copy(arg, tmp.length()); + arg[tmp.length()] = '\0'; + args[i + 1] = arg; + } + + return args; }