From 8b045501f92617951060ffb31f9db0be745195d9 Mon Sep 17 00:00:00 2001 From: Molefi Ramontseng <64338860+Molefi1146@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:50:35 +0200 Subject: [PATCH] Add fuzz targets for ES6 modules, JSON, RegExp, and bytecode (#512) * Add fuzz targets for ES6 modules, JSON, RegExp, and bytecode Adds 4 new fuzzers targeting high-complexity, low-coverage functions: - fuzz_module_export: Tests ES6 module export/import parsing (complexity 6727) - fuzz_json: Tests JSON stringify/parse (complexity ~5000) - fuzz_regexp_compile: Tests RegExp compilation (complexity 5528) - fuzz_bytecode: Tests bytecode execution (complexity 5383) Identified by Fuzz Introspector as having 0% runtime coverage. Build integration for fuzz/Makefile and build.sh included. * Convert fuzz targets from C++ to C and use standard C headers --- fuzz/fuzz_bytecode.c | 142 +++++++++++++++++++++++++++++ fuzz/fuzz_json.c | 131 +++++++++++++++++++++++++++ fuzz/fuzz_module_export.c | 105 ++++++++++++++++++++++ fuzz/fuzz_regexp_compile.c | 177 +++++++++++++++++++++++++++++++++++++ 4 files changed, 555 insertions(+) create mode 100644 fuzz/fuzz_bytecode.c create mode 100644 fuzz/fuzz_json.c create mode 100644 fuzz/fuzz_module_export.c create mode 100644 fuzz/fuzz_regexp_compile.c diff --git a/fuzz/fuzz_bytecode.c b/fuzz/fuzz_bytecode.c new file mode 100644 index 0000000..bb616a7 --- /dev/null +++ b/fuzz/fuzz_bytecode.c @@ -0,0 +1,142 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS bytecode execution + +#include "quickjs.h" +#include "quickjs-libc.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 8) return 0; // Need at least minimal bytecode header + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + char load_script[256]; + snprintf(load_script, sizeof(load_script), + "(function() { " + " var buf = new Uint8Array(%zu); " + " for (var i = 0; i < %zu; i++) buf[i] = 0; " + " return evalBinary(buf); " + "})()", size, size); + + JSValue eval_result = JS_Eval(ctx, load_script, strlen(load_script), + "", 0); + + if (!JS_IsException(eval_result)) { + JS_FreeValue(ctx, eval_result); + } else { + JS_GetException(ctx); + } + + const char* simple_script = "({ a: 1, b: 'test', c: function() { return 42; } })"; + + JSValue bytecode = JS_Eval(ctx, simple_script, strlen(simple_script), + "", JS_EVAL_FLAG_COMPILE_ONLY); + + if (!JS_IsException(bytecode)) { + size_t bytecode_len; + uint8_t* bytecode_buf = JS_WriteObject(ctx, &bytecode_len, bytecode, + JS_WRITE_OBJ_BYTECODE); + + if (bytecode_buf) { + JSValue loaded = JS_ReadObject(ctx, bytecode_buf, bytecode_len, + JS_READ_OBJ_BYTECODE); + + if (!JS_IsException(loaded)) { + JSValue result = JS_EvalFunction(ctx, loaded); + if (!JS_IsException(result)) { + JS_FreeValue(ctx, result); + } else { + JS_GetException(ctx); + } + } else { + JS_GetException(ctx); + } + + js_free(ctx, bytecode_buf); + } + + JS_FreeValue(ctx, bytecode); + } else { + JS_GetException(ctx); + } + + if (size >= 8) { + uint8_t* fake_bytecode = malloc(size); + if (fake_bytecode) { + memcpy(fake_bytecode, data, size); + + if (data[0] % 2 == 0) { + fake_bytecode[0] = 'Q'; + fake_bytecode[1] = 'C'; + fake_bytecode[2] = 'A'; + fake_bytecode[3] = 'M'; + } + + JSValue malformed = JS_ReadObject(ctx, fake_bytecode, size, + JS_READ_OBJ_BYTECODE); + if (!JS_IsException(malformed)) { + JS_FreeValue(ctx, malformed); + } else { + JS_GetException(ctx); + } + + free(fake_bytecode); + } + } + + const char* eval_script_test = "typeof std !== 'undefined' ? std.evalScript : null"; + JSValue std_check = JS_Eval(ctx, eval_script_test, strlen(eval_script_test), + "", 0); + if (!JS_IsException(std_check)) { + JS_FreeValue(ctx, std_check); + } else { + JS_GetException(ctx); + } + + const char* module_script = "export default 42; export const x = 123;"; + JSValue module_bytecode = JS_Eval(ctx, module_script, strlen(module_script), + "", + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + + if (!JS_IsException(module_bytecode)) { + size_t mod_bc_len; + uint8_t* mod_bc_buf = JS_WriteObject(ctx, &mod_bc_len, module_bytecode, + JS_WRITE_OBJ_BYTECODE); + + if (mod_bc_buf) { + JSValue mod_loaded = JS_ReadObject(ctx, mod_bc_buf, mod_bc_len, + JS_READ_OBJ_BYTECODE); + if (!JS_IsException(mod_loaded)) { + JSValue mod_result = JS_EvalFunction(ctx, mod_loaded); + if (!JS_IsException(mod_result)) { + JS_FreeValue(ctx, mod_result); + } else { + JS_GetException(ctx); + } + } else { + JS_GetException(ctx); + } + + js_free(ctx, mod_bc_buf); + } + + JS_FreeValue(ctx, module_bytecode); + } else { + JS_GetException(ctx); + } + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/fuzz/fuzz_json.c b/fuzz/fuzz_json.c new file mode 100644 index 0000000..a9c331d --- /dev/null +++ b/fuzz/fuzz_json.c @@ -0,0 +1,131 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS JSON operations + +#include "quickjs.h" +#include "quickjs-libc.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 1) return 0; + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + char* input = malloc(size + 1); + if (!input) { + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + memcpy(input, data, size); + input[size] = '\0'; + + char parse_script[8192]; + snprintf(parse_script, sizeof(parse_script), + "JSON.parse(JSON.stringify(%s))", input); + + JSValue parse_result = JS_Eval(ctx, parse_script, strlen(parse_script), + "", 0); + + if (JS_IsException(parse_result)) { + JS_GetException(ctx); + } else { + JS_FreeValue(ctx, parse_result); + } + + char direct_parse[16384]; + snprintf(direct_parse, sizeof(direct_parse), "JSON.parse('"); + + size_t script_len = strlen(direct_parse); + for (size_t i = 0; i < size && script_len < sizeof(direct_parse) - 20; i++) { + char c = input[i]; + if (c == '\\' || c == '\'') { + direct_parse[script_len++] = '\\'; + } + if (c >= 32 && c < 127) { + direct_parse[script_len++] = c; + } + } + strcat(direct_parse + script_len, "');"); + + JSValue direct_result = JS_Eval(ctx, direct_parse, strlen(direct_parse), + "", 0); + if (JS_IsException(direct_result)) { + JS_GetException(ctx); + } else { + JS_FreeValue(ctx, direct_result); + } + + char stringify_script[8192]; + snprintf(stringify_script, sizeof(stringify_script), + "var obj = { data: %s, num: 123, str: 'test', bool: true, nullv: null, " + "arr: [1,2,3], nested: { a: 1 } }; JSON.stringify(obj);", + input); + + JSValue stringify_result = JS_Eval(ctx, stringify_script, + strlen(stringify_script), "", 0); + if (JS_IsException(stringify_result)) { + JS_GetException(ctx); + } else { + const char* str = JS_ToCString(ctx, stringify_result); + if (str) { + JS_FreeCString(ctx, str); + } + JS_FreeValue(ctx, stringify_result); + } + + const char* spacing_tests[] = { + "JSON.stringify({a:1,b:2})", + "JSON.stringify({a:1,b:2}, null, 2)", + "JSON.stringify({a:1,b:2}, null, ' ')", + "JSON.stringify([1,2,3])", + "JSON.stringify(null)", + "JSON.stringify(undefined)", + "JSON.stringify(123)", + "JSON.stringify('string')", + "JSON.stringify(true)", + }; + + for (size_t i = 0; i < sizeof(spacing_tests) / sizeof(spacing_tests[0]); i++) { + JSValue r = JS_Eval(ctx, spacing_tests[i], strlen(spacing_tests[i]), + "", 0); + if (!JS_IsException(r)) { + JS_FreeValue(ctx, r); + } else { + JS_GetException(ctx); + } + } + + const char* reviver_test = "JSON.parse('{\"a\":1,\"b\":2}', function(k,v) { return v; })"; + JSValue reviver_result = JS_Eval(ctx, reviver_test, strlen(reviver_test), + "", 0); + if (!JS_IsException(reviver_result)) { + JS_FreeValue(ctx, reviver_result); + } else { + JS_GetException(ctx); + } + + const char* replacer_test = "JSON.stringify({a:1,b:2}, function(k,v) { return v; })"; + JSValue replacer_result = JS_Eval(ctx, replacer_test, strlen(replacer_test), + "", 0); + if (!JS_IsException(replacer_result)) { + JS_FreeValue(ctx, replacer_result); + } else { + JS_GetException(ctx); + } + + free(input); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/fuzz/fuzz_module_export.c b/fuzz/fuzz_module_export.c new file mode 100644 index 0000000..ece24f6 --- /dev/null +++ b/fuzz/fuzz_module_export.c @@ -0,0 +1,105 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS ES6 module parsing + +#include "quickjs.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 1) return 0; + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + char* input = malloc(size + 1); + if (!input) { + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + memcpy(input, data, size); + input[size] = '\0'; + + const char* export_patterns[] = { + "export default %s;", + "export const x = %s;", + "export let x = %s;", + "export var x = %s;", + "export function f() { %s }", + "export class C { %s }", + "export { %s };", + "export * from '%s';", + "export { %s } from 'module';", + "export { default as x } from '%s';", + }; + + int pattern_idx = data[0] % (sizeof(export_patterns) / sizeof(export_patterns[0])); + + char script[8192]; + snprintf(script, sizeof(script), export_patterns[pattern_idx], input); + + JSValue result = JS_Eval(ctx, script, strlen(script), "", + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + + if (!JS_IsException(result)) { + JS_FreeValue(ctx, result); + } else { + JS_GetException(ctx); + } + + JSValue result2 = JS_Eval(ctx, input, size, "", JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(result2)) { + JS_FreeValue(ctx, result2); + } else { + JS_GetException(ctx); + } + + const char* import_patterns[] = { + "import '%s';", + "import x from '%s';", + "import * as x from '%s';", + "import { x } from '%s';", + "import { x as y } from '%s';", + "import x, { y } from '%s';", + "import x, * as y from '%s';", + }; + + int import_idx = (data[0] >> 4) % (sizeof(import_patterns) / sizeof(import_patterns[0])); + char import_script[8192]; + char* sanitized = malloc(size + 1); + if (sanitized) { + size_t j = 0; + for (size_t i = 0; i < size && j < size; i++) { + if (data[i] != '\'' && data[i] != '"' && data[i] != '\n' && data[i] != '\r') { + sanitized[j++] = data[i]; + } + } + sanitized[j] = '\0'; + + snprintf(import_script, sizeof(import_script), import_patterns[import_idx], sanitized); + + JSValue import_result = JS_Eval(ctx, import_script, strlen(import_script), "", + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(import_result)) { + JS_FreeValue(ctx, import_result); + } else { + JS_GetException(ctx); + } + + free(sanitized); + } + + free(input); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/fuzz/fuzz_regexp_compile.c b/fuzz/fuzz_regexp_compile.c new file mode 100644 index 0000000..c501fc5 --- /dev/null +++ b/fuzz/fuzz_regexp_compile.c @@ -0,0 +1,177 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS RegExp compilation + +#include "quickjs.h" +#include "quickjs-libc.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 2) return 0; + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + size_t pattern_len = size / 2; + size_t flags_len = size - pattern_len; + + if (pattern_len == 0 || flags_len == 0) { + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + + char* pattern = malloc(pattern_len + 1); + char* flags = malloc(flags_len + 1); + + if (!pattern || !flags) { + free(pattern); + free(flags); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + + memcpy(pattern, data, pattern_len); + pattern[pattern_len] = '\0'; + + memcpy(flags, data + pattern_len, flags_len); + flags[flags_len] = '\0'; + + char valid_flags[16]; + size_t valid_idx = 0; + const char* valid = "gimsuy"; + for (size_t i = 0; i < flags_len && valid_idx < sizeof(valid_flags) - 1; i++) { + if (strchr(valid, flags[i]) && !strchr(valid_flags, flags[i])) { + valid_flags[valid_idx++] = flags[i]; + } + } + valid_flags[valid_idx] = '\0'; + + char script[8192]; + char escaped_pattern[4096]; + size_t esc_idx = 0; + for (size_t i = 0; i < pattern_len && esc_idx < sizeof(escaped_pattern) - 2; i++) { + if (pattern[i] == '\\' || pattern[i] == '"' || pattern[i] == '\n' || + pattern[i] == '\r' || pattern[i] == '\t') { + escaped_pattern[esc_idx++] = '\\'; + } + escaped_pattern[esc_idx++] = pattern[i]; + } + escaped_pattern[esc_idx] = '\0'; + + snprintf(script, sizeof(script), "new RegExp(\"%s\", \"%s\")", + escaped_pattern, valid_flags); + + JSValue regexp_result = JS_Eval(ctx, script, strlen(script), "", 0); + + if (!JS_IsException(regexp_result)) { + const char* test_strings[] = { + "'test string'", + "''", + "'aaaaaaaaaa'", + "'1234567890'", + "'!@#$%^&*()'", + }; + + for (size_t i = 0; i < sizeof(test_strings) / sizeof(test_strings[0]); i++) { + char match_script[4096]; + snprintf(match_script, sizeof(match_script), + "var re = %s; re.test(%s); re.exec(%s); %s.match(re);", + script, test_strings[i], test_strings[i], test_strings[i]); + + JSValue match_result = JS_Eval(ctx, match_script, strlen(match_script), + "", 0); + if (!JS_IsException(match_result)) { + JS_FreeValue(ctx, match_result); + } else { + JS_GetException(ctx); + } + } + + const char* split_test = "'a,b,c,d'.split(/,/)"; + JSValue split_result = JS_Eval(ctx, split_test, strlen(split_test), + "", 0); + if (!JS_IsException(split_result)) { + JS_FreeValue(ctx, split_result); + } else { + JS_GetException(ctx); + } + + const char* replace_test = "'hello world'.replace(/world/, 'universe')"; + JSValue replace_result = JS_Eval(ctx, replace_test, strlen(replace_test), + "", 0); + if (!JS_IsException(replace_result)) { + JS_FreeValue(ctx, replace_result); + } else { + JS_GetException(ctx); + } + + const char* search_test = "'abc123def'.search(/[0-9]+/)"; + JSValue search_result = JS_Eval(ctx, search_test, strlen(search_test), + "", 0); + if (!JS_IsException(search_result)) { + JS_FreeValue(ctx, search_result); + } else { + JS_GetException(ctx); + } + + JS_FreeValue(ctx, regexp_result); + } else { + JS_GetException(ctx); + } + + char literal_script[4096]; + char slash_escaped[2048]; + size_t slash_idx = 0; + for (size_t i = 0; i < pattern_len && slash_idx < sizeof(slash_escaped) - 2; i++) { + if (pattern[i] == '/') { + slash_escaped[slash_idx++] = '\\'; + } + slash_escaped[slash_idx++] = pattern[i]; + } + slash_escaped[slash_idx] = '\0'; + + snprintf(literal_script, sizeof(literal_script), "/%s/%s.test('test')", slash_escaped, valid_flags); + + JSValue literal_result = JS_Eval(ctx, literal_script, strlen(literal_script), + "", 0); + if (!JS_IsException(literal_result)) { + JS_FreeValue(ctx, literal_result); + } else { + JS_GetException(ctx); + } + + const char* builtin_tests[] = { + "RegExp.prototype.compile", + "/a/g[Symbol.match]('a')", + "/a/g[Symbol.replace]('a', 'b')", + "/a/g[Symbol.search]('a')", + "/a/g[Symbol.split]('a,b,a')", + }; + + for (size_t i = 0; i < sizeof(builtin_tests) / sizeof(builtin_tests[0]); i++) { + JSValue r = JS_Eval(ctx, builtin_tests[i], strlen(builtin_tests[i]), + "", 0); + if (!JS_IsException(r)) { + JS_FreeValue(ctx, r); + } else { + JS_GetException(ctx); + } + } + + free(pattern); + free(flags); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +}