diff --git a/Sources/FuzzilliCli/Profiles/NjsProfile.swift b/Sources/FuzzilliCli/Profiles/NjsProfile.swift new file mode 100644 index 000000000..6d6efe243 --- /dev/null +++ b/Sources/FuzzilliCli/Profiles/NjsProfile.swift @@ -0,0 +1,59 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Fuzzilli + +let njsProfile = Profile( + processArgs: { randomize in + ["fuzz"] + }, + + processEnv: ["UBSAN_OPTIONS": "handle_segv=0"], + + maxExecsBeforeRespawn: 1000, + + timeout: 250, + + codePrefix: """ + """, + + codeSuffix: """ + """, + + ecmaVersion: ECMAScriptVersion.es6, + + startupTests: [ + // Check that the fuzzilli integration is available. + ("fuzzilli('FUZZILLI_PRINT', 'test')", .shouldSucceed), + + // Check that common crash types are detected. + ("fuzzilli('FUZZILLI_CRASH', 0)", .shouldCrash), + ("fuzzilli('FUZZILLI_CRASH', 1)", .shouldCrash), + ("fuzzilli('FUZZILLI_CRASH', 2)", .shouldCrash), + ], + + additionalCodeGenerators: [], + + additionalProgramTemplates: WeightedList([]), + + disabledCodeGenerators: [], + + disabledMutators: [], + + additionalBuiltins: [:], + + additionalObjectGroups: [], + + optionalPostProcessor: nil +) \ No newline at end of file diff --git a/Sources/FuzzilliCli/Profiles/Profile.swift b/Sources/FuzzilliCli/Profiles/Profile.swift index 5041f0f6e..a00fc69b2 100644 --- a/Sources/FuzzilliCli/Profiles/Profile.swift +++ b/Sources/FuzzilliCli/Profiles/Profile.swift @@ -51,4 +51,5 @@ let profiles = [ "xs": xsProfile, "v8holefuzzing": v8HoleFuzzingProfile, "serenity": serenityProfile, + "njs": njsProfile, ] diff --git a/Targets/njs/README.md b/Targets/njs/README.md new file mode 100644 index 000000000..c9a8c4f91 --- /dev/null +++ b/Targets/njs/README.md @@ -0,0 +1,14 @@ +# Target: NJS + +To build njs for fuzzing: +* Step 1 - prepare env + * Run `setup.sh`, this will: + * Clone the NJS repo from https://github.com/nginx/njs/ + * Apply the relevant patches & add a _fuzzilli_ JS module +* Step 2 - Build fuzzer + * Run `fuzzbuild.sh`, this will: + * Configure with `./configure --cc=clang --cc-opt="-g -fsanitize-coverage=trace-pc-guard"` + * Run `make njs_fuzzilli` + +The REPRL shell/fuzzable build will be saved at `/build/njs_fuzzilli` + diff --git a/Targets/njs/REVISION b/Targets/njs/REVISION new file mode 100644 index 000000000..94042f211 --- /dev/null +++ b/Targets/njs/REVISION @@ -0,0 +1 @@ +d8fbff1b0b1099fac5f75112580d671007584b00 \ No newline at end of file diff --git a/Targets/njs/fuzzbuild.sh b/Targets/njs/fuzzbuild.sh new file mode 100644 index 000000000..3e445c6de --- /dev/null +++ b/Targets/njs/fuzzbuild.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +cd njs/ +./configure --cc=clang --cc-opt="-g -fsanitize-coverage=trace-pc-guard" +make njs_fuzzilli \ No newline at end of file diff --git a/Targets/njs/mod/gen-fuzzilli-shell.sh b/Targets/njs/mod/gen-fuzzilli-shell.sh new file mode 100644 index 000000000..90e8f7d48 --- /dev/null +++ b/Targets/njs/mod/gen-fuzzilli-shell.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +NEEDLE='main(int argc, char \*\*argv)' +MODIF='not_main(int argc, char \*\*argv)' +sed -i "s/$NEEDLE/$MODIF/" ./external/njs_fuzzilli_shell.c + +sed -i '1s/^/int not_main(int argc, char **argv);\n/' ./external/njs_fuzzilli_shell.c \ No newline at end of file diff --git a/Targets/njs/mod/njs_coverage.h b/Targets/njs/mod/njs_coverage.h new file mode 100644 index 000000000..5dcc20975 --- /dev/null +++ b/Targets/njs/mod/njs_coverage.h @@ -0,0 +1,11 @@ +void __sanitizer_cov_reset_edgeguards(void); +void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop); +void __sanitizer_cov_trace_pc_guard(uint32_t *guard); + +struct shmem_data { + uint32_t num_edges; + unsigned char edges[]; +}; + +#define SHM_SIZE 0x100000 +#define MAX_EDGES ((SHM_SIZE - 4) * 8) \ No newline at end of file diff --git a/Targets/njs/mod/njs_fuzzilli.c b/Targets/njs/mod/njs_fuzzilli.c new file mode 100644 index 000000000..eaa26d065 --- /dev/null +++ b/Targets/njs/mod/njs_fuzzilli.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "njs_coverage.h" +#include + + +// defs +#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); } + +// Will be imported from `njs_fuzzilli_module.o` during linking phase +extern struct shmem_data* __shmem; +extern uint32_t *__edges_start, *__edges_stop; + + +njs_str_t* njs_fetch_fuzz_input(void); + +njs_str_t* njs_fetch_fuzz_input(void) { + njs_str_t *script = NULL; + char *script_src, *ptr; + size_t script_size = 0, remaining = 0; + unsigned action; + + script_size = 0; + CHECK(read(REPRL_CRFD, &action, 4) == 4); + if (action == 'cexe') { + CHECK(read(REPRL_CRFD, &script_size, 8) == 8); + } else { + fprintf(stderr, "Unknown action: %u\n", action); + _exit(-1); + } + + script_src = malloc(script_size+1); + ptr = script_src; + remaining = script_size; + + while (remaining > 0) { + ssize_t rv = read(REPRL_DRFD, ptr, remaining); + if (rv <= 0) { + fprintf(stderr, "Failed to load script\n"); + _exit(-1); + } + remaining -= rv; + ptr += rv; + } + + script = malloc(sizeof(njs_str_t)); + script_src[script_size] = '\0'; + script->start = (u_char*)script_src; + script->length = script_size; + + return script; +} + + +static njs_int_t +njs_main_fuzzable(njs_opts_t *opts) +{ + njs_int_t ret; + njs_engine_t *engine; + + engine = njs_create_engine(opts); + if (engine == NULL) { + return NJS_ERROR; + } + + ret = njs_console_init(opts, &njs_console); + if (njs_slow_path(ret != NJS_OK)) { + njs_stderror("njs_console_init() failed\n"); + return NJS_ERROR; + } + + ret = njs_process_script(engine, &njs_console, &opts->command); + engine->destroy(engine); + return ret; +} + + +int +main(int argc, char **argv) +{ + njs_opts_t opts; + njs_memzero(&opts, sizeof(njs_opts_t)); + int result=0, status=0; + + if(argc < 2) { + printf("usage: ./%s \navailable opts: \n\t'filename.js' - path of js file to be executed\n\tfuzz - entering REPRL mode(fuzzilli)\n", argv[0]); + return NJS_ERROR; + } + + if(!strcmp(argv[1], "fuzz")) { + char helo[] = "HELO"; + if (write(REPRL_CWFD, helo, 4) != 4 || read(REPRL_CRFD, helo, 4) != 4) { + printf("Invalid HELO response from parent\n"); + _exit(-1); + } + + if (memcmp(helo, "HELO", 4) != 0) { + printf("Invalid response from parent\n"); + _exit(-1); + } + + while(1) { + njs_str_t* fuzzer_input = njs_fetch_fuzz_input(); + + opts.file = (char *) "fuzzer"; + opts.command.start = fuzzer_input->start; + opts.command.length = fuzzer_input->length; + opts.suppress_stdout = 0; + result = njs_main_fuzzable(&opts); + + free(fuzzer_input->start); + free(fuzzer_input); + + status = (result & 0xff) << 8; + CHECK(write(REPRL_CWFD, &status, 4) == 4); + __sanitizer_cov_reset_edgeguards(); + } + } else { + njs_int_t ret; + njs_engine_t *engine; + + opts.file = argv[1]; + engine = njs_create_engine(&opts); + if (engine == NULL) { + return NJS_ERROR; + } + + ret = njs_console_init(&opts, &njs_console); + if (njs_slow_path(ret != NJS_OK)) { + njs_stderror("njs_console_init() failed\n"); + return NJS_ERROR; + } + result = njs_process_file(&opts); + engine->destroy(engine); + + } + + return result; +} + diff --git a/Targets/njs/mod/njs_fuzzilli_module.c b/Targets/njs/mod/njs_fuzzilli_module.c new file mode 100644 index 000000000..1398cf7ea --- /dev/null +++ b/Targets/njs/mod/njs_fuzzilli_module.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "njs_coverage.h" + +static njs_int_t njs_fuzzilli_func(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); + +static njs_int_t njs_fuzzilli_init(njs_vm_t *vm); + +static njs_external_t njs_ext_fuzzilli[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "fuzzilli", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("testing"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_fuzzilli_func, + } + }, +}; + +njs_module_t njs_fuzzilli_module = { + .name = njs_str("fuzzilli"), + .preinit = NULL, + .init = njs_fuzzilli_init, +}; + + +#define REPRL_DWFD 103 + +static njs_int_t +njs_fuzzilli_func(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + uint32_t num; + njs_int_t ret; + njs_value_t *value, lvalue; + njs_value_t *value2, lvalue2; + njs_string_prop_t string; + njs_string_prop_t string2; + + + value = njs_lvalue_arg(&lvalue, args, nargs, 1); + + ret = njs_value_to_string(vm, value, value); + if(njs_slow_path(ret != NJS_OK)) { return ret; } + + (void) njs_string_trim(value, &string, NJS_TRIM_START); + + char *str = (char *)string.start; + str[string.length] = 0x00; + + if (!strcmp(str, "FUZZILLI_CRASH")) { + // fetch arg + ret = njs_value_to_uint32(vm, njs_arg(args, nargs, 2), &num); + if(njs_slow_path(ret != NJS_OK)) { return ret; } + + // execute action + switch (num) { + case 0: + *((int*)0x41414141) = 0x1337; + break; + case 1: + assert(0); + break; + default: + assert(0); + break; + } + } else if (!strcmp(str, "FUZZILLI_PRINT") && nargs > 1) { + // fetch arg + value2 = njs_lvalue_arg(&lvalue2, args, nargs, 2); + value2->type = NJS_STRING; + ret = njs_value_to_string(vm, value2, value2); + if(njs_slow_path(ret != NJS_OK)) { return ret; } + (void) njs_string_trim(value2, &string2, NJS_TRIM_START); + + char* print_str = (char*)string2.start; + print_str[string2.length] = 0x00; + + // execute action + 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 NJS_OK; +} + +static njs_int_t +njs_fuzzilli_init(njs_vm_t *vm) +{ + njs_int_t ret, proto_id; + njs_str_t name = njs_str("fuzzer"); + njs_str_t fuzzer_func = njs_str("fuzzer.testing"); + njs_str_t builtin_name = njs_str("fuzzilli"); + njs_opaque_value_t value; + njs_opaque_value_t method; + + proto_id = njs_vm_external_prototype(vm, njs_ext_fuzzilli, + njs_nitems(njs_ext_fuzzilli)); + if (njs_slow_path(proto_id < 0)) { + return NJS_ERROR; + } + + ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_bind(vm, &name, njs_value_arg(&value), 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_value(vm, &fuzzer_func, njs_value_arg(&method)); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_bind(vm, &builtin_name, njs_value_arg(&method), 0); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + return NJS_OK; +} + + + +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; +} \ No newline at end of file diff --git a/Targets/njs/setup.sh b/Targets/njs/setup.sh new file mode 100644 index 000000000..44c028f24 --- /dev/null +++ b/Targets/njs/setup.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# get codebase +git clone https://github.com/nginx/njs.git + + +# add fuzzilli reprl shell +MODIF='' +MODIF+="\n"'# njs fuzzer(fuzzilli)' +MODIF+="\n"'' +MODIF+="\n"'cat << END >> $NJS_MAKEFILE' +MODIF+="\n"'' +MODIF+="\n"'$NJS_BUILD_DIR\/njs_fuzzilli: \\\\' +MODIF+="\n"' $NJS_BUILD_DIR\/libnjs.a \\\\' +MODIF+="\n"' external\/njs_fuzzilli.c' +MODIF+="\n"' cp external\/njs_shell.c external\/njs_fuzzilli_shell.c' +MODIF+="\n"'' +MODIF+="\n"' .\/external\/gen-fuzzilli-shell.sh' +MODIF+="\n"' ' +MODIF+="\n"' \\\$(NJS_CC) -c \\\$(NJS_LIB_INCS) \\\$(CFLAGS) \\\\' +MODIF+="\n"' \\\$(NJS_LIB_AUX_CFLAGS) \\\\' +MODIF+="\n"' -o $NJS_BUILD_DIR\/external\/njs_fuzzilli_shell.o \\\\' +MODIF+="\n"' external\/njs_fuzzilli_shell.c' +MODIF+="\n"'' +MODIF+="\n"' \\\$(NJS_LINK) -o $NJS_BUILD_DIR\/njs_fuzzilli \\\$(NJS_LIB_INCS) \\\\' +MODIF+="\n"' \\\$(NJS_CFLAGS) \\\$(NJS_LIB_AUX_CFLAGS)\\\\' +MODIF+="\n"' external\/njs_fuzzilli.c \\\\' +MODIF+="\n"' $NJS_BUILD_DIR\/libnjs.a \\\\' +MODIF+="\n"' $NJS_LD_OPT -lm $NJS_LIBS $NJS_LIB_AUX_LIBS $NJS_READLINE_LIB' +MODIF+="\n"'' +MODIF+="\n"'END' +MODIF+="\n"'' +MODIF+="\n"'# lib tests.' +MODIF+="\n"'' + +NEEDLE='# lib tests.' + +sed -i "s/$NEEDLE/$MODIF/" ./njs/auto/make + + +# add fuzzilli extension sources +cp mod/* ./njs/external/ + +cat << END >> ./njs/auto/modules + +njs_module_name=njs_fuzzilli_module +njs_module_incs= +njs_module_srcs=external/njs_fuzzilli_module.c + +. auto/module + +END + + +# add `make njs_fuzzilli target` +NEEDLE='njs: $NJS_BUILD_DIR\/njs_auto_config.h $NJS_BUILD_DIR\/njs' + +MODIF='' +MODIF+="\n"'njs_fuzzilli: $NJS_BUILD_DIR\/njs_fuzzilli' +MODIF+="\n"'njs: $NJS_BUILD_DIR\/njs_auto_config.h $NJS_BUILD_DIR\/njs' +MODIF+="\n" + +sed -i "s/$NEEDLE/$MODIF/" ./njs/auto/make + +echo "[+] Done preparing codebase"