Memory leak in JS_ReadBigInt when a bjson payload is truncated inside a BigInt
Los mantenedores suelen responder en 2 días
Ya se ha fusionado un pull request relacionado.
- #1840 de @theaddonn — fusionado
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 88/100
Línea de trabajo
Lee JS_ReadBigInt en quickjs.c y sigue cómo se libera su asignación en la ruta de fail. Reproduce el problema con la carga útil bjson truncada proporcionada bajo AddressSanitizer y verifica después que la fuga haya desaparecido para cada truncamiento; el resultado esperado es el mismo SyntaxError, sin objetos JSBigInt con fugas. El issue indica que la suite de pruebas existente pasa sin cambios.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
JS_ReadBigInt allocates the JSBigInt before it reads the limbs. When the
payload ends in the middle of the limbs, the function jumps to its fail label,
which releases obj but never the bigint — so the JSBigInt is leaked.
Found while fuzzing the bjson reader with a locally built AddressSanitizer
binary. The reproducer below is plain JavaScript, no C code required.
Steps to reproduce
import * as bjson from "qjs:bjson";
const buf = bjson.write({ v: 123456789012345678901234567890n });
// Set the checksum to the "no checksum" escape value, so the reader does not
// reject the payload before it reaches the bigint.
new DataView(buf).setUint32(1, 0xFFFFFFFF, true);
for (let cut = 1; cut <= 6; cut++) {
bjson.read(buf, 0, buf.byteLength - cut); // throws SyntaxError
}
Expected: each call throws SyntaxError: read after the end of the buffer
and nothing is leaked.
Actual: each call throws the same error and leaks a JSBigInt
(28 bytes each on x86-64).
ASan output
Built with gcc -g -O1 -fsanitize=address, running the script above:
=================================================================
==7905==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 168 byte(s) in 6 object(s) allocated from:
#1 0x5f7094d3e8b9 in js_def_malloc quickjs.c:2409
#2 0x5f7094d36000 in arena_malloc_large quickjs.c:1780
#3 0x5f7094d52516 in js_arena_malloc quickjs.c:1836
#4 0x5f7094d52516 in js_malloc_rt quickjs.c:2018
#5 0x5f7094d8a3a6 in js_malloc quickjs.c:2106
#6 0x5f7094dc3f82 in js_bigint_new quickjs.c:12666
#7 0x5f7094e92f00 in JS_ReadBigInt quickjs.c:39655
#8 0x5f7094e92f00 in JS_ReadObjectRec quickjs.c:40452
#9 0x5f7094e91048 in JS_ReadObjectTag quickjs.c:40092
#10 0x5f7094e91048 in JS_ReadObjectRec quickjs.c:40425
#11 0x5f7094e94229 in JS_ReadObject2 quickjs.c:40603
#12 0x5f7094e94ac9 in JS_ReadObject quickjs.c:40618
#13 0x5f7094ec2a77 in js_bjson_read quickjs-libc.c:5008
Six truncations leak 168 bytes in total.
Root cause
JS_ReadBigInt in quickjs.c:
p = js_bigint_new(s->ctx, (len - 1) / (JS_LIMB_BITS / 8) + 1);
if (!p)
goto fail;
for (i = 0; i < len / (JS_LIMB_BITS / 8); i++) {
if (bc_get_u32(s, &v))
goto fail; /* <-- p is leaked here */
p->tab[i] = v;
}
n = len % (JS_LIMB_BITS / 8);
if (n != 0) {
...
for (i = 0; i < n; i++) {
if (bc_get_u8(s, &v8))
goto fail; /* <-- and here */
...
}
}
...
fail:
JS_FreeValue(s->ctx, obj);
return JS_EXCEPTION;
obj is released, p is not.
Suggested fix
- JSBigInt *p;
+ JSBigInt *p = NULL;
...
fail:
+ if (p)
+ js_free_rt(s->ctx->rt, p);
JS_FreeValue(s->ctx, obj);
return JS_EXCEPTION;
js_free_rt() is the counterpart of the js_malloc() call inside
js_bigint_new() (js_malloc(ctx, …) forwards to js_malloc_rt(ctx->rt, …)).
p must be initialised to NULL because the first goto fail happens before
js_bigint_new().
With this change the reproducer above leaks nothing, and the existing local
test suite still passes unchanged (run-test262 -c tests.conf → 0/121 errors).
Notes
- The
failpath is reachable from two places: the limb loop
(bc_get_u32) and the sign-extension loop (bc_get_u8). - This may be specific to the current BigInt implementation: the allocation
pattern was introduced together with it (git log -S "js_bigint_new" -- quickjs.cpoints at the "New BigInt implementation" commit), so the older
libbf-based code may not have had this path. - This is a leak, not memory corruption, so per SECURITY.md it should be a
normal bug report. Reaching it requires deliberately malformed input; with
the checksum left enabled, the reader rejects the payload before it reaches
the BigInt.
- Lenguaje dominante
- C
- Estrellas
- 3.9k
- Forks
- 389
- Merge medio
- 2 d 17 h
- PR fusionados (30 d)
- 21
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de quickjs-ng/quickjs
-
Array.prototype.find frees an element twice when interrupted between iterationsPosiblemente ocupada @josefguenther la tomó hace 18 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 73/100
quickjs-ng/quickjs#1792 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
Object.prototype.propertyIsEnumerable converts `this` before the property keyPosiblemente ocupada @SomSamantray la tomó hace 14 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
quickjs-ng/quickjs#1772 ·
Los mantenedores suelen responder en 2 días
-
Deserialization corrupts data or returns `undefined` when a RegExp precedes a shared object referencePosiblemente ocupada @Sree24-ui la tomó hace 17 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
quickjs-ng/quickjs#1771 · 2 comentarios ·
Los mantenedores suelen responder en 2 días
-
Crash in js_map_finalizer when a WeakMap value is the symbol key of the next entryPosiblemente ocupada @wleeaf la tomó hace 1 día. Abierto
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
quickjs-ng/quickjs#1835 · 1 reacción ·
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
quickjs-ng/quickjs#1833 ·
Los mantenedores suelen responder en 2 días
Todos los issues de quickjs-ng/quickjs
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
-
area:ci priority:low type:chore
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
crazy-goat/php-fpm-ng#1021 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
CCExtractor/ccextractor#2425 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
pawelsalawa/letos#5839 ·