From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 00B7813E8CC0; Fri, 6 Jun 2025 15:54:05 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 00B7813E8CC0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1749214445; bh=v2l+A56qS0b5IDE+3xuX3DZGIIEFTRzq0DB9Jb9xYCU=; h=Date:To:Cc:References:In-Reply-To:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=pgP26DimMY6agx27oTo8nopOi1iXEth7i89BKmYkWQZgrxRaKFplMzR254FfiORtv gjCpAbqC+EEmE3KjqmdUdXRtsyCgTXleEQP9KbhKUhNofxIQbV8PJZF4gUyFq0cBR8 nPn88CTAbuHlZbM7adA3EQ9+p+h3MK7eWCuC3BjY= Received: from send264.i.mail.ru (send264.i.mail.ru [95.163.59.103]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 08AC113E8CC2 for ; Fri, 6 Jun 2025 15:54:04 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 08AC113E8CC2 Received: by exim-smtp-567cc788d4-7jjpv with esmtpa (envelope-from ) id 1uNWaA-00000000GSa-3okV; Fri, 06 Jun 2025 15:54:03 +0300 Content-Type: multipart/alternative; boundary="------------zYs8WMATaS63qkFIkC4WsZtu" Message-ID: <4c846b1b-e15e-49a8-86a7-da857c7a5a5b@tarantool.org> Date: Fri, 6 Jun 2025 15:54:02 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Content-Language: en-US To: Sergey Kaplun Cc: tarantool-patches@dev.tarantool.org References: <20250425134215.8184-1-skaplun@tarantool.org> In-Reply-To: <20250425134215.8184-1-skaplun@tarantool.org> X-Mailru-Src: smtp X-4EC0790: 10 X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD95E97EA1894635BC30BDA652621C30F12CEE5E60E62F0856D182A05F538085040B4E497611073AE493DE06ABAFEAF67050BBB8E4C97A02C567C39B2E766707315B21206976FA500EF X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE721AF84DC1D70954DEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637AC83A81C8FD4AD23D82A6BABE6F325AC2E85FA5F3EDFCBAA7353EFBB5533756680FD86C7FE90CFE5D270CA750F35017C5E836DD39301923664A9E8430FC04A88389733CBF5DBD5E913377AFFFEAFD269176DF2183F8FC7C0D9442B0B5983000E8941B15DA834481FCF19DD082D7633A0EF3E4896CB9E6436389733CBF5DBD5E9D5E8D9A59859A8B636DA1BED736F9328CC7F00164DA146DA6F5DAA56C3B73B237318B6A418E8EAB8D32BA5DBAC0009BE9E8FC8737B5C2249B3B269C6689FA51776E601842F6C81A12EF20D2F80756B5FB606B96278B59C4276E601842F6C81A127C277FBC8AE2E8BAB01FFF7D7E061323AA81AA40904B5D99C9F4D5AE37F343AD1F44FA8B9022EA23BBE47FD9DD3FB595F5C1EE8F4F765FC72CEEB2601E22B093A03B725D353964B0B7D0EA88DDEDAC722CA9DD8327EE4930A3850AC1BE2E735D2457FAF19517CF2C4224003CC83647689D4C264860C145E X-C1DE0DAB: 0D63561A33F958A5774406999DB5EC3D5002B1117B3ED69682BE52FCE397443E5B6221DB6D7A72AD823CB91A9FED034534781492E4B8EEAD39B22F2BA948E1C8BDAD6C7F3747799A X-C8649E89: 1C3962B70DF3F0ADE00A9FD3E00BEEDF3FED46C3ACD6F73ED3581295AF09D3DF87807E0823442EA2ED31085941D9CD0AF7F820E7B07EA4CFFAE8C62B82B3C046B321DCA1D4C7AF700A6E581F647F99E11C26E1D1B0EEB59EA96D85E4AF70DF374322DC41441C31F0FF931529A83B5D1856F4120204BBB19C780F8846E4051A6D111DC66A97D0BFE2913E6812662D5F2AB9AF64DB4688768036DF5FE9C0001AF333F2C28C22F508233FCF178C6DD14203 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXMZebaIdHP2ghjoIc/363UZI6Kf1ptIMVSykAyseJQ6/JAN/Gl+WIis= X-Mailru-Sender: 520A125C2F17F0B1E52FEF5D219D6140E1D58D6D4E9F0527479CDAE959BF642488E83B44E42233360152A3D17938EB451EB5A0BCEC6A560B3DDE9B364B0DF289BE2DA36745F2EEB5CEBA01FB949A1F1EEAB4BC95F72C04283CDA0F3B3F5B9367 X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH luajit] Rework stack overflow handling. X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Bronnikov via Tarantool-patches Reply-To: Sergey Bronnikov Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" This is a multi-part message in MIME format. --------------zYs8WMATaS63qkFIkC4WsZtu Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Hello, Sergey, thanks for the patch! See my comments below. Sergey On 4/25/25 16:42, Sergey Kaplun wrote: > From: Mike Pall > > Reported by pwnhacker0x18. Fixed by Peter Cawley. > > (cherry picked from commit defe61a56751a0db5f00ff3ab7b8f45436ba74c8) > > In case of the Lua stack overflow error, LuaJIT restores the `L->top` > value and pushes the error message above. It is possible that the > restored value is greater than `L->maxstack`, so pushing the error > message causes dirty write out-of-bounds. > > This patch prevents it by overwriting stack overflow handling machinery. > Now, in the aforementioned case, the last frame is replaced with a dummy > frame to avoid dirty writes. > > Sergey Kaplun: > * added the description and the test for the problem > > Part of tarantool/tarantool#11278 > --- > > Related issues: > *https://github.com/LuaJIT/LuaJIT/issues/1152 > *https://github.com/tarantool/tarantool/issues/11278 > Branch:https://github.com/tarantool/luajit/tree/skaplun/lj-1152-stack-buffer-overflow-on-error > > The CI is red due to CMake minimum required version in c-dt module in > Tarantool. These problems are already fixed, please re-push the branch. > > The cherry-pick also included a part fixed in the merge commit: > https://github.com/LuaJIT/LuaJIT/commit/0d313b243 > > src/lj_debug.c | 1 + > src/lj_err.c | 23 +++++- > src/lj_err.h | 1 + > src/lj_state.c | 58 +++++++++----- > test/LuaJIT-tests/lang/stackov.lua | 8 +- > ...52-stack-buffer-overflow-on-error.test.lua | 79 +++++++++++++++++++ > test/tarantool-tests/utils/CMakeLists.txt | 1 + > 7 files changed, 148 insertions(+), 23 deletions(-) > create mode 100644 test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua > > diff --git a/src/lj_debug.c b/src/lj_debug.c > index 107f464c..76e48aca 100644 > --- a/src/lj_debug.c > +++ b/src/lj_debug.c > @@ -64,6 +64,7 @@ static BCPos debug_framepc(lua_State *L, GCfunc *fn, cTValue *nextframe) > if (cf == NULL || (char *)cframe_pc(cf) == (char *)cframe_L(cf)) > return NO_BCPOS; > ins = cframe_pc(cf); /* Only happens during error/hook handling. */ > + if (!ins) return NO_BCPOS; > } else { > if (frame_islua(nextframe)) { > ins = frame_pc(nextframe); > diff --git a/src/lj_err.c b/src/lj_err.c > index 1a9a2f2b..80dca847 100644 > --- a/src/lj_err.c > +++ b/src/lj_err.c > @@ -784,7 +784,14 @@ LJ_NOINLINE void lj_err_mem(lua_State *L) > TValue *base = tvref(G(L)->jit_base); > if (base) L->base = base; > } > - if (curr_funcisL(L)) L->top = curr_topL(L); > + if (curr_funcisL(L)) { > + L->top = curr_topL(L); > + if (LJ_UNLIKELY(L->top > tvref(L->maxstack))) { > + /* The current Lua frame violates the stack. Replace it with a dummy. */ > + L->top = L->base; > + setframe_gc(L->base - 1 - LJ_FR2, obj2gco(L), LJ_TTHREAD); > + } > + } > setstrV(L, L->top++, lj_err_str(L, LJ_ERR_ERRMEM)); > lj_err_throw(L, LUA_ERRMEM); > } > @@ -845,9 +852,11 @@ LJ_NOINLINE void LJ_FASTCALL lj_err_run(lua_State *L) > { > ptrdiff_t ef = (LJ_HASJIT && tvref(G(L)->jit_base)) ? 0 : finderrfunc(L); > if (ef) { > - TValue *errfunc = restorestack(L, ef); > - TValue *top = L->top; > + TValue *errfunc, *top; > + lj_state_checkstack(L, LUA_MINSTACK * 2); /* Might raise new error. */ > lj_trace_abort(G(L)); > + errfunc = restorestack(L, ef); > + top = L->top; > if (!tvisfunc(errfunc) || L->status == LUA_ERRERR) { > setstrV(L, top-1, lj_err_str(L, LJ_ERR_ERRERR)); > lj_err_throw(L, LUA_ERRERR); > @@ -862,7 +871,15 @@ LJ_NOINLINE void LJ_FASTCALL lj_err_run(lua_State *L) > lj_err_throw(L, LUA_ERRRUN); > } > > +/* Stack overflow error. */ > +void LJ_FASTCALL lj_err_stkov(lua_State *L) > +{ > + lj_debug_addloc(L, err2msg(LJ_ERR_STKOV), L->base-1, NULL); > + lj_err_run(L); > +} > + > #if LJ_HASJIT > +/* Rethrow error after doing a trace exit. */ > LJ_NOINLINE void LJ_FASTCALL lj_err_trace(lua_State *L, int errcode) > { > if (errcode == LUA_ERRRUN) > diff --git a/src/lj_err.h b/src/lj_err.h > index b0c72c24..56bd0375 100644 > --- a/src/lj_err.h > +++ b/src/lj_err.h > @@ -23,6 +23,7 @@ LJ_DATA const char *lj_err_allmsg; > LJ_FUNC GCstr *lj_err_str(lua_State *L, ErrMsg em); > LJ_FUNCA_NORET void LJ_FASTCALL lj_err_throw(lua_State *L, int errcode); > LJ_FUNC_NORET void lj_err_mem(lua_State *L); > +LJ_FUNC_NORET void LJ_FASTCALL lj_err_stkov(lua_State *L); > LJ_FUNC_NORET void LJ_FASTCALL lj_err_run(lua_State *L); > #if LJ_HASJIT > LJ_FUNCA_NORET void LJ_FASTCALL lj_err_trace(lua_State *L, int errcode); > diff --git a/src/lj_state.c b/src/lj_state.c > index 5a920102..053e5ec9 100644 > --- a/src/lj_state.c > +++ b/src/lj_state.c > @@ -120,27 +120,49 @@ void lj_state_shrinkstack(lua_State *L, MSize used) > /* Try to grow stack. */ > void LJ_FASTCALL lj_state_growstack(lua_State *L, MSize need) > { > - MSize n; > - if (L->stacksize >= LJ_STACK_MAXEX) { > - /* 4. Throw 'error in error handling' when we are _over_ the limit. */ > - if (L->stacksize > LJ_STACK_MAXEX) > + MSize n = L->stacksize + need; > + if (LJ_LIKELY(n < LJ_STACK_MAX)) { /* The stack can grow as requested. */ > + if (n < 2 * L->stacksize) { /* Try to double the size. */ > + n = 2 * L->stacksize; > + if (n > LJ_STACK_MAX) > + n = LJ_STACK_MAX; > + } > + resizestack(L, n); > + } else { /* Request would overflow. Raise a stack overflow error. */ > + if (LJ_HASJIT) { > + TValue *base = tvref(G(L)->jit_base); > + if (base) L->base = base; > + } > + if (curr_funcisL(L)) { > + L->top = curr_topL(L); > + if (L->top > tvref(L->maxstack)) { > + /* The current Lua frame violates the stack, so replace it with a > + ** dummy. This can happen when BC_IFUNCF is trying to grow the stack. > + */ > + L->top = L->base; > + setframe_gc(L->base - 1 - LJ_FR2, obj2gco(L), LJ_TTHREAD); > + } > + } > + if (L->stacksize <= LJ_STACK_MAXEX) { > + /* An error handler might want to inspect the stack overflow error, but > + ** will need some stack space to run in. We give it a stack size beyond > + ** the normal limit in order to do so, then rely on lj_state_relimitstack > + ** calls during unwinding to bring us back to a convential stack size. > + ** The + 1 is space for the error message, and 2 * LUA_MINSTACK is for > + ** the lj_state_checkstack() call in lj_err_run(). > + */ > + resizestack(L, LJ_STACK_MAX + 1 + 2 * LUA_MINSTACK); > + lj_err_stkov(L); /* May invoke an error handler. */ > + } else { > + /* If we're here, then the stack overflow error handler is requesting > + ** to grow the stack even further. We have no choice but to abort the > + ** error handler. > + */ > + GCstr *em = lj_err_str(L, LJ_ERR_STKOV); /* Might OOM. */ > + setstrV(L, L->top++, em); /* There is always space to push an error. */ > lj_err_throw(L, LUA_ERRERR); /* Does not invoke an error handler. */ > - /* 1. We are _at_ the limit after the last growth. */ > - if (L->status < LUA_ERRRUN) { /* 2. Throw 'stack overflow'. */ > - L->status = LUA_ERRRUN; /* Prevent ending here again for pushed msg. */ > - lj_err_msg(L, LJ_ERR_STKOV); /* May invoke an error handler. */ > } > - /* 3. Add space (over the limit) for pushed message and error handler. */ > - } > - n = L->stacksize + need; > - if (n > LJ_STACK_MAX) { > - n += 2*LUA_MINSTACK; > - } else if (n < 2*L->stacksize) { > - n = 2*L->stacksize; > - if (n >= LJ_STACK_MAX) > - n = LJ_STACK_MAX; > } > - resizestack(L, n); > } > > void LJ_FASTCALL lj_state_growstack1(lua_State *L) > diff --git a/test/LuaJIT-tests/lang/stackov.lua b/test/LuaJIT-tests/lang/stackov.lua > index 8afa86b4..b052ad80 100644 > --- a/test/LuaJIT-tests/lang/stackov.lua > +++ b/test/LuaJIT-tests/lang/stackov.lua I don't get why we need this change. With reverted patch this test is passed. > @@ -31,13 +31,17 @@ end > do --- Base test. > local err, s = xpcall(f, debug.traceback) > assert(err == false) > - test_error_msg(f, s) > + -- There is no place on the stack to invoke the handler. > + -- Just test the error reason. > + assert(string.match(s, "stack overflow")) > end > > do --- Stack overflow with non-empty arg list. > local err, s = xpcall(g, debug.traceback, 1) > assert(err == false) > - test_error_msg(g, s) > + -- There is no place on the stack to invoke the handler. > + -- Just test the error reason. > + assert(string.match(s, "stack overflow")) > end > > do --- Vararg tail call with non-empty arg list. +slow > diff --git a/test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua b/test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua > new file mode 100644 > index 00000000..08e344de > --- /dev/null > +++ b/test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua > @@ -0,0 +1,79 @@ > +local tap = require('tap') > +local allocinject = require('allocinject') > + > +local test = tap.test('lj-1152-stack-buffer-overflow-on-error') > +test:plan(6) > + > +local LJ_MAX_LOCVAR = 200 > + > +-- Generate the following Lua chunk: > +-- local function recursive_f() > +-- local _ > +-- ... > +-- recursive_f() > +-- end > +-- return recursive_f > +local function generate_recursive_f(n_locals) > + local chunk = 'local function recursive_f()\n' > + for _ = 1, n_locals do > + chunk = chunk .. 'local _\n' > + end > + chunk = chunk .. [[ > + recursive_f() > + end > + return recursive_f > + ]] > + local f = assert(loadstring(chunk)) > + return f() > +end > + > +-- Use `coroutine.wrap()` for functions to use newly created stack > +-- with fixed number of stack slots. > + > +-- Check that we still got the correct error message in case of > +-- the unsafe error handler function. > +coroutine.wrap(function() > + -- XXX: Use different recursive functions as callee and handler > + -- to be sure to get the invalid stack value instead of `nil`. > + local function recursive() recursive() end > + local function bad_errfunc() bad_errfunc() end > + local r, e = xpcall(recursive, bad_errfunc) > + -- XXX: Don't create a constant string that is anchored to the > + -- prototype. It is necessary to make the error message freed by > + -- the GC and OOM raising in the last test. > + local EXPECTED_MSG = 'stack ' .. 'overflow' > +test:ok(not r, 'correct status') > +test:like(e, EXPECTED_MSG, 'correct error message') > +end)() > + > +coroutine.wrap(function() > + -- Collect all strings including the possibly-existed string > + -- with the 'stack overflow' error message. > + collectgarbage() > + local function recursive() recursive() end > + -- Avoid trace recording. A trace can't be allocated anyway. > + jit.off(recursive) > + > + -- Check the case when the error > + allocinject.enable_null_alloc() > + local r, e = pcall(recursive) > + allocinject.disable() > + > +test:ok(not r, 'correct status') > +test:like(e, 'not enough memory', 'correct error message') > +end)() > + > +-- Check overflow of the buffer related to the Lua stack. > +-- This test fails under ASAN without the patch. > +local recursive_f = generate_recursive_f(LJ_MAX_LOCVAR) > +coroutine.wrap(function() > + local r, e = pcall(recursive_f) > +test:ok(not r, 'correct status') > + -- XXX: Don't create a constant string that is anchored to the > + -- prototype. It is necessary to make the error message freed by > + -- the GC and OOM raising in the last test. > + local EXPECTED_MSG = 'stack ' .. 'overflow' > +test:like(e, EXPECTED_MSG, 'correct error message') > +end)() > + > +test:done(true) > diff --git a/test/tarantool-tests/utils/CMakeLists.txt b/test/tarantool-tests/utils/CMakeLists.txt > index 15871934..e74e184b 100644 > --- a/test/tarantool-tests/utils/CMakeLists.txt > +++ b/test/tarantool-tests/utils/CMakeLists.txt > @@ -1,4 +1,5 @@ > list(APPEND tests > + lj-1152-stack-buffer-overflow-on-error.test.lua > lj-1166-error-stitch-oom-ir-buff.test.lua > lj-1166-error-stitch-oom-snap-buff.test.lua > lj-1247-fin-tab-rehashing-on-trace.test.lua --------------zYs8WMATaS63qkFIkC4WsZtu Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit

