Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Memory leak in JS_ReadBigInt when a bjson payload is truncated inside a BigInt

Cerrado Apto para principiantes
#1,836 0 comentarios 0 reacciones 0 asignados Ver en GitHub

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
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c
Área
backend, security

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 fail path 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.c points 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

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de quickjs-ng/quickjs

Todos los issues de quickjs-ng/quickjs

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.