diff --git a/tools/fuzzer/Dockerfile b/tools/fuzzer/Dockerfile new file mode 100644 index 0000000..bcea178 --- /dev/null +++ b/tools/fuzzer/Dockerfile @@ -0,0 +1,30 @@ +FROM swift + +RUN apt-get update && \ + apt-get install -y git build-essential clang libreadline-dev + +RUN git clone https://github.com/googleprojectzero/fuzzilli + +ADD . /fuzzilli/Targets/mujs + +RUN git -C /fuzzilli/Targets/mujs apply /fuzzilli/Targets/mujs/tools/fuzzer/fuzz.patch + +RUN make -C /fuzzilli/Targets/mujs \ + CC=clang \ + XCFLAGS="-DFUZZILLI -pipe -fsanitize=address -fno-omit-frame-pointer -fsanitize-coverage=trace-pc-guard" \ + LDFLAGS="-g -fsanitize=address -fsanitize-coverage=trace-pc-guard" + +RUN cp /fuzzilli/Targets/mujs/tools/fuzzer/mujsProfile.swift /fuzzilli/Sources/FuzzilliCli/Profiles + +RUN sed -i 's/jerryscript/mujs/g' /fuzzilli/Sources/FuzzilliCli/Profiles/Profile.swift + +RUN mkdir -p /fuzzilli/FuzzStorage/old_corpus + +WORKDIR /fuzzilli + +RUN swift build + +ENV NUM_CORES=1 +ENV DURATION=0 + +CMD ["/bin/sh", "-c", "timeout $DURATION swift run FuzzilliCli --profile=mujs --storagePath=FuzzStorage/ --jobs=$NUM_CORES --resume Targets/mujs/build/release/mujs"] diff --git a/tools/fuzzer/README.md b/tools/fuzzer/README.md new file mode 100644 index 0000000..df931a6 --- /dev/null +++ b/tools/fuzzer/README.md @@ -0,0 +1,16 @@ +# Fuzzer + +Fuzz MuJS with [fuzzilli](https://github.com/googleprojectzero/fuzzilli), a (coverage-)guided fuzzer for dynamic language interpreters based on a custom intermediate language ("FuzzIL") which can be mutated and translated to JavaScript. + +## Usage + +From the root of the git repository: + +```sh +docker build -t fuzz-mujs -f tools/fuzzer/Dockerfile . +docker run -it --rm -e NUM_CORES=1 -e DURATION=0 fuzz-mujs +``` + +You can modify `NUM_CORES` and `DURATION`. +`NUM_CORES` specifies the number of cores that will be used while fuzzing. +`DURATION` specifies how long the fuzzing will run, with 0 indicating that it will run indefinitely. diff --git a/tools/fuzzer/fuzz.patch b/tools/fuzzer/fuzz.patch new file mode 100644 index 0000000..131a4f2 --- /dev/null +++ b/tools/fuzzer/fuzz.patch @@ -0,0 +1,253 @@ +diff --git a/main.c b/main.c +index bbc5f2f..ac3528e 100644 +--- a/main.c ++++ b/main.c +@@ -1,6 +1,11 @@ + #include + #include ++#include + #include ++#include ++#include ++#include ++#include + #ifdef _MSC_VER + #include + #else +@@ -10,6 +15,138 @@ + + #include "mujs.h" + ++#ifdef FUZZILLI ++ ++#ifdef __linux__ ++#define S_IREAD __S_IREAD ++#define S_IWRITE __S_IWRITE ++#endif ++ ++#define REPRL_CRFD 100 ++#define REPRL_CWFD 101 ++#define REPRL_DRFD 102 ++#define REPRL_DWFD 103 ++ ++#define SHM_SIZE 0x100000 ++#define MAX_EDGES ((SHM_SIZE - 4) * 8) ++ ++#define CHECK(cond) if (!(cond)) { fprintf(stderr, "\"" #cond "\" failed\n"); _exit(-1); } ++ ++struct shmem_data { ++ uint32_t num_edges; ++ unsigned char edges[]; ++}; ++ ++struct shmem_data* __shmem; ++uint32_t *__edges_start, *__edges_stop; ++ ++void __sanitizer_cov_reset_edgeguards() ++{ ++ uint64_t N = 0; ++ for (uint32_t *x = __edges_start; x < __edges_stop && N < MAX_EDGES; x++) ++ *x = ++N; ++} ++ ++void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) ++{ ++ // Avoid duplicate initialization ++ if (start == stop || *start) ++ return; ++ ++ if (__edges_start != NULL || __edges_stop != NULL) { ++ fprintf(stderr, "Coverage instrumentation is only supported for a single module\n"); ++ _exit(-1); ++ } ++ ++ __edges_start = start; ++ __edges_stop = stop; ++ ++ // Map the shared memory region ++ const char* shm_key = getenv("SHM_ID"); ++ if (!shm_key) { ++ puts("[COV] no shared memory bitmap available, skipping"); ++ __shmem = (struct shmem_data*) malloc(SHM_SIZE); ++ } else { ++ int fd = shm_open(shm_key, O_RDWR, S_IREAD | S_IWRITE); ++ if (fd <= -1) { ++ fprintf(stderr, "Failed to open shared memory region: %s\n", strerror(errno)); ++ _exit(-1); ++ } ++ ++ __shmem = (struct shmem_data*) mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); ++ if (__shmem == MAP_FAILED) { ++ fprintf(stderr, "Failed to mmap shared memory region\n"); ++ _exit(-1); ++ } ++ } ++ ++ __sanitizer_cov_reset_edgeguards(); ++ ++ __shmem->num_edges = stop - start; ++ printf("[COV] edge counters initialized. Shared memory: %s with %u edges\n", shm_key, __shmem->num_edges); ++} ++ ++void __sanitizer_cov_trace_pc_guard(uint32_t *guard) ++{ ++ // There's a small race condition here: if this function executes in two threads for the same ++ // edge at the same time, the first thread might disable the edge (by setting the guard to zero) ++ // before the second thread fetches the guard value (and thus the index). However, our ++ // instrumentation ignores the first edge (see libcoverage.c) and so the race is unproblematic. ++ uint32_t index = *guard; ++ // If this function is called before coverage instrumentation is properly initialized we want to return early. ++ if (!index) return; ++ __shmem->edges[index / 8] |= 1 << (index % 8); ++ *guard = 0; ++} ++ ++static void jsB_fuzzilli(js_State *J) ++{ ++ const char* str = js_tostring(J, 1); ++ if (!str) { ++ printf("js_fuzzilli NO CMD\n"); ++ return; ++ } ++ if (!strcmp(str, "FUZZILLI_CRASH")) { ++ printf("js_fuzzilli CRASH\n"); ++ int arg = js_tointeger(J, 2); ++ ++ switch (arg) { ++ case 0: ++ // check crash ++ *((char *) 0) = 0; ++ break; ++ case 1: { ++ // check ASAN ++ char *data = malloc(64); ++ free(data); ++ data[0]++; ++ break; ++ } ++ case 2: { ++ // check assert ++ assert(0); ++ break; ++ } ++ } ++ } else if (!strcmp(str, "FUZZILLI_PRINT")) { ++ // get next argument off the stack to print ++ const char* print_str = js_tostring(J, 2); ++ printf("js_fuzzilli PRINT %s\n", print_str); ++ FILE* fzliout = fdopen(REPRL_DWFD, "w"); ++ if (!fzliout) { ++ fprintf(stderr, "Fuzzer output channel not available, printing to stdout instead\n"); ++ fzliout = stdout; ++ } ++ if (print_str) { ++ fprintf(fzliout, "%s\n", print_str); ++ } ++ fflush(fzliout); ++ } ++ return; ++} ++ ++#endif // FUZZILLI ++ + static char *xoptarg; /* Global argument pointer. */ + static int xoptind = 0; /* Global argv index. */ + static int xgetopt(int argc, char *argv[], char *optstring) +@@ -288,6 +425,8 @@ static void usage(void) + exit(1); + } + ++#ifndef FUZZILLI ++ + int + main(int argc, char **argv) + { +@@ -381,3 +520,88 @@ main(int argc, char **argv) + + return status; + } ++ ++#else ++ ++int ++main(int argc, char **argv) ++{ ++ js_State *J; ++ int status = 0; ++ ++ char helo[] = "HELO"; ++ if ((write(REPRL_CWFD, helo, 4) != 4) || (read(REPRL_CRFD, helo, 4) != 4)) { ++ fprintf(stderr, "Error writing or reading HELO\n"); ++ _exit(-1); ++ } ++ if (memcmp(helo, "HELO", 4) != 0) { ++ fprintf(stderr, "Invalid response from parent\n"); ++ _exit(-1); ++ } ++ ++ while (1) { ++ unsigned action = 0; ++ ssize_t nread = read(REPRL_CRFD, &action, 4); ++ fflush(0); ++ if (nread != 4 || action != 0x63657865) { // 'exec' ++ fprintf(stderr, "Unknown action: %x\n", action); ++ _exit(-1); ++ } ++ ++ size_t script_size = 0; ++ read(REPRL_CRFD, &script_size, 8); ++ ++ ssize_t remaining = (ssize_t) script_size; ++ char* buffer = (char *) malloc(script_size+1); ++ ssize_t rv = read(REPRL_DRFD, buffer, (size_t) remaining); ++ if (rv <= 0) { ++ fprintf(stderr, "Failed to load script\n"); ++ _exit(-1); ++ } ++ ++ buffer[script_size] = 0; ++ ++ J = js_newstate(NULL, NULL, 0); ++ js_newcfunction(J, jsB_gc, "gc", 0); ++ js_setglobal(J, "gc"); ++ js_newcfunction(J, jsB_load, "load", 1); ++ js_setglobal(J, "load"); ++ js_newcfunction(J, jsB_compile, "compile", 2); ++ js_setglobal(J, "compile"); ++ js_newcfunction(J, jsB_print, "print", 0); ++ js_setglobal(J, "print"); ++ js_newcfunction(J, jsB_write, "write", 0); ++ js_setglobal(J, "write"); ++ js_newcfunction(J, jsB_read, "read", 1); ++ js_setglobal(J, "read"); ++ js_newcfunction(J, jsB_readline, "readline", 0); ++ js_setglobal(J, "readline"); ++ js_newcfunction(J, jsB_repr, "repr", 0); ++ js_setglobal(J, "repr"); ++ js_newcfunction(J, jsB_quit, "quit", 1); ++ js_setglobal(J, "quit"); ++ js_newcfunction(J, jsB_fuzzilli, "fuzzilli", 2); ++ js_setglobal(J, "fuzzilli"); ++ js_dostring(J, require_js); ++ js_dostring(J, stacktrace_js); ++ ++ int ret_value = js_dostring(J, buffer); ++ if (ret_value != 0) { ++ fprintf(stderr, "Failed to eval_buf reprl\n"); ++ } ++ fflush(stdout); ++ fflush(stderr); ++ status = (ret_value & 0xff) << 8; ++ if (write(REPRL_CWFD, &status, 4) != 4) { ++ fprintf(stderr, "Erroring writing return value over REPRL_CWFD\n"); ++ } ++ ++ js_gc(J, 0); ++ js_freestate(J); ++ __sanitizer_cov_reset_edgeguards(); ++ } ++ ++ return 0; ++} ++ ++#endif // FUZZILLI diff --git a/tools/fuzzer/mujsProfile.swift b/tools/fuzzer/mujsProfile.swift new file mode 100644 index 0000000..77c531b --- /dev/null +++ b/tools/fuzzer/mujsProfile.swift @@ -0,0 +1,31 @@ +import Fuzzilli + +let mujsProfile = Profile( + processArguments: [], + + processEnv: ["UBSAN_OPTIONS":"handle_segv=1", "ASAN_OPTIONS":"detect_leaks=0, abort_on_error=1"], + + codePrefix: """ + function main() { + """, + + codeSuffix: """ + } + main(); + """, + + ecmaVersion: ECMAScriptVersion.es5, + + crashTests: ["fuzzilli('FUZZILLI_CRASH', 0)", "fuzzilli('FUZZILLI_CRASH', 1)", "fuzzilli('FUZZILLI_CRASH', 2)"], + + additionalCodeGenerators: WeightedList([]), + + additionalProgramTemplates: WeightedList([]), + + disabledCodeGenerators: [], + + additionalBuiltins: [ + "gc" : .function([] => .undefined), + "print" : .function([] => .undefined), + ] +)