Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ fuzz_compile: $(OBJDIR)/fuzz_compile.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a
fuzz_regexp: $(OBJDIR)/fuzz_regexp.o $(OBJDIR)/libregexp.fuzz.o $(OBJDIR)/cutils.fuzz.o $(OBJDIR)/libunicode.fuzz.o
$(CC) $(CFLAGS_OPT) $^ -o fuzz_regexp $(LIB_FUZZING_ENGINE)

libfuzzer: fuzz_eval fuzz_compile fuzz_regexp
fuzz_bytecode: $(OBJDIR)/fuzz_bytecode.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a
$(CC) $(CFLAGS_OPT) $^ -o fuzz_bytecode $(LIB_FUZZING_ENGINE)

libfuzzer: fuzz_eval fuzz_compile fuzz_regexp fuzz_bytecode

ifneq ($(CROSS_PREFIX),)

Expand Down
161 changes: 38 additions & 123 deletions fuzz/fuzz_bytecode.c
Original file line number Diff line number Diff line change
@@ -1,142 +1,57 @@
// Copyright 2025 Google LLC
// Fuzz target for QuickJS bytecode execution
// Fuzz target for QuickJS bytecode loading. The raw fuzzer input is treated
// as a JS_READ_OBJ_BYTECODE object, i.e. exactly the bytes qjsc emits and an
// embedding host may load from disk. The input is first deserialised with
// JS_ReadObject (the bytecode reader) and then executed like
// js_std_eval_binary does (module resolve, import meta, JS_EvalFunction,
// js_std_loop), so both the reader and the interpreter run on
// attacker-controlled bytecode. Memory/stack/interrupt limits come from
// fuzz_common.c, matching the other quickjs targets.

#include "quickjs.h"
#include "quickjs-libc.h"
#include "cutils.h"
#include "fuzz/fuzz_common.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

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) {
if (size == 0)
return 0;

JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
test_one_input_init(rt, ctx);

JSValue obj = JS_ReadObject(ctx, data, size, JS_READ_OBJ_BYTECODE);
if (JS_IsException(obj)) {
js_std_dump_error(ctx);
js_std_free_handlers(rt);
JS_FreeContext(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),
"<bytecode-load>", 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),
"<compile>", 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);

reset_nbinterrupts();
if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
if (JS_ResolveModule(ctx, obj) < 0) {
js_std_free_handlers(rt);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
js_module_set_import_meta(ctx, obj, FALSE, TRUE);
}

const char* eval_script_test = "typeof std !== 'undefined' ? std.evalScript : null";
JSValue std_check = JS_Eval(ctx, eval_script_test, strlen(eval_script_test),
"<std-check>", 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),
"<module-compile>",
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);
JSValue val = JS_EvalFunction(ctx, obj);
if (!JS_IsException(val)) {
js_std_loop(ctx);
JS_FreeValue(ctx, val);
} else {
JS_GetException(ctx);
js_std_dump_error(ctx);
}

js_std_free_handlers(rt);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);

return 0;
}
21 changes: 21 additions & 0 deletions fuzz/fuzz_bytecode.dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"\x05"
"\x05\x01"
"\x01\x00\x00"
"QC"
"other.js"
"main"
"default"
"constructor"
"length"
"prototype"
"async"
"static"
"get"
"set"
"eval"
"import"
"export"
"await"
"label"
"finally"
"instanceof"
Binary file added fuzz/fuzz_bytecode_seeds/seed-00.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-01.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-02.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-03.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-04.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-05.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-06.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-07.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-08.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-09.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-10.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-11.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-12.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-13.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-14.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-15.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-16.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-17.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-18.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-19.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-20.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-21.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-22.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-23.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-24.bin
Binary file not shown.
Binary file added fuzz/fuzz_bytecode_seeds/seed-25.bin
Binary file not shown.