From a1d1bce0b7a2682a890cbe6866e1cddbbb1ae940 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 17 Oct 2024 08:45:04 +0200 Subject: [PATCH] Fix crash in deserializer (#602) Check inside the deserializer that const atoms are indeed const, don't trust the input. The serializer only writes type 0 records for const atoms but the byte stream may have been corrupted or manipulated. Overlooked during review of c25aad7 ("Add ability to (de)serialize symbols") Found with libfuzzer and it found it _really_ fast. Great tool. --- .gitignore | 3 ++- Makefile | 6 ++++- fuzz.c | 23 +++++++++++++++++++ quickjs.c | 4 ++++ tests/test_bjson.js | 55 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 fuzz.c diff --git a/.gitignore b/.gitignore index fbeb98f..4e44ee6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ build/ unicode/ test262_*.txt .idea -cmake-* \ No newline at end of file +cmake-* +fuzz diff --git a/Makefile b/Makefile index a5a5734..684fe17 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,10 @@ endif all: $(QJS) +fuzz: + clang -g -O1 -fsanitize=fuzzer -o fuzz fuzz.c + ./fuzz + $(BUILD_DIR): cmake -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) @@ -106,4 +110,4 @@ unicode_gen: $(BUILD_DIR) libunicode-table.h: unicode_gen $(BUILD_DIR)/unicode_gen unicode $@ -.PHONY: all debug install clean codegen distclean stats test test262 test262-update test262-check microbench unicode_gen $(QJS) $(QJSC) +.PHONY: all debug fuzz install clean codegen distclean stats test test262 test262-update test262-check microbench unicode_gen $(QJS) $(QJSC) diff --git a/fuzz.c b/fuzz.c new file mode 100644 index 0000000..cb5381d --- /dev/null +++ b/fuzz.c @@ -0,0 +1,23 @@ +// clang -g -O1 -fsanitize=fuzzer -o fuzz fuzz.c +#include "quickjs.h" +#include "quickjs.c" +#include "cutils.c" +#include "libbf.c" +#include "libregexp.c" +#include "libunicode.c" +#include + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + JSRuntime *rt = JS_NewRuntime(); + if (!rt) + exit(1); + JSContext *ctx = JS_NewContext(rt); + if (!ctx) + exit(1); + JSValueConst val = JS_ReadObject(ctx, buf, len, /*flags*/0); + JS_FreeValue(ctx, val); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; +} diff --git a/quickjs.c b/quickjs.c index 3642aea..90e9729 100644 --- a/quickjs.c +++ b/quickjs.c @@ -35587,6 +35587,10 @@ static int JS_ReadObjectAtoms(BCReaderState *s) if (type == 0) { if (bc_get_u32(s, &atom)) return -1; + if (!__JS_AtomIsConst(atom)) { + JS_ThrowInternalError(s->ctx, "out of range atom"); + return -1; + } } else { if (type < JS_ATOM_TYPE_STRING || type >= JS_ATOM_TYPE_PRIVATE) { JS_ThrowInternalError(s->ctx, "invalid symbol type %d", type); diff --git a/tests/test_bjson.js b/tests/test_bjson.js index 0a92162..c1f72bb 100644 --- a/tests/test_bjson.js +++ b/tests/test_bjson.js @@ -1,6 +1,45 @@ import * as bjson from "bjson"; import { assert } from "./assert.js"; +function base64decode(s) { + var A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var n = s.indexOf("="); + if (n < 0) n = s.length; + if (n & 3 === 1) throw Error("bad base64"); // too much padding + var r = new Uint8Array(3 * (n>>2) + (n>>1 & 1) + (n & 1)); + var a, b, c, d, i, j; + a = b = c = d = i = j = 0; + while (i+3 < n) { + a = A.indexOf(s[i++]); + b = A.indexOf(s[i++]); + c = A.indexOf(s[i++]); + d = A.indexOf(s[i++]); + if (~63 & (a|b|c|d)) throw Error("bad base64"); + r[j++] = a<<2 | b>>4; + r[j++] = 255 & b<<4 | c>>2; + r[j++] = 255 & c<<6 | d; + } + switch (n & 3) { + case 2: + a = A.indexOf(s[i++]); + b = A.indexOf(s[i++]); + if (~63 & (a|b)) throw Error("bad base64"); + if (b & 15) throw Error("bad base64"); + r[j++] = a<<2 | b>>4; + break; + case 3: + a = A.indexOf(s[i++]); + b = A.indexOf(s[i++]); + c = A.indexOf(s[i++]); + if (~63 & (a|b|c)) throw Error("bad base64"); + if (c & 3) throw Error("bad base64"); + r[j++] = a<<2 | b>>4; + r[j++] = 255 & b<<4 | c>>2; + break; + } + return r.buffer; +} + function toHex(a) { var i, s = "", tab, v; @@ -188,6 +227,21 @@ function bjson_test_symbol() assert(o, r); } +function bjson_test_fuzz() +{ + var corpus = [ + "EBAAAAAABGA=", + ]; + for (var input of corpus) { + var buf = base64decode(input); + try { + bjson.read(buf, 0, buf.byteLength); + } catch (e) { + // okay, ignore + } + } +} + function bjson_test_all() { var obj; @@ -221,6 +275,7 @@ function bjson_test_all() bjson_test_map(); bjson_test_set(); bjson_test_symbol(); + bjson_test_fuzz(); } bjson_test_all();