From: Sergey Bronnikov via Tarantool-patches <tarantool-patches@dev.tarantool.org> To: tarantool-patches@dev.tarantool.org, Sergey Kaplun <skaplun@tarantool.org> Subject: [Tarantool-patches] [PATCH luajit 1/2] LJ_FR2: Fix stack checks in vararg calls. Date: Wed, 27 Aug 2025 12:44:37 +0300 [thread overview] Message-ID: <43f2870a9d46587fde4b3dd31c46af0563dac455.1756287598.git.sergeyb@tarantool.org> (raw) In-Reply-To: <cover.1756287598.git.sergeyb@tarantool.org> Thanks to Peter Cawley. (cherry picked from commit d1a2fef8a8f53b0055ee041f7f63d83a27444ffa) The builtin `pcall()` has two separate ways by which it can grow the stack by one slot: 1. Resolving the `__call` metamethod of its first argument. 2. Growing the stack by one slot in LJ_FR2 mode. The first case leads to a stack smash if `pcall()` is used as `__call`. Setting a metatable with this metamethod will cause an infinite loop which fills up the stack with `pcall`-frames and then keeps going beyond the end of the stack until it segfaults. Either of these points can cause an issue if `pcall()` is used as `__newindex`. The patch partially fixes aforementioned issues. Sergey Bronnikov: * added the description and the test for the problem Part of tarantool/tarantool#11691 --- src/lj_def.h | 2 +- src/lj_dispatch.c | 2 +- src/vm_arm64.dasc | 1 + src/vm_mips64.dasc | 1 + ...048-fix-stack-checks-vararg-calls.test.lua | 56 +++++++++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/tarantool-tests/lj-1048-fix-stack-checks-vararg-calls.test.lua diff --git a/src/lj_def.h b/src/lj_def.h index a5bca6b0..7e4f251e 100644 --- a/src/lj_def.h +++ b/src/lj_def.h @@ -69,7 +69,7 @@ typedef unsigned int uintptr_t; #define LJ_MAX_UPVAL 60 /* Max. # of upvalues. */ #define LJ_MAX_IDXCHAIN 100 /* __index/__newindex chain limit. */ -#define LJ_STACK_EXTRA (5+2*LJ_FR2) /* Extra stack space (metamethods). */ +#define LJ_STACK_EXTRA (5+3*LJ_FR2) /* Extra stack space (metamethods). */ #define LJ_NUM_CBPAGE 1 /* Number of FFI callback pages. */ diff --git a/src/lj_dispatch.c b/src/lj_dispatch.c index a44a5adf..431cb3c2 100644 --- a/src/lj_dispatch.c +++ b/src/lj_dispatch.c @@ -453,7 +453,7 @@ static int call_init(lua_State *L, GCfunc *fn) int numparams = pt->numparams; int gotparams = (int)(L->top - L->base); int need = pt->framesize; - if ((pt->flags & PROTO_VARARG)) need += 1+gotparams; + if ((pt->flags & PROTO_VARARG)) need += 1+LJ_FR2+gotparams; lj_state_checkstack(L, (MSize)need); numparams -= gotparams; return numparams >= 0 ? numparams : 0; diff --git a/src/vm_arm64.dasc b/src/vm_arm64.dasc index c5f0a7a7..cf8e575a 100644 --- a/src/vm_arm64.dasc +++ b/src/vm_arm64.dasc @@ -3779,6 +3779,7 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop) | add TMP2, BASE, RC | add LFUNC:CARG3, CARG3, TMP0, lsl #47 | add RA, RA, RC + | sub CARG1, CARG1, #8 | add TMP0, RC, #16+FRAME_VARG | str LFUNC:CARG3, [TMP2], #8 // Store (tagged) copy of LFUNC. | ldr KBASE, [PC, #-4+PC2PROTO(k)] diff --git a/src/vm_mips64.dasc b/src/vm_mips64.dasc index 44fba36c..7f49df5b 100644 --- a/src/vm_mips64.dasc +++ b/src/vm_mips64.dasc @@ -5267,6 +5267,7 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop) | settp LFUNC:RB, TMP0 | daddu TMP0, RA, RC | sd LFUNC:RB, 0(TMP1) // Store (tagged) copy of LFUNC. + | daddiu TMP2, TMP2, -8 | daddiu TMP3, RC, 16+FRAME_VARG | sltu AT, TMP0, TMP2 | ld KBASE, -4+PC2PROTO(k)(PC) diff --git a/test/tarantool-tests/lj-1048-fix-stack-checks-vararg-calls.test.lua b/test/tarantool-tests/lj-1048-fix-stack-checks-vararg-calls.test.lua new file mode 100644 index 00000000..e300d5c1 --- /dev/null +++ b/test/tarantool-tests/lj-1048-fix-stack-checks-vararg-calls.test.lua @@ -0,0 +1,56 @@ +local tap = require('tap') + +-- A test file to demonstrate a stack overflow in `pcall()` in +-- some cases, see below testcase descriptions. +-- See also https://github.com/LuaJIT/LuaJIT/issues/1048. +local test = tap.test('lj-1048-fix-stack-checks-vararg-calls'):skipcond({ + ['Test requires JIT enabled'] = not jit.status(), +}) + +test:plan(2) + +-- The first testcase demonstrate a stack overflow in `pcall()` +-- by recursive calling `pcall()`. The functions are vararg +-- because stack check in BC_IFUNCV is off by one without the +-- patch. +local function prober_1(...) -- luacheck: no unused + pcall(pcall, pcall, pcall, pcall, pcall, pcall, pcall, pcall, pairs, {}) +end + +local function looper_1(n, ...) + prober_1(...) + prober_1(nil, ...) + return looper_1(n + 1, n, ...) +end + +pcall(coroutine.wrap(looper_1), 0) + +test:ok(true, 'no stack overflow with recursive pcall') + +-- The second testcase demonstrate a stack overflow in `pcall()` +-- with using metamethods. A stack overflow is triggered when +-- `pcall()` is used as `__call` metamethod, setting metatable +-- will cause an infinite loop which fills up the stack with +-- `pcall`-frames and then keeps going beyond the end of the +-- stack until it segfaults. Also, a stack overflow can be +-- triggered when `pcall()` is used as `__newindex` metamethod. +-- The functions are vararg because stack check in BC_IFUNCV is +-- off by one without the patch. + +local mt = setmetatable({}, { __newindex = pcall, __call = pairs }) + +local function prober_2(...) -- luacheck: no unused + mt[mt] = mt +end + +local function looper_2(n, ...) + prober_2(...) + prober_2(nil, ...) + return looper_2(n + 1, n, ...) +end + +pcall(coroutine.wrap(looper_2), 0) + +test:ok(true, 'no stack overflow with using metamethod') + +test:done(true) -- 2.43.0
next prev parent reply other threads:[~2025-08-27 9:45 UTC|newest] Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top 2025-08-27 9:44 [Tarantool-patches] [PATCH luajit 0/2] Fix stack overflow in pcall/xpcall Sergey Bronnikov via Tarantool-patches 2025-08-27 9:44 ` Sergey Bronnikov via Tarantool-patches [this message] 2025-09-01 13:07 ` [Tarantool-patches] [PATCH luajit 1/2] LJ_FR2: Fix stack checks in vararg calls Sergey Kaplun via Tarantool-patches 2025-08-27 9:44 ` [Tarantool-patches] [PATCH luajit 2/2] Add stack check to pcall/xpcall Sergey Bronnikov via Tarantool-patches 2025-09-01 13:36 ` Sergey Kaplun via Tarantool-patches
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=43f2870a9d46587fde4b3dd31c46af0563dac455.1756287598.git.sergeyb@tarantool.org \ --to=tarantool-patches@dev.tarantool.org \ --cc=estetus@gmail.com \ --cc=skaplun@tarantool.org \ --subject='Re: [Tarantool-patches] [PATCH luajit 1/2] LJ_FR2: Fix stack checks in vararg calls.' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox