Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/lapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,10 @@ LUA_API int lua_gc (lua_State *L, int what, int data) {
LUA_API int lua_error (lua_State *L) {
lua_lock(L);
LUAI_TRY_BLOCK(L) {
const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)";
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
thrlua_log(L, DCRITICAL, "lua_error called 1: %s\n", msg);
api_checknelems(L, 1);
thrlua_log(L, DCRITICAL, "lua_error called %s\n", "end");
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
luaG_errormsg(L);
} LUAI_TRY_FINALLY(L) {
lua_unlock(L);
Expand Down
10 changes: 10 additions & 0 deletions src/ldebug.c
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,16 @@ void luaG_errormsg (lua_State *L) {
incr_top(L);
luaD_call(L, L->top - 2, 1); /* call it */
}
/* Only log when there is no error handler -- that means the
* subsequent luaD_throw will hit the panic branch. When
* L->errorJmp is set the error will be caught by pcall/xpcall
* and is part of normal control flow (e.g. lua-aws uses pcall
* for method-existence checks). */
if (!L->errorJmp) {
const char *msg = (L->top > L->base && ttisstring(L->top - 1))
? svalue(L->top - 1) : "(non-string error)";
thrlua_log(L, DCRITICAL, "luaG_errormsg: %s\n", msg);
}
luaD_throw(L, LUA_ERRRUN);
}

Expand Down
15 changes: 14 additions & 1 deletion src/ldo.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,23 @@ void luaD_throw (lua_State *L, int errcode) {
LUAI_THROW(L, L->errorJmp);
}
else {
thrlua_log(L, DCRITICAL,
"luaD_throw: no error handler, errcode=%d, L=%p, invoking panic\n",
errcode, (void *)L);
L->status = cast_byte(errcode);
if (G(L)->panic) {
resetstack(L, errcode);
lua_unlock(L);
/* Do NOT call lua_unlock(L) here. When luaD_throw is reached
* via LUAI_TRY_END re-throw, the LUAI_TRY_FINALLY block has
* already run and the lock state depends on the call site:
* some FINALLY blocks call lua_unlock (lapi.c, lauxlib.c) while
* others call lua_lock (ldo.c callhook/precall). Unconditionally
* unlocking here caused a double-unlock that corrupted the
* pthread mutex on Linux/glibc, making every subsequent
* lua_lock() on this lua_State fail with EINVAL and abort().
* All direct callers of luaD_throw (lmem.c, ldebug.c, lundump.c,
* llex.c) always have an outer errorJmp so they never reach
* this panic branch. */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Panic callback runs under Lua lock

Medium Severity

Removing lua_unlock(L) means G(L)->panic(L) now executes while the state lock may still be held. In unprotected error paths, panic handlers that call back into Lua APIs can block or fail on re-lock, changing panic behavior from prior releases.

Fix in Cursor Fix in Web

G(L)->panic(L);
}
exit(EXIT_FAILURE);
Expand Down
10 changes: 10 additions & 0 deletions src/thrlua.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,16 @@ extern void lua_do_longjmp(luai_jmpbuf env, int val)
#define LUAI_TRY_END(L) \
(L)->errorJmp = lj.previous; \
if (lj.status) { \
/* Only log when there is NO outer handler -- that is the \
* dangerous path that hits luaD_throw's panic branch. \
* Normal error propagation (with an outer handler) is \
* high-frequency and must not be logged. */ \
if (!lj.previous) { \
thrlua_log((L), DCRITICAL, \
"LUAI_TRY_END: error status=%d in TRY block at %s:%d, " \
"re-throwing with NO outer handler, L=%p\n", \
lj.status, lj.file, lj.line, (void *)(L)); \
} \
luaD_throw((L), lj.status); \
} \
} while(0)
Expand Down