Hello, Sergey,

thanks for the patch! See my comments below.

Sergey

On 4/25/25 16:42, Sergey Kaplun wrote:
From: Mike Pall <mike>

Reported by pwnhacker0x18. Fixed by Peter Cawley.

(cherry picked from commit defe61a56751a0db5f00ff3ab7b8f45436ba74c8)

In case of the Lua stack overflow error, LuaJIT restores the `L->top`
value and pushes the error message above. It is possible that the
restored value is greater than `L->maxstack`, so pushing the error
message causes dirty write out-of-bounds.

This patch prevents it by overwriting stack overflow handling machinery.
Now, in the aforementioned case, the last frame is replaced with a dummy
frame to avoid dirty writes.

Sergey Kaplun:
* added the description and the test for the problem

Part of tarantool/tarantool#11278
---

Related issues:
* https://github.com/LuaJIT/LuaJIT/issues/1152
* https://github.com/tarantool/tarantool/issues/11278
Branch: https://github.com/tarantool/luajit/tree/skaplun/lj-1152-stack-buffer-overflow-on-error

The CI is red due to CMake minimum required version in c-dt module in
Tarantool.
These problems are already fixed, please re-push the branch.

The cherry-pick also included a part fixed in the merge commit:
https://github.com/LuaJIT/LuaJIT/commit/0d313b243

 src/lj_debug.c                                |  1 +
 src/lj_err.c                                  | 23 +++++-
 src/lj_err.h                                  |  1 +
 src/lj_state.c                                | 58 +++++++++-----
 test/LuaJIT-tests/lang/stackov.lua            |  8 +-
 ...52-stack-buffer-overflow-on-error.test.lua | 79 +++++++++++++++++++
 test/tarantool-tests/utils/CMakeLists.txt     |  1 +
 7 files changed, 148 insertions(+), 23 deletions(-)
 create mode 100644 test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua

diff --git a/src/lj_debug.c b/src/lj_debug.c
index 107f464c..76e48aca 100644
--- a/src/lj_debug.c
+++ b/src/lj_debug.c
@@ -64,6 +64,7 @@ static BCPos debug_framepc(lua_State *L, GCfunc *fn, cTValue *nextframe)
     if (cf == NULL || (char *)cframe_pc(cf) == (char *)cframe_L(cf))
       return NO_BCPOS;
     ins = cframe_pc(cf);  /* Only happens during error/hook handling. */
+    if (!ins) return NO_BCPOS;
   } else {
     if (frame_islua(nextframe)) {
       ins = frame_pc(nextframe);
diff --git a/src/lj_err.c b/src/lj_err.c
index 1a9a2f2b..80dca847 100644
--- a/src/lj_err.c
+++ b/src/lj_err.c
@@ -784,7 +784,14 @@ LJ_NOINLINE void lj_err_mem(lua_State *L)
     TValue *base = tvref(G(L)->jit_base);
     if (base) L->base = base;
   }
-  if (curr_funcisL(L)) L->top = curr_topL(L);
+  if (curr_funcisL(L)) {
+    L->top = curr_topL(L);
+    if (LJ_UNLIKELY(L->top > tvref(L->maxstack))) {
+      /* The current Lua frame violates the stack. Replace it with a dummy. */
+      L->top = L->base;
+      setframe_gc(L->base - 1 - LJ_FR2, obj2gco(L), LJ_TTHREAD);
+    }
+  }
   setstrV(L, L->top++, lj_err_str(L, LJ_ERR_ERRMEM));
   lj_err_throw(L, LUA_ERRMEM);
 }
@@ -845,9 +852,11 @@ LJ_NOINLINE void LJ_FASTCALL lj_err_run(lua_State *L)
 {
   ptrdiff_t ef = (LJ_HASJIT && tvref(G(L)->jit_base)) ? 0 : finderrfunc(L);
   if (ef) {
-    TValue *errfunc = restorestack(L, ef);
-    TValue *top = L->top;
+    TValue *errfunc, *top;
+    lj_state_checkstack(L, LUA_MINSTACK * 2);  /* Might raise new error. */
     lj_trace_abort(G(L));
+    errfunc = restorestack(L, ef);
+    top = L->top;
     if (!tvisfunc(errfunc) || L->status == LUA_ERRERR) {
       setstrV(L, top-1, lj_err_str(L, LJ_ERR_ERRERR));
       lj_err_throw(L, LUA_ERRERR);
@@ -862,7 +871,15 @@ LJ_NOINLINE void LJ_FASTCALL lj_err_run(lua_State *L)
   lj_err_throw(L, LUA_ERRRUN);
 }
 
+/* Stack overflow error. */
+void LJ_FASTCALL lj_err_stkov(lua_State *L)
+{
+  lj_debug_addloc(L, err2msg(LJ_ERR_STKOV), L->base-1, NULL);
+  lj_err_run(L);
+}
+
 #if LJ_HASJIT
+/* Rethrow error after doing a trace exit. */
 LJ_NOINLINE void LJ_FASTCALL lj_err_trace(lua_State *L, int errcode)
 {
   if (errcode == LUA_ERRRUN)
diff --git a/src/lj_err.h b/src/lj_err.h
index b0c72c24..56bd0375 100644
--- a/src/lj_err.h
+++ b/src/lj_err.h
@@ -23,6 +23,7 @@ LJ_DATA const char *lj_err_allmsg;
 LJ_FUNC GCstr *lj_err_str(lua_State *L, ErrMsg em);
 LJ_FUNCA_NORET void LJ_FASTCALL lj_err_throw(lua_State *L, int errcode);
 LJ_FUNC_NORET void lj_err_mem(lua_State *L);
+LJ_FUNC_NORET void LJ_FASTCALL lj_err_stkov(lua_State *L);
 LJ_FUNC_NORET void LJ_FASTCALL lj_err_run(lua_State *L);
 #if LJ_HASJIT
 LJ_FUNCA_NORET void LJ_FASTCALL lj_err_trace(lua_State *L, int errcode);
diff --git a/src/lj_state.c b/src/lj_state.c
index 5a920102..053e5ec9 100644
--- a/src/lj_state.c
+++ b/src/lj_state.c
@@ -120,27 +120,49 @@ void lj_state_shrinkstack(lua_State *L, MSize used)
 /* Try to grow stack. */
 void LJ_FASTCALL lj_state_growstack(lua_State *L, MSize need)
 {
-  MSize n;
-  if (L->stacksize >= LJ_STACK_MAXEX) {
-    /* 4. Throw 'error in error handling' when we are _over_ the limit. */
-    if (L->stacksize > LJ_STACK_MAXEX)
+  MSize n = L->stacksize + need;
+  if (LJ_LIKELY(n < LJ_STACK_MAX)) {  /* The stack can grow as requested. */
+    if (n < 2 * L->stacksize) {  /* Try to double the size. */
+      n = 2 * L->stacksize;
+      if (n > LJ_STACK_MAX)
+	n = LJ_STACK_MAX;
+    }
+    resizestack(L, n);
+  } else {  /* Request would overflow. Raise a stack overflow error. */
+    if (LJ_HASJIT) {
+      TValue *base = tvref(G(L)->jit_base);
+      if (base) L->base = base;
+    }
+    if (curr_funcisL(L)) {
+      L->top = curr_topL(L);
+      if (L->top > tvref(L->maxstack)) {
+	/* The current Lua frame violates the stack, so replace it with a
+	** dummy. This can happen when BC_IFUNCF is trying to grow the stack.
+	*/
+	L->top = L->base;
+	setframe_gc(L->base - 1 - LJ_FR2, obj2gco(L), LJ_TTHREAD);
+      }
+    }
+    if (L->stacksize <= LJ_STACK_MAXEX) {
+      /* An error handler might want to inspect the stack overflow error, but
+      ** will need some stack space to run in. We give it a stack size beyond
+      ** the normal limit in order to do so, then rely on lj_state_relimitstack
+      ** calls during unwinding to bring us back to a convential stack size.
+      ** The + 1 is space for the error message, and 2 * LUA_MINSTACK is for
+      ** the lj_state_checkstack() call in lj_err_run().
+      */
+      resizestack(L, LJ_STACK_MAX + 1 + 2 * LUA_MINSTACK);
+      lj_err_stkov(L);  /* May invoke an error handler. */
+    } else {
+      /* If we're here, then the stack overflow error handler is requesting
+      ** to grow the stack even further. We have no choice but to abort the
+      ** error handler.
+      */
+      GCstr *em = lj_err_str(L, LJ_ERR_STKOV);  /* Might OOM. */
+      setstrV(L, L->top++, em);  /* There is always space to push an error. */
       lj_err_throw(L, LUA_ERRERR);  /* Does not invoke an error handler. */
-    /* 1. We are _at_ the limit after the last growth. */
-    if (L->status < LUA_ERRRUN) {  /* 2. Throw 'stack overflow'. */
-      L->status = LUA_ERRRUN;  /* Prevent ending here again for pushed msg. */
-      lj_err_msg(L, LJ_ERR_STKOV);  /* May invoke an error handler. */
     }
-    /* 3. Add space (over the limit) for pushed message and error handler. */
-  }
-  n = L->stacksize + need;
-  if (n > LJ_STACK_MAX) {
-    n += 2*LUA_MINSTACK;
-  } else if (n < 2*L->stacksize) {
-    n = 2*L->stacksize;
-    if (n >= LJ_STACK_MAX)
-      n = LJ_STACK_MAX;
   }
-  resizestack(L, n);
 }
 
 void LJ_FASTCALL lj_state_growstack1(lua_State *L)
diff --git a/test/LuaJIT-tests/lang/stackov.lua b/test/LuaJIT-tests/lang/stackov.lua
index 8afa86b4..b052ad80 100644
--- a/test/LuaJIT-tests/lang/stackov.lua
+++ b/test/LuaJIT-tests/lang/stackov.lua
I don't get why we need this change. With reverted patch this test is passed.
@@ -31,13 +31,17 @@ end
 do --- Base test.
   local err, s = xpcall(f, debug.traceback)
   assert(err == false)
-  test_error_msg(f, s)
+  -- There is no place on the stack to invoke the handler.
+  -- Just test the error reason.
+  assert(string.match(s, "stack overflow"))
 end
 
 do --- Stack overflow with non-empty arg list.
   local err, s = xpcall(g, debug.traceback, 1)
   assert(err == false)
-  test_error_msg(g, s)
+  -- There is no place on the stack to invoke the handler.
+  -- Just test the error reason.
+  assert(string.match(s, "stack overflow"))
 end
 
 do --- Vararg tail call with non-empty arg list. +slow
diff --git a/test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua b/test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua
new file mode 100644
index 00000000..08e344de
--- /dev/null
+++ b/test/tarantool-tests/lj-1152-stack-buffer-overflow-on-error.test.lua
@@ -0,0 +1,79 @@
+local tap = require('tap')
+local allocinject = require('allocinject')
+
+local test = tap.test('lj-1152-stack-buffer-overflow-on-error')
+test:plan(6)
+
+local LJ_MAX_LOCVAR = 200
+
+-- Generate the following Lua chunk:
+--   local function recursive_f()
+--     local _
+--     ...
+--     recursive_f()
+--   end
+--   return recursive_f
+local function generate_recursive_f(n_locals)
+  local chunk = 'local function recursive_f()\n'
+  for _ = 1, n_locals do
+    chunk = chunk .. 'local _\n'
+  end
+  chunk = chunk .. [[
+      recursive_f()
+    end
+    return recursive_f
+  ]]
+  local f = assert(loadstring(chunk))
+  return f()
+end
+
+-- Use `coroutine.wrap()` for functions to use newly created stack
+-- with fixed number of stack slots.
+
+-- Check that we still got the correct error message in case of
+-- the unsafe error handler function.
+coroutine.wrap(function()
+  -- XXX: Use different recursive functions as callee and handler
+  -- to be sure to get the invalid stack value instead of `nil`.
+  local function recursive() recursive() end
+  local function bad_errfunc() bad_errfunc() end
+  local r, e = xpcall(recursive, bad_errfunc)
+  -- XXX: Don't create a constant string that is anchored to the
+  -- prototype. It is necessary to make the error message freed by
+  -- the GC and OOM raising in the last test.
+  local EXPECTED_MSG = 'stack ' .. 'overflow'
+  test:ok(not r, 'correct status')
+  test:like(e, EXPECTED_MSG, 'correct error message')
+end)()
+
+coroutine.wrap(function()
+  -- Collect all strings including the possibly-existed string
+  -- with the 'stack overflow' error message.
+  collectgarbage()
+  local function recursive() recursive() end
+  -- Avoid trace recording. A trace can't be allocated anyway.
+  jit.off(recursive)
+
+  -- Check the case when the error
+  allocinject.enable_null_alloc()
+  local r, e = pcall(recursive)
+  allocinject.disable()
+
+  test:ok(not r, 'correct status')
+  test:like(e, 'not enough memory', 'correct error message')
+end)()
+
+-- Check overflow of the buffer related to the Lua stack.
+-- This test fails under ASAN without the patch.
+local recursive_f = generate_recursive_f(LJ_MAX_LOCVAR)
+coroutine.wrap(function()
+  local r, e = pcall(recursive_f)
+  test:ok(not r, 'correct status')
+  -- XXX: Don't create a constant string that is anchored to the
+  -- prototype. It is necessary to make the error message freed by
+  -- the GC and OOM raising in the last test.
+  local EXPECTED_MSG = 'stack ' .. 'overflow'
+  test:like(e, EXPECTED_MSG, 'correct error message')
+end)()
+
+test:done(true)
diff --git a/test/tarantool-tests/utils/CMakeLists.txt b/test/tarantool-tests/utils/CMakeLists.txt
index 15871934..e74e184b 100644
--- a/test/tarantool-tests/utils/CMakeLists.txt
+++ b/test/tarantool-tests/utils/CMakeLists.txt
@@ -1,4 +1,5 @@
 list(APPEND tests
+  lj-1152-stack-buffer-overflow-on-error.test.lua
   lj-1166-error-stitch-oom-ir-buff.test.lua
   lj-1166-error-stitch-oom-snap-buff.test.lua
   lj-1247-fin-tab-rehashing-on-trace.test.lua
--------------zYs8WMATaS63qkFIkC4WsZtu--