bellard / quickjs

Public repository of the QuickJS Javascript Engine.
https://bellard.org/quickjs
Other
8.09k stars 844 forks source link

Running GC or exiting with pending promise in top level await causes use-after-free crash in GC. #280

Open chmeyer-ms opened 3 months ago

chmeyer-ms commented 3 months ago

Version 3b45d155c77bbdfe9177b1e03db830d2aff0b2a8

Tested on Windows (vc and clang) and Linux (gcc).

This will crash on Windows but appears to succeed on Linux, however Valgrind reports a use-after-free issue. Windows builds with /fsanitize=address (or -fsanitize) result in a similar report.

On Windows the crash comes from JS_RunGC (either called manually or during JS_FreeRuntime). The top of the call stack is JS_FreeValueRT coming from the finalizer for js_c_function_data_finalizer with a JSCFunctionDataRecord containing js_async_module_execution_fulfilled.

A few things to note:

Test code

int main(int argc, char **argv)
{
    const char* script = 
    "let p = new Promise((resolve, reject) => {"
    "});"
    "await p;";

    JSRuntime* rt = JS_NewRuntime();
    JSContext* ctx = JS_NewContext(rt);
    JSValue ret = JS_Eval(ctx, script, -1, "eval", JS_EVAL_TYPE_MODULE);
    JS_FreeValue(ctx, ret);
    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
}

Valgrind output.

==29640== Memcheck, a memory error detector
==29640== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==29640== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==29640== Command: ./qjs-debug
==29640==
==29640== Invalid read of size 4
==29640==    at 0x116951: JS_FreeValueRT (quickjs.h:661)
==29640==    by 0x120984: js_c_function_data_finalizer (quickjs.c:5106)
==29640==    by 0x121937: free_object (quickjs.c:5467)
==29640==    by 0x121A14: free_gc_object (quickjs.c:5487)
==29640==    by 0x1226A1: gc_free_cycles (quickjs.c:5837)
==29640==    by 0x1227E8: JS_RunGC (quickjs.c:5868)
==29640==    by 0x1185F1: JS_FreeRuntime (quickjs.c:1956)
==29640==    by 0x116141: main (qjs.c:581)
==29640==  Address 0x4baaa60 is 0 bytes inside a block of size 280 free'd
==29640==    at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==29640==    by 0x117E8A: js_def_free (quickjs.c:1744)
==29640==    by 0x117124: js_free_rt (quickjs.c:1320)
==29640==    by 0x1172E9: js_free (quickjs.c:1375)
==29640==    by 0x168CBC: js_free_module_def (quickjs.c:27323)
==29640==    by 0x118C4C: js_free_modules (quickjs.c:2231)
==29640==    by 0x118FAA: JS_FreeContext (quickjs.c:2317)
==29640==    by 0x120E31: js_autoinit_free (quickjs.c:5188)
==29640==    by 0x120F68: free_property (quickjs.c:5208)
==29640==    by 0x12185D: free_object (quickjs.c:5449)
==29640==    by 0x121A14: free_gc_object (quickjs.c:5487)
==29640==    by 0x1226A1: gc_free_cycles (quickjs.c:5837)
==29640==  Block was alloc'd at
==29640==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==29640==    by 0x117DDE: js_def_malloc (quickjs.c:1728)
==29640==    by 0x1170F2: js_malloc_rt (quickjs.c:1315)
==29640==    by 0x1171AC: js_mallocz_rt (quickjs.c:1336)
==29640==    by 0x117291: js_mallocz (quickjs.c:1365)
==29640==    by 0x168739: js_new_module_def (quickjs.c:27245)
==29640==    by 0x17C751: __JS_EvalInternal (quickjs.c:34420)
==29640==    by 0x17CC80: JS_EvalInternal (quickjs.c:34504)
==29640==    by 0x17CE32: JS_EvalThis (quickjs.c:34535)
==29640==    by 0x17CE9A: JS_Eval (quickjs.c:34543)
==29640==    by 0x11610A: main (qjs.c:578)
==29640==
==29640== Invalid write of size 4
==29640==    at 0x11695A: JS_FreeValueRT (quickjs.h:661)
==29640==    by 0x120984: js_c_function_data_finalizer (quickjs.c:5106)
==29640==    by 0x121937: free_object (quickjs.c:5467)
==29640==    by 0x121A14: free_gc_object (quickjs.c:5487)
==29640==    by 0x1226A1: gc_free_cycles (quickjs.c:5837)
==29640==    by 0x1227E8: JS_RunGC (quickjs.c:5868)
==29640==    by 0x1185F1: JS_FreeRuntime (quickjs.c:1956)
==29640==    by 0x116141: main (qjs.c:581)
==29640==  Address 0x4baaa60 is 0 bytes inside a block of size 280 free'd
==29640==    at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==29640==    by 0x117E8A: js_def_free (quickjs.c:1744)
==29640==    by 0x117124: js_free_rt (quickjs.c:1320)
==29640==    by 0x1172E9: js_free (quickjs.c:1375)
==29640==    by 0x168CBC: js_free_module_def (quickjs.c:27323)
==29640==    by 0x118C4C: js_free_modules (quickjs.c:2231)
==29640==    by 0x118FAA: JS_FreeContext (quickjs.c:2317)
==29640==    by 0x120E31: js_autoinit_free (quickjs.c:5188)
==29640==    by 0x120F68: free_property (quickjs.c:5208)
==29640==    by 0x12185D: free_object (quickjs.c:5449)
==29640==    by 0x121A14: free_gc_object (quickjs.c:5487)
==29640==    by 0x1226A1: gc_free_cycles (quickjs.c:5837)
==29640==  Block was alloc'd at
==29640==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==29640==    by 0x117DDE: js_def_malloc (quickjs.c:1728)
==29640==    by 0x1170F2: js_malloc_rt (quickjs.c:1315)
==29640==    by 0x1171AC: js_mallocz_rt (quickjs.c:1336)
==29640==    by 0x117291: js_mallocz (quickjs.c:1365)
==29640==    by 0x168739: js_new_module_def (quickjs.c:27245)
==29640==    by 0x17C751: __JS_EvalInternal (quickjs.c:34420)
==29640==    by 0x17CC80: JS_EvalInternal (quickjs.c:34504)
==29640==    by 0x17CE32: JS_EvalThis (quickjs.c:34535)
==29640==    by 0x17CE9A: JS_Eval (quickjs.c:34543)
==29640==    by 0x11610A: main (qjs.c:578)
==29640==
==29640== Invalid read of size 4
==29640==    at 0x116960: JS_FreeValueRT (quickjs.h:661)
==29640==    by 0x120984: js_c_function_data_finalizer (quickjs.c:5106)
==29640==    by 0x121937: free_object (quickjs.c:5467)
==29640==    by 0x121A14: free_gc_object (quickjs.c:5487)
==29640==    by 0x1226A1: gc_free_cycles (quickjs.c:5837)
==29640==    by 0x1227E8: JS_RunGC (quickjs.c:5868)
==29640==    by 0x1185F1: JS_FreeRuntime (quickjs.c:1956)
==29640==    by 0x116141: main (qjs.c:581)
==29640==  Address 0x4baaa60 is 0 bytes inside a block of size 280 free'd
==29640==    at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==29640==    by 0x117E8A: js_def_free (quickjs.c:1744)
==29640==    by 0x117124: js_free_rt (quickjs.c:1320)
==29640==    by 0x1172E9: js_free (quickjs.c:1375)
==29640==    by 0x168CBC: js_free_module_def (quickjs.c:27323)
==29640==    by 0x118C4C: js_free_modules (quickjs.c:2231)
==29640==    by 0x118FAA: JS_FreeContext (quickjs.c:2317)
==29640==    by 0x120E31: js_autoinit_free (quickjs.c:5188)
==29640==    by 0x120F68: free_property (quickjs.c:5208)
==29640==    by 0x12185D: free_object (quickjs.c:5449)
==29640==    by 0x121A14: free_gc_object (quickjs.c:5487)
==29640==    by 0x1226A1: gc_free_cycles (quickjs.c:5837)
==29640==  Block was alloc'd at
==29640==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==29640==    by 0x117DDE: js_def_malloc (quickjs.c:1728)
==29640==    by 0x1170F2: js_malloc_rt (quickjs.c:1315)
==29640==    by 0x1171AC: js_mallocz_rt (quickjs.c:1336)
==29640==    by 0x117291: js_mallocz (quickjs.c:1365)
==29640==    by 0x168739: js_new_module_def (quickjs.c:27245)
==29640==    by 0x17C751: __JS_EvalInternal (quickjs.c:34420)
==29640==    by 0x17CC80: JS_EvalInternal (quickjs.c:34504)
==29640==    by 0x17CE32: JS_EvalThis (quickjs.c:34535)
==29640==    by 0x17CE9A: JS_Eval (quickjs.c:34543)
==29640==    by 0x11610A: main (qjs.c:578)
==29640==
==29640==
==29640== HEAP SUMMARY:
==29640==     in use at exit: 0 bytes in 0 blocks
==29640==   total heap usage: 1,501 allocs, 1,501 frees, 135,153 bytes allocated
==29640==
==29640== All heap blocks were freed -- no leaks are possible
==29640==
==29640== For lists of detected and suppressed errors, rerun with: -s
==29640== ERROR SUMMARY: 6 errors from 3 contexts (suppressed: 0 from 0)
JakeShirley commented 2 months ago

Does force-rejecting all of the promises in the system resolve the use-after-free here?