OSS-Fuzz targets improvements (#267)

* Move fuzz target sources from the oss-fuzz repository here

* Add support to build libFuzzer targets
* Simplify the fuzz_eval and fuzz_compile targets

The use of JS_NewContext instead of JS_NewContextRaw spares to call
JS_AddIntrinsic<XYZ> functions from the fuzz target, since the public
JS_NewContext API does exactly the same.

* Simplify the fuzz_regexp target

fuzz_regexp doesn't need to be dependant on libquickjs since the
runtime and the context - that were provided by libquickjs - were
only created to call two simple functions implemented in libquickjs
which could be mimicked by the fuzzer.
The removal of runtime and context objects implicated further
simplifications, like the omission of their one-time creation.
Finally, writing the result of the regexp operations into a file
is also superfluous, since it's not used by anybody.

* Recreate and destroy JS runtime and context in fuzz_eval and fuzz_compile targets

Before this patch, the test executions were not independent,
since all the executed tests used the same JavaScript runtime and
context, causing irreproducible failure reports.

* Enable bignumber support in eval and compile targets

Big numbers are used by the input corpus, but the targets were not
able to interpret them since they were not compiled into them.
This change improved the inital coverage of the fuzz_eval target with
21% and the coverage of the fuzz_compile target with 25% when using
the official corpus.

* Ensure std and os modules are available in the fuzz_eval and fuzz_compile targets
* Add fuzzer dictionary with builtin and variable names. Furthermore, added a JS script that collects all the builtin
names from the executing engine. 
* Move common fuzzer code into one place
* Enable to define the LIB_FUZZING_ENGINE variable to ease the oss-fuzz integration
* Add README to fuzzers
This commit is contained in:
Renáta Hodován 2024-05-08 18:19:48 +02:00 committed by GitHub
parent 0c8fecab23
commit 01454caf78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 553 additions and 1 deletions

View file

@ -111,6 +111,7 @@ ifdef CONFIG_CLANG
AR=$(CROSS_PREFIX)ar
endif
endif
LIB_FUZZING_ENGINE ?= "-fsanitize=fuzzer"
else ifdef CONFIG_COSMO
CONFIG_LTO=
HOST_CC=gcc
@ -248,6 +249,17 @@ qjs-debug$(EXE): $(patsubst %.o, %.debug.o, $(QJS_OBJS))
qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
fuzz_eval: $(OBJDIR)/fuzz_eval.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a
$(CC) $(CFLAGS_OPT) $^ -o fuzz_eval $(LIB_FUZZING_ENGINE)
fuzz_compile: $(OBJDIR)/fuzz_compile.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a
$(CC) $(CFLAGS_OPT) $^ -o fuzz_compile $(LIB_FUZZING_ENGINE)
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
ifneq ($(CROSS_PREFIX),)
$(QJSC): $(OBJDIR)/qjsc.host.o \
@ -289,6 +301,9 @@ libquickjs.a: $(patsubst %.o, %.nolto.o, $(QJS_LIB_OBJS))
$(AR) rcs $@ $^
endif # CONFIG_LTO
libquickjs.fuzz.a: $(patsubst %.o, %.fuzz.o, $(QJS_LIB_OBJS))
$(AR) rcs $@ $^
repl.c: $(QJSC) repl.js
$(QJSC) -c -o $@ -m repl.js
@ -317,6 +332,9 @@ run-test262-32: $(patsubst %.o, %.m32.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS)
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_OPT) -c -o $@ $<
$(OBJDIR)/fuzz_%.o: fuzz/fuzz_%.c | $(OBJDIR)
$(CC) $(CFLAGS_OPT) -c -I. -o $@ $<
$(OBJDIR)/%.host.o: %.c | $(OBJDIR)
$(HOST_CC) $(CFLAGS_OPT) -c -o $@ $<
@ -335,6 +353,9 @@ $(OBJDIR)/%.m32s.o: %.c | $(OBJDIR)
$(OBJDIR)/%.debug.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_DEBUG) -c -o $@ $<
$(OBJDIR)/%.fuzz.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_OPT) -fsanitize=fuzzer-no-link -c -o $@ $<
$(OBJDIR)/%.check.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -DCONFIG_CHECK_JSVALUE -c -o $@ $<
@ -346,7 +367,7 @@ unicode_gen: $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o libunicode.c u
clean:
rm -f repl.c qjscalc.c out.c
rm -f *.a *.o *.d *~ unicode_gen regexp_test $(PROGS)
rm -f *.a *.o *.d *~ unicode_gen regexp_test fuzz_eval fuzz_compile fuzz_regexp $(PROGS)
rm -f hello.c test_fib.c
rm -f examples/*.so tests/*.so
rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug

27
fuzz/README Normal file
View file

@ -0,0 +1,27 @@
libFuzzer support for QuickJS
=============================
Build QuickJS with libFuzzer support as follows:
CONFIG_CLANG=y make libfuzzer
This can be extended with sanitizer support to improve efficacy:
CONFIG_CLANG=y CONFIG_ASAN=y make libfuzzer
Currently, there are three fuzzing targets defined: fuzz_eval, fuzz_compile and fuzz_regexp.
The above build command will produce an executable binary for each of them, which can be
simply executed as:
./fuzz_eval
or with an initial corpus:
./fuzz_compile corpus_dir/
or with a predefined dictionary to improve its efficacy:
./fuzz_eval -dict fuzz/fuzz.dict
or with arbitrary CLI arguments provided by libFuzzer (https://llvm.org/docs/LibFuzzer.html).

257
fuzz/fuzz.dict Normal file
View file

@ -0,0 +1,257 @@
"__loadScript"
"abs"
"acos"
"acosh"
"add"
"AggregateError"
"and"
"apply"
"Array"
"ArrayBuffer"
"asin"
"asinh"
"atan"
"atan2"
"atanh"
"Atomics"
"BigDecimal"
"BigFloat"
"BigFloatEnv"
"BigInt"
"BigInt64Array"
"BigUint64Array"
"Boolean"
"cbrt"
"ceil"
"chdir"
"clearTimeout"
"close"
"clz32"
"compareExchange"
"console"
"construct"
"cos"
"cosh"
"DataView"
"Date"
"decodeURI"
"decodeURIComponent"
"defineProperty"
"deleteProperty"
"dup"
"dup2"
"E"
"encodeURI"
"encodeURIComponent"
"err"
"Error"
"escape"
"eval"
"EvalError"
"evalScript"
"exchange"
"exec"
"exit"
"exp"
"expm1"
"fdopen"
"Float32Array"
"Float64Array"
"floor"
"fround"
"Function"
"gc"
"get"
"getcwd"
"getenv"
"getenviron"
"getOwnPropertyDescriptor"
"getpid"
"getPrototypeOf"
"globalThis"
"has"
"hypot"
"imul"
"in"
"Infinity"
"Int16Array"
"Int32Array"
"Int8Array"
"InternalError"
"isatty"
"isExtensible"
"isFinite"
"isLockFree"
"isNaN"
"iterateBuiltIns"
"JSON"
"kill"
"length"
"LN10"
"LN2"
"load"
"loadFile"
"loadScript"
"log"
"log10"
"LOG10E"
"log1p"
"log2"
"LOG2E"
"lstat"
"Map"
"Math"
"max"
"min"
"mkdir"
"NaN"
"notify"
"now"
"Number"
"O_APPEND"
"O_CREAT"
"O_EXCL"
"O_RDONLY"
"O_RDWR"
"O_TRUNC"
"O_WRONLY"
"Object"
"open"
"Operators"
"or"
"os"
"out"
"ownKeys"
"parse"
"parseExtJSON"
"parseFloat"
"parseInt"
"PI"
"pipe"
"platform"
"popen"
"pow"
"preventExtensions"
"print"
"printf"
"Promise"
"Proxy"
"puts"
"random"
"RangeError"
"read"
"readdir"
"readlink"
"realpath"
"ReferenceError"
"Reflect"
"RegExp"
"remove"
"rename"
"round"
"S_IFBLK"
"S_IFCHR"
"S_IFDIR"
"S_IFIFO"
"S_IFLNK"
"S_IFMT"
"S_IFREG"
"S_IFSOCK"
"S_ISGID"
"S_ISUID"
"scriptArgs"
"seek"
"SEEK_CUR"
"SEEK_END"
"SEEK_SET"
"set"
"Set"
"setenv"
"setPrototypeOf"
"setReadHandler"
"setTimeout"
"setWriteHandler"
"SharedArrayBuffer"
"SIGABRT"
"SIGALRM"
"SIGCHLD"
"SIGCONT"
"SIGFPE"
"SIGILL"
"SIGINT"
"sign"
"signal"
"SIGPIPE"
"SIGQUIT"
"SIGSEGV"
"SIGSTOP"
"SIGTERM"
"SIGTSTP"
"SIGTTIN"
"SIGTTOU"
"SIGUSR1"
"SIGUSR2"
"sin"
"sinh"
"sleep"
"sleepAsync"
"sprintf"
"sqrt"
"SQRT1_2"
"SQRT2"
"stat"
"std"
"store"
"strerror"
"String"
"stringify"
"sub"
"Symbol"
"symlink"
"SyntaxError"
"tan"
"tanh"
"tmpfile"
"trunc"
"ttyGetWinSize"
"ttySetRaw"
"TypeError"
"Uint16Array"
"Uint32Array"
"Uint8Array"
"Uint8ClampedArray"
"undefined"
"unescape"
"unsetenv"
"URIError"
"urlGet"
"utimes"
"wait"
"waitpid"
"WeakMap"
"WeakSet"
"WNOHANG"
"Worker"
"write"
"xor"
"v0"
"v1"
"v2"
"v3"
"v4"
"v5"
"v6"
"v7"
"v8"
"v9"
"v10"
"v11"
"v12"
"v13"
"v14"
"v15"
"v16"
"v17"
"v18"
"v19"
"v20"

22
fuzz/fuzz_common.h Normal file
View file

@ -0,0 +1,22 @@
/* Copyright 2020 Google Inc.
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
http://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.
*/
#include "quickjs.h"
#include "quickjs-libc.h"
static int nbinterrupts = 0;
void reset_nbinterrupts();
void test_one_input_init(JSRuntime *rt, JSContext *ctx);

