Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta Adatta ai principianti
#1,836 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
88/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
c
Ambito
backend, security

Direzione di ricerca

Leggi JS_ReadBigInt in quickjs.c e segui come viene liberata la sua allocazione nel percorso fail. Riproduci il problema con il payload bjson troncato fornito sotto AddressSanitizer, quindi verifica che la perdita sia risolta per ogni troncamento; il risultato atteso è lo stesso SyntaxError, senza oggetti JSBigInt persi. L’issue riporta che la suite di test esistente passa senza modifiche.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.
Lingua principale
C
Stelle
3.9k
Fork
389
Merge medio
4g 1h
PR unite (30g)
24

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di quickjs-ng/quickjs

Tutte le issue di quickjs-ng/quickjs

Issue simili

Altre issue su C

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.