[Vulnerability] Pollfd Slot-Index Confusion in js_os_poll() : an fd number is used as the pollfd slot index, causing a heap-buffer-overflow and a release-build SIGSEGV
I maintainer di solito rispondono entro 3 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 55/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- c
- Ambito
- operating-systems, security
Direzione di ricerca
Read quickjs-libc.c around js_os_poll(), js_poll_add_poll_fd() and the JSThreadState poll fields. Trace how poll slot indices are assigned and used for the Atomics wakeup pipe, then check the supplied proof of concept or reproduce it with the ASan command in the issue. Done means the waker is accessed using its poll array slot and the reported out-of-bounds read no longer occurs.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
1. Summary
js_os_poll() appends the Atomics.waitAsync() waker pipe to ts->poll_fds[] without storing its slot index, then looks the slot up using the file descriptor number, although slot indices are handed out sequentially by js_poll_add_poll_fd() (the rw-handler, port and Windows handles[ret] paths all keep the index correctly — only this line mixes the two index spaces). Because js_poll_expand() only reallocs and never zeroes, both an out-of-bounds and an uninitialised-slot path are open. Ordinary JS triggers it: new os.Worker() parks a second runtime inside js_os_poll(), Atomics.waitAsync() plus a cross-runtime Atomics.notify() produces the wakeup, and os.open() pushes the fd number past the 16-entry array. ASan reliably reports heap-buffer-overflow READ of size 2 with an overrun of (read_fd-16)*8+6 bytes controlled linearly by the fd number; a plain release build SIGSEGVs from about 20 lines of JS (read_fd = 60011 → ≈469 KiB past the array into an unmapped page).
2. version:
Git master branch
commit: https://github.com/bellard/quickjs/commit/712e8c0ca26949477c38f91dbaf955cc48e2f261
3. OS and version:
Ubuntu 22.04 LTS
4. Steps to Reproduce:
The proof-of-concept file for reproducing the vulnerability:
(1) debug + asan build: heap-buffer-overflow
command: ./qjs-debug poc.js 300
Output:
[main] held fds = 300 next free fd = 305
[worker] async waiter registered; next free fd = 312
=================================================================
==15776==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7105aa5e6cf6 at pc 0x575aba7921d0 bp 0x7045a83fe910 sp 0x7045a83fe908
READ of size 2 at 0x7105aa5e6cf6 thread T1
#0 0x575aba7921cf in js_os_poll /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:2731:59
#1 0x575aba78a77e in js_std_loop /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:4406:30
#2 0x575aba794df7 in worker_func /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:3741:5
#3 0x575aba560246 in asan_thread_start(void*) asan_interceptors.cpp.o
#4 0x7445ab294ac2 in start_thread nptl/pthread_create.c:442:8
#5 0x7445ab3268cf misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
0x7105aa5e6cf6 is located 2358 bytes after 128-byte region [0x7105aa5e6340,0x7105aa5e63c0)
allocated by thread T1 here:
#0 0x575aba563381 in realloc (/home/testuser/Desktop/js/quickjs/build/debug-asan/qjs-debug+0x2ad381) (BuildId: 23ad03d87e7257f7f4d5f71e7004c194f8cfb0a4)
#1 0x575aba79378f in js_poll_expand /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:2593:15
#2 0x575aba792eeb in js_poll_add_poll_fd /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:2607:13
#3 0x575aba791ad5 in js_os_poll /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:2701:9
#4 0x575aba78a77e in js_std_loop /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:4406:30
#5 0x575aba794df7 in worker_func /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:3741:5
#6 0x575aba560246 in asan_thread_start(void*) asan_interceptors.cpp.o
Thread T1 created by T0 here:
#0 0x575aba545a11 in pthread_create (/home/testuser/Desktop/js/quickjs/build/debug-asan/qjs-debug+0x28fa11) (BuildId: 23ad03d87e7257f7f4d5f71e7004c194f8cfb0a4)
#1 0x575aba79281b in js_worker_ctor /home/testuser/Desktop/js/quickjs/quickjs/quickjs-libc.c:3839:11
#2 0x575aba5a9ff4 in js_call_c_function /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:18073:19
#3 0x575aba602cef in JS_CallConstructorInternal /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:21411:16
#4 0x575aba5e7f2b in JS_CallInternal /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:18666:27
#5 0x575aba6690b7 in async_func_resume /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:21542:15
#6 0x575aba6a5695 in js_async_function_resume /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:21821:16
#7 0x575aba617f04 in js_async_function_call /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:21915:5
#8 0x575aba672447 in js_execute_sync_module /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:32434:19
#9 0x575aba671c70 in js_inner_module_evaluation /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:32543:13
#10 0x575aba66e1b7 in js_evaluate_module /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:32593:9
#11 0x575aba606b22 in JS_EvalFunctionInternal /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:38329:19
#12 0x575aba606a1f in JS_EvalFunction /home/testuser/Desktop/js/quickjs/quickjs/quickjs.c:38343:12
#13 0x575aba5a66e5 in eval_buf /home/testuser/Desktop/js/quickjs/quickjs/qjs.c:66:19
#14 0x575aba5a68e6 in eval_file /home/testuser/Desktop/js/quickjs/quickjs/qjs.c:105:11
#15 0x575aba5a5dc7 in main /home/testuser/Desktop/js/quickjs/quickjs/qjs.c:538:17
#16 0x7445ab229d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
(2) release build: SIGSEGV
command: ulimit -n 200000; ./qjs ./poc.js 60000
Gdb output:
~/Desktop/quickjs/build/release$ ulimit -n 200000;gdb -q -batch -ex 'run poc.js 60000' -ex 'print ts->atomics_async_waker.read_fd' -ex 'print ts->poll_fds_size' --args ./qjs ./poc.js 60000
pwndbg: loaded 194 pwndbg commands. Type pwndbg [filter] for a list.
pwndbg: created 11 GDB functions (can be used with print/break). Type help function to see them.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[main] held fds = 60000 next free fd = 60005
[New Thread 0x7ffff7bff640 (LWP 16545)]
[worker] async waiter registered; next free fd = 60012
Thread 2 "qjs" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7bff640 (LWP 16545)]
0x00005555556c3aa9 in js_os_poll (ctx=ctx@entry=0x7ffff0009a90) at quickjs-libc.c:2731
2731 if (ts->poll_fds[ts->atomics_async_waker.read_fd].revents != 0) {
$1 = 0xea6a
$2 = 0x10
Exception occurred: Error: No selected thread. (<class 'gdb.error'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
Python Exception <class 'gdb.error'>: No selected thread
5. Root cause
When js_os_poll() appends the atomics wakeup JSWaker to ts->poll_fds[], it never stores the slot index; after poll() returns it indexes the slot array with the file descriptor number instead:
/* quickjs-libc.c:2701 defect #1: waker added, slot index not saved */
2701: if (js_poll_add_poll_fd(ts, &nfds, ts->atomics_async_waker.read_fd, POLLIN))
2702: return -1;
2704: nfds = poll(ts->poll_fds, nfds, min_delay);
2705: if (nfds > 0) {
...
2731: if (ts->poll_fds[ts->atomics_async_waker.read_fd].revents != 0) { /* defect #2: fd used as slot index */
2732: js_waker_clear(&ts->atomics_async_waker);
2733: }
Slot indices are assigned sequentially by js_poll_add_poll_fd() and have nothing to do with the fd number:
/* quickjs-libc.c:2601-2616 index is nfds; only 0..nfds-1 are ever written */
2610: fds = &ts->poll_fds[nfds++];
2611: fds->fd = fd;
2612: fds->events = events;
2613: fds->revents = 0;
6. Fix recommendations
/* quickjs-libc.c: new field in JSThreadState */
#if !defined(_WIN32)
struct pollfd *poll_fds;
int poll_fds_size;
int atomics_async_waker_poll_index; /* slot in poll_fds -- NOT the fd */
#endif
/* js_os_poll() @2701 */
ts->atomics_async_waker_poll_index = nfds;
if (js_poll_add_poll_fd(ts, &nfds, ts->atomics_async_waker.read_fd, POLLIN))
return -1;
/* js_os_poll() @2731 */
if (ts->poll_fds[ts->atomics_async_waker_poll_index].revents != 0) {
js_waker_clear(&ts->atomics_async_waker);
}
- Lingua principale
- C
- Stelle
- 11.1k
- Fork
- 1.2k
- Merge medio
- 11h 20m
- PR unite (30g)
- 1
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
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di bellard/quickjs
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
bellard/quickjs#602 · 1 commento ·
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 58/100
I maintainer di solito rispondono entro 3 giorni
Tutte le issue di bellard/quickjs
Issue simili
-
要トリアージ 辞書
Difficoltà 1/5 1-3 ore Idoneità per principianti 86/100
yksr-melt/Meltype#1625 · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
[yugabyted] xcluster set_up and add_to_replication fail for database names containing - . or (Apertakind/bug priority/medium qa_automation
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
yugabyte/yugabyte-db#37324 ·
I maintainer di solito rispondono entro 1 giorno
-
Add CMake export targetAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
tree-sitter/tree-sitter#6111 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
meshcore-dev/MeshCore#3781 ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 80/100
ly05010419/MacNativeGames#1897 ·
I maintainer di solito rispondono entro 1 giorno