93
fuzz/fuzz_compile.c Normal file
View file

@ -0,0 +1,93 @@
/* Copyright 2020 Google Inc.
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
http://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.
*/
#include "quickjs.h"
#include "quickjs-libc.h"
#include "cutils.h"
#include "fuzz/fuzz_common.h"
#include <stdint.h>
#include <stdio.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0)
return 0;
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
test_one_input_init(rt, ctx);
uint8_t *null_terminated_data = malloc(size + 1);
memcpy(null_terminated_data, data, size);
null_terminated_data[size] = 0;
JSValue obj = JS_Eval(ctx, (const char *)null_terminated_data, size, "<none>", JS_EVAL_FLAG_COMPILE_ONLY | JS_EVAL_TYPE_MODULE);
free(null_terminated_data);
//TODO target with JS_ParseJSON
if (JS_IsException(obj)) {
js_std_free_handlers(rt);
JS_FreeValue(ctx, obj);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
obj = js_std_await(ctx, obj);
size_t bytecode_size;
uint8_t* bytecode = JS_WriteObject(ctx, &bytecode_size, obj, JS_WRITE_OBJ_BYTECODE);
JS_FreeValue(ctx, obj);
if (!bytecode) {
js_std_free_handlers(rt);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
obj = JS_ReadObject(ctx, bytecode, bytecode_size, JS_READ_OBJ_BYTECODE);
js_free(ctx, bytecode);
if (JS_IsException(obj)) {
js_std_free_handlers(rt);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
reset_nbinterrupts();
/* this is based on
* js_std_eval_binary(ctx, bytecode, bytecode_size, 0);
* modified so as not to exit on JS exception
*/
JSValue val;
if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
if (JS_ResolveModule(ctx, obj) < 0) {
JS_FreeValue(ctx, obj);
js_std_free_handlers(rt);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
js_module_set_import_meta(ctx, obj, FALSE, TRUE);
}
val = JS_EvalFunction(ctx, obj);
if (JS_IsException(val)) {
js_std_dump_error(ctx);
} else {
js_std_loop(ctx);
}
JS_FreeValue(ctx, val);
js_std_free_handlers(rt);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}

49
fuzz/fuzz_eval.c Normal file
View file

@ -0,0 +1,49 @@
/* Copyright 2020 Google Inc.
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
http://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.
*/
#include "quickjs.h"
#include "quickjs-libc.h"
#include "fuzz/fuzz_common.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0)
return 0;
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
test_one_input_init(rt, ctx);
uint8_t *null_terminated_data = malloc(size + 1);
memcpy(null_terminated_data, data, size);
null_terminated_data[size] = 0;
reset_nbinterrupts();
//the final 0 does not count (as in strlen)
JSValue val = JS_Eval(ctx, (const char *)null_terminated_data, size, "<none>", JS_EVAL_TYPE_GLOBAL);
free(null_terminated_data);
//TODO targets with JS_ParseJSON, JS_ReadObject
if (!JS_IsException(val)) {
js_std_loop(ctx);
JS_FreeValue(ctx, val);
}
js_std_free_handlers(rt);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}

59
fuzz/fuzz_regexp.c Normal file
View file

@ -0,0 +1,59 @@
/* Copyright 2020 Google Inc.
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
http://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.
*/
#include "libregexp.h"
#include "quickjs-libc.h"
int lre_check_stack_overflow(void *opaque, size_t alloca_size) { return 0; }
void *lre_realloc(void *opaque, void *ptr, size_t size)
{
return realloc(ptr, size);
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
int len, ret, i;
uint8_t *bc;
char error_msg[64];
const uint8_t *input;
uint8_t *capture[255 * 2];
size_t size1 = size;
//Splits buffer into 2 sub buffers delimited by null character
for (i = 0; i < size; i++) {
if (data[i] == 0) {
size1 = i;
break;
}
}
if (size1 == size) {
//missing delimiter
return 0;
}
bc = lre_compile(&len, error_msg, sizeof(error_msg), (const char *) data,
size1, 0, NULL);
if (!bc) {
return 0;
}
input = data + size1 + 1;
ret = lre_exec(capture, bc, input, 0, size - (size1 + 1), 0, NULL);
if (ret == 1) {
lre_get_capture_count(bc);
}
free(bc);
return 0;
}

24
fuzz/generate_dict.js Normal file
View file

@ -0,0 +1,24 @@
// Function to recursively iterate through built-in names.
function collectBuiltinNames(obj, visited = new Set(), result = new Set()) {
// Check if the object has already been visited to avoid infinite recursion.
if (visited.has(obj))
return;
// Add the current object to the set of visited objects
visited.add(obj);
// Get the property names of the current object
const properties = Object.getOwnPropertyNames(obj);
// Iterate through each property
for (var i=0; i < properties.length; i++) {
var property = properties[i];
if (property != "collectBuiltinNames" && typeof property != "number")
result.add(property);
// Check if the property is an object and if so, recursively iterate through its properties.
if (typeof obj[property] === 'object' && obj[property] !== null)
collectBuiltinNames(obj[property], visited, result);
}
return result;
}
// Start the recursive iteration with the global object.
console.log(Array.from(collectBuiltinNames(this)).join('\n'));