Heap-buffer-overflow in add_fast_array_element() via String.prototype.split("")
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
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
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di bellard/quickjs
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di bellard/quickjs
Issue simili
-
5700模组网络优先级问题Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
severity: low
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
luainkernel/lunatik#2372 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 75/100
ly05010419/MacNativeGames#1796 ·
I maintainer di solito rispondono entro 1 giorno
-
ZSTD_decompressStream accepts a Compressed_Block with Block_Size 0 that ZSTD_decompress rejectsAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
jniebuhr/gaggimate#1009 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno