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.
This commit is contained in:
Ben Noordhuis 2024-10-17 08:45:04 +02:00 committed by GitHub
parent e4406fa55f
commit a1d1bce0b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 89 additions and 2 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ unicode/
test262_*.txt
.idea
cmake-*
fuzz

View file

@ -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)

23
fuzz.c Normal file
View file

@ -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 <stdlib.h>
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;
}

View file

@ -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);

View file

@ -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();