Heap-buffer-overflow in add_fast_array_element() via String.prototype.split("")

Aperta Adatta ai principianti
#601 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
66/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
c
Ambito
security

Direzione di ricerca

Inizia in quickjs.c, in expand_fast_array() e add_fast_array_element(), quindi riproduci il caso fornito con String.prototype.split("") usando una build con AddressSanitizer. Controlla il limite di capacità rispetto alla scrittura in quickjs.c:9766 e verifica che la correzione elimini la scrittura fuori dai limiti segnalata; l’issue non indica alcun test di regressione.

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

Descrizione

Heap-buffer-overflow in add_fast_array_element() via String.prototype.split("")

Affected commit: 712e8c0ca26949477c38f91dbaf955cc48e2f261 (2026-11-02)
Severity: High — 8-byte out-of-bounds write past heap allocation
Reproducibility: 100%
Found by: coverage-guided fuzzing with AddressSanitizer


ASan Stack Trace
==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x616000001188
WRITE of size 8 at 0x616000001188 thread T0

    #0  add_fast_array_element      quickjs.c:9766
    #1  JS_CreateProperty           quickjs.c:10361
    #2  JS_DefineProperty           quickjs.c:10870
    #3  JS_DefinePropertyValue      quickjs.c:10913
    #4  JS_DefinePropertyValueValue quickjs.c:10930
    #5  js_string_split             quickjs.c:48797
    #6  js_call_c_function          quickjs.c:18073
    #7  JS_CallInternal             quickjs.c:18280
    #8  JS_CallInternal             quickjs.c:18684
    #9  JS_CallFree                 quickjs.c:21306
    #10 JS_EvalFunctionInternal     quickjs.c:38319
    #11 __JS_EvalInternal           quickjs.c:38452
    #12 JS_Eval                     quickjs.c:38519

0x616000001188 is 0 bytes past the end of a 520-byte heap region allocated by js_realloc2 (from expand_fast_array).

Root Cause

expand_fast_array() (quickjs.c:9723) — computes new capacity:

static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len)
{
    uint32_t new_size;
    size_t slack;
    JSValue *new_array_prop;
    /* XXX: potential arithmetic overflow */
    new_size = max_int(new_len, p->u.array.u1.size * 3 / 2);
    new_array_prop = js_realloc2(ctx, p->u.array.u.values,
                                 sizeof(JSValue) * new_size, &slack);
    ...
    p->u.array.u1.size = new_size;
}

add_fast_array_element() (quickjs.c:9749) — capacity check and write:

static inline int add_fast_array_element(JSContext *ctx, JSObject *p, ...)
{
    uint32_t new_len = p->u.array.count + 1;
    ...
    if (unlikely(new_len > p->u.array.u1.size)) {     // ← boundary: `>` should be `>=`
        expand_fast_array(ctx, p, new_len);
    }
    p->u.array.u.values[new_len - 1] = val;           // ← OOB write at line 9766
}

The off-by-one: when p->u.array.u1.size already equals new_len (i.e., the buffer was allocated for exactly new_len elements in a previous expand_fast_array call with no slack returned by js_realloc2), the > check passes without expanding. The subsequent write to values[new_len - 1] uses new_len = count + 1, which is size + 1 — writing one JSValue (8 bytes) past the end of the buffer.

The trigger path via js_string_split repeatedly calls JS_CreateDataPropertyUint32 for each split element, hitting this boundary condition after the array reaches a specific allocation size.

PoC (81 bytes, minimized)
"auuuuuuuuuPpr( a= !d=uitr a= !d=uitrnŰ{ buŰ, buint8/////tbc".split("").r===

To reproduce:

# Build with ASan
clang -fsanitize=address -g -o qjs_asan quickjs.c libregexp.c libunicode.c cutils.c quickjs-libc.c

# Run PoC
./qjs_asan -i poc.js
Suggested Fix

One-line change — use >= instead of > so the buffer is expanded when new_len equals the current capacity:

--- a/quickjs.c
+++ b/quickjs.c
@@ -9763,7 +9763,7 @@ static inline int add_fast_array_element(JSContext *ctx, JSObject *p,
         }
     }
-    if (unlikely(new_len > p->u.array.u1.size)) {
+    if (unlikely(new_len >= p->u.array.u1.size)) {
         if (expand_fast_array(ctx, p, new_len)) {
             JS_FreeValue(ctx, val);
             return -1;
Lingua principale
C
Stelle
11.1k
Fork
1.2k
Merge medio
11h 20m
PR unite (30g)
1

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 bellard/quickjs

Tutte le issue di bellard/quickjs

Issue simili

Altre issue su C

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.