From 65641a0c1e85cc266d7613d6673a22ec834bb941 Mon Sep 17 00:00:00 2001 From: Aayush Kapoor Date: Tue, 25 Aug 2026 16:56:38 -0400 Subject: [PATCH 1/7] Fix(wasi): enable configurable stack overflow protection --- .github/workflows/ci.yml | 5 +++++ quickjs.c | 4 ---- tests/wasi-stack-limit.js | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tests/wasi-stack-limit.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c22bf73e5..6d1bf10c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -469,6 +469,11 @@ jobs: wasmtime run build/qjs -qd echo "console.log('hello wasi!');" > t.js wasmtime run --dir . build/qjs t.js + wasmtime run --dir . build/qjs --stack-size 64K tests/wasi-stack-limit.js + if wasmtime run --dir . build/qjs tests/wasi-stack-limit.js; then + echo "WASI stack guard should be disabled by default" + exit 1 + fi - name: build wasi reactor run: | cmake -B build -DQJS_BUILD_WERROR=ON -DCMAKE_TOOLCHAIN_FILE=/opt/wasi-sdk/share/cmake/wasi-sdk.cmake -DQJS_WASI_REACTOR=ON diff --git a/quickjs.c b/quickjs.c index 43819d7a9..9908e0934 100644 --- a/quickjs.c +++ b/quickjs.c @@ -3122,15 +3122,11 @@ JSRuntime *JS_GetRuntime(JSContext *ctx) static void update_stack_limit(JSRuntime *rt) { -#if defined(__wasi__) - rt->stack_limit = 0; /* no limit */ -#else if (rt->stack_size == 0) { rt->stack_limit = 0; /* no limit */ } else { rt->stack_limit = rt->stack_top - rt->stack_size; } -#endif } void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) diff --git a/tests/wasi-stack-limit.js b/tests/wasi-stack-limit.js new file mode 100644 index 000000000..630bc4327 --- /dev/null +++ b/tests/wasi-stack-limit.js @@ -0,0 +1,17 @@ +let overflow; + +try { + (function recurse() { + recurse(); + })(); +} catch (error) { + overflow = error; +} + +if (!(overflow instanceof Error) || + !/stack/i.test(overflow.message)) { + throw new Error(`expected a stack overflow, got ${overflow}`); +} + +if (Function("return 20 + 22")() !== 42) + throw new Error("runtime is unusable after stack overflow"); From 78e0b523359151254d17a61fc9ce9333fce7f313 Mon Sep 17 00:00:00 2001 From: Aayush Kapoor Date: Tue, 1 Sep 2026 10:13:12 -0400 Subject: [PATCH 2/7] use default stack size --- quickjs.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/quickjs.c b/quickjs.c index 9908e0934..66488a43b 100644 --- a/quickjs.c +++ b/quickjs.c @@ -2368,9 +2368,6 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) rt->js_class_id_alloc = JS_CLASS_INIT_COUNT; rt->stack_size = JS_DEFAULT_STACK_SIZE; -#ifdef __wasi__ - rt->stack_size = 0; -#endif JS_UpdateStackTop(rt); From f1e28b766c96e3f6129c61b0327ade51e27e2035 Mon Sep 17 00:00:00 2001 From: Aayush Kapoor Date: Tue, 1 Sep 2026 12:36:20 -0400 Subject: [PATCH 3/7] enable stack checks --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d1bf10c9..680b962b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -470,10 +470,7 @@ jobs: echo "console.log('hello wasi!');" > t.js wasmtime run --dir . build/qjs t.js wasmtime run --dir . build/qjs --stack-size 64K tests/wasi-stack-limit.js - if wasmtime run --dir . build/qjs tests/wasi-stack-limit.js; then - echo "WASI stack guard should be disabled by default" - exit 1 - fi + wasmtime run --dir . build/qjs tests/wasi-stack-limit.js - name: build wasi reactor run: | cmake -B build -DQJS_BUILD_WERROR=ON -DCMAKE_TOOLCHAIN_FILE=/opt/wasi-sdk/share/cmake/wasi-sdk.cmake -DQJS_WASI_REACTOR=ON From 40752fb3040df0c135adf2290f769c23ff8abe57 Mon Sep 17 00:00:00 2001 From: Aayush Kapoor Date: Tue, 1 Sep 2026 13:18:54 -0400 Subject: [PATCH 4/7] fix ci --- quickjs.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quickjs.h b/quickjs.h index a78077989..3654cdfc4 100644 --- a/quickjs.h +++ b/quickjs.h @@ -440,8 +440,12 @@ static inline bool JS_VALUE_IS_NAN(JSValue v) #define JS_PROP_REFLECT_DEFINE_PROPERTY (1 << 19) /* internal use */ #ifndef JS_DEFAULT_STACK_SIZE +#ifdef __wasi__ +#define JS_DEFAULT_STACK_SIZE (64 * 1024) +#else #define JS_DEFAULT_STACK_SIZE (1024 * 1024) #endif +#endif /* JS_Eval() flags */ #define JS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */ From 2a312de9499d2a5f92503dbff7128aab50716c91 Mon Sep 17 00:00:00 2001 From: Aayush Kapoor Date: Mon, 14 Sep 2026 00:31:36 +0530 Subject: [PATCH 5/7] fix stack reservation --- CMakeLists.txt | 1 + quickjs.h | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69c39ce16..5d93324a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WASI") _WASI_EMULATED_SIGNAL ) add_link_options( + -Wl,-z,stack-size=2097152 -lwasi-emulated-process-clocks -lwasi-emulated-signal ) diff --git a/quickjs.h b/quickjs.h index 3654cdfc4..a78077989 100644 --- a/quickjs.h +++ b/quickjs.h @@ -440,12 +440,8 @@ static inline bool JS_VALUE_IS_NAN(JSValue v) #define JS_PROP_REFLECT_DEFINE_PROPERTY (1 << 19) /* internal use */ #ifndef JS_DEFAULT_STACK_SIZE -#ifdef __wasi__ -#define JS_DEFAULT_STACK_SIZE (64 * 1024) -#else #define JS_DEFAULT_STACK_SIZE (1024 * 1024) #endif -#endif /* JS_Eval() flags */ #define JS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */ From 239b550dd3f05be7317f535032aecffa69c02f36 Mon Sep 17 00:00:00 2001 From: Aayush Kapoor Date: Tue, 15 Sep 2026 13:49:19 +0530 Subject: [PATCH 6/7] fix stack limit underflow --- quickjs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickjs.c b/quickjs.c index 66488a43b..1f4151efc 100644 --- a/quickjs.c +++ b/quickjs.c @@ -3119,7 +3119,7 @@ JSRuntime *JS_GetRuntime(JSContext *ctx) static void update_stack_limit(JSRuntime *rt) { - if (rt->stack_size == 0) { + if (rt->stack_size == 0 || rt->stack_top < rt->stack_size) { rt->stack_limit = 0; /* no limit */ } else { rt->stack_limit = rt->stack_top - rt->stack_size; From 351cd18e1333bdaec62a5cf7de14d7b107b4a391 Mon Sep 17 00:00:00 2001 From: Aayush Kapoor Date: Tue, 15 Sep 2026 14:12:54 +0530 Subject: [PATCH 7/7] fix tests --- tests/wasi-stack-limit.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/wasi-stack-limit.js b/tests/wasi-stack-limit.js index 630bc4327..dea86fc7c 100644 --- a/tests/wasi-stack-limit.js +++ b/tests/wasi-stack-limit.js @@ -1,9 +1,14 @@ let overflow; +// Use a large interpreter frame so QuickJS's linear-memory stack guard trips +// before Wasmtime's independent WebAssembly call stack is exhausted. +const args = Array(1024).fill("0").join(","); +const recurse = Function( + `return function recurse() { recurse(${args}); }` +)(); + try { - (function recurse() { - recurse(); - })(); + recurse(); } catch (error) { overflow = error; }