Compare commits

...

4 Commits

Author SHA1 Message Date
Igor Munkin
96c60f6ca4
Merge d969cb9c31 into 84cb21ffaf 2025-03-11 02:56:28 +08:00
Mike Pall
84cb21ffaf REVERT: Change handling of nil value markers in template tables. 2025-03-10 02:56:07 +01:00
Mike Pall
4f2bb199fe macOS: Fix Apple hardened runtime support and put behind build option.
Reported by vanc. #1334
2025-03-10 02:53:20 +01:00
Igor Munkin
d969cb9c31
Prevent Lua VM re-entry through JIT trace.
JIT recording semantics assumes FFI calls are leaf regarding the LuaJIT
VM: if the execution exited Lua world through FFI machinery it is not
re-entering Lua world again.

However, there is a way to break this assumption via FFI: one can
re-enter LuaJIT VM via Lua C API used within the particular C routine
called via FFI. As a result the following host stack mix is created:
| Lua-FFI -> C routine -> Lua-C API -> Lua VM

This sort of re-entrancy is not supported by LuaJIT tracing compiler.
@mraleph named such kind of the call stack an "FFI sandwich" in the
tarantool/tarantool#4427.

This changeset introduces the mechanism for Lua-C API callbacks similar
to the one implemented for Lua-FFI: trace recording is aborted when the
execution re-enters LuaJIT VM. If re-enter is detected while running the
particular mcode, the runtime finishes its execution with EXIT_FAILURE
code and calls panic routine prior to the exit.

Co-authored-by: Vyacheslav Egorov <vegorov@google.com>
Co-authored-by: Sergey Ostanevich <sergos@tarantool.org>
Signed-off-by: Igor Munkin <imun@cpan.org>
2024-02-21 19:30:34 +03:00
10 changed files with 53 additions and 26 deletions

View File

@ -92,6 +92,18 @@ static GCtab *getcurrenv(lua_State *L)
return fn->c.gct == ~LJ_TFUNC ? tabref(fn->c.env) : tabref(L->env);
}
static void check_vm_sandwich(lua_State *L)
{
global_State *g = G(L);
/* Forbid Lua world re-entry while running the trace */
if (tvref(g->jit_base)) {
setstrV(L, L->top++, lj_err_str(L, LJ_ERR_JITREVM));
if (g->panic) g->panic(L);
exit(EXIT_FAILURE);
}
lj_trace_abort(g); /* Never record across Lua VM entrance */
}
/* -- Miscellaneous API functions ----------------------------------------- */
LUA_API int lua_status(lua_State *L)
@ -318,6 +330,7 @@ LUA_API int lua_equal(lua_State *L, int idx1, int idx2)
return (int)(uintptr_t)base;
} else {
L->top = base+2;
check_vm_sandwich(L);
lj_vm_call(L, base, 1+1);
L->top -= 2+LJ_FR2;
return tvistruecond(L->top+1+LJ_FR2);
@ -341,6 +354,7 @@ LUA_API int lua_lessthan(lua_State *L, int idx1, int idx2)
return (int)(uintptr_t)base;
} else {
L->top = base+2;
check_vm_sandwich(L);
lj_vm_call(L, base, 1+1);
L->top -= 2+LJ_FR2;
return tvistruecond(L->top+1+LJ_FR2);
@ -786,6 +800,7 @@ LUA_API void lua_concat(lua_State *L, int n)
}
n -= (int)(L->top - (top - 2*LJ_FR2));
L->top = top+2;
check_vm_sandwich(L);
lj_vm_call(L, top, 1+1);
L->top -= 1+LJ_FR2;
copyTV(L, L->top-1, L->top+LJ_FR2);
@ -805,6 +820,7 @@ LUA_API void lua_gettable(lua_State *L, int idx)
cTValue *v = lj_meta_tget(L, t, L->top-1);
if (v == NULL) {
L->top += 2;
check_vm_sandwich(L);
lj_vm_call(L, L->top-2, 1+1);
L->top -= 2+LJ_FR2;
v = L->top+1+LJ_FR2;
@ -820,6 +836,7 @@ LUA_API void lua_getfield(lua_State *L, int idx, const char *k)
v = lj_meta_tget(L, t, &key);
if (v == NULL) {
L->top += 2;
check_vm_sandwich(L);
lj_vm_call(L, L->top-2, 1+1);
L->top -= 2+LJ_FR2;
v = L->top+1+LJ_FR2;
@ -978,6 +995,7 @@ LUA_API void lua_settable(lua_State *L, int idx)
TValue *base = L->top;
copyTV(L, base+2, base-3-2*LJ_FR2);
L->top = base+3;
check_vm_sandwich(L);
lj_vm_call(L, base, 0+1);
L->top -= 3+LJ_FR2;
}
@ -998,6 +1016,7 @@ LUA_API void lua_setfield(lua_State *L, int idx, const char *k)
TValue *base = L->top;
copyTV(L, base+2, base-3-2*LJ_FR2);
L->top = base+3;
check_vm_sandwich(L);
lj_vm_call(L, base, 0+1);
L->top -= 2+LJ_FR2;
}
@ -1130,6 +1149,7 @@ LUA_API void lua_call(lua_State *L, int nargs, int nresults)
lj_checkapi(L->status == LUA_OK || L->status == LUA_ERRERR,
"thread called in wrong state %d", L->status);
lj_checkapi_slot(nargs+1);
check_vm_sandwich(L);
lj_vm_call(L, api_call_base(L, nargs), nresults+1);
}
@ -1148,6 +1168,7 @@ LUA_API int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc)
cTValue *o = index2adr_stack(L, errfunc);
ef = savestack(L, o);
}
check_vm_sandwich(L);
status = lj_vm_pcall(L, api_call_base(L, nargs), nresults+1, ef);
if (status) hook_restore(g, oldh);
return status;
@ -1176,6 +1197,7 @@ LUA_API int lua_cpcall(lua_State *L, lua_CFunction func, void *ud)
int status;
lj_checkapi(L->status == LUA_OK || L->status == LUA_ERRERR,
"thread called in wrong state %d", L->status);
check_vm_sandwich(L);
status = lj_vm_cpcall(L, func, ud, cpcall);
if (status) hook_restore(g, oldh);
return status;
@ -1188,6 +1210,7 @@ LUALIB_API int luaL_callmeta(lua_State *L, int idx, const char *field)
if (LJ_FR2) setnilV(top++);
copyTV(L, top++, index2adr(L, idx));
L->top = top;
check_vm_sandwich(L);
lj_vm_call(L, top-1, 1+1);
return 1;
}

View File

@ -179,7 +179,7 @@ static const void *bcread_varinfo(GCproto *pt)
}
/* Read a single constant key/value of a template table. */
static void bcread_ktabk(LexState *ls, TValue *o, GCtab *t)
static void bcread_ktabk(LexState *ls, TValue *o)
{
MSize tp = bcread_uleb128(ls);
if (tp >= BCDUMP_KTAB_STR) {
@ -191,8 +191,6 @@ static void bcread_ktabk(LexState *ls, TValue *o, GCtab *t)
} else if (tp == BCDUMP_KTAB_NUM) {
o->u32.lo = bcread_uleb128(ls);
o->u32.hi = bcread_uleb128(ls);
} else if (tp == BCDUMP_KTAB_NIL) { /* Restore nil value marker. */
settabV(ls->L, o, t);
} else {
lj_assertLS(tp <= BCDUMP_KTAB_TRUE, "bad constant type %d", tp);
setpriV(o, ~tp);
@ -209,15 +207,15 @@ static GCtab *bcread_ktab(LexState *ls)
MSize i;
TValue *o = tvref(t->array);
for (i = 0; i < narray; i++, o++)
bcread_ktabk(ls, o, t);
bcread_ktabk(ls, o);
}
if (nhash) { /* Read hash entries. */
MSize i;
for (i = 0; i < nhash; i++) {
TValue key;
bcread_ktabk(ls, &key, t);
bcread_ktabk(ls, &key);
lj_assertLS(!tvisnil(&key), "nil key");
bcread_ktabk(ls, lj_tab_set(ls->L, t, &key), t);
bcread_ktabk(ls, lj_tab_set(ls->L, t, &key));
}
}
return t;

View File

@ -71,8 +71,6 @@ static void bcwrite_ktabk(BCWriteCtx *ctx, cTValue *o, int narrow)
*p++ = BCDUMP_KTAB_NUM;
p = lj_strfmt_wuleb128(p, o->u32.lo);
p = lj_strfmt_wuleb128(p, o->u32.hi);
} else if (tvistab(o)) { /* Write the nil value marker as a nil. */
*p++ = BCDUMP_KTAB_NIL;
} else {
lj_assertBCW(tvispri(o), "unhandled type %d", itype(o));
*p++ = BCDUMP_KTAB_NIL+~itype(o);
@ -135,7 +133,7 @@ static void bcwrite_ktab_sorted_hash(BCWriteCtx *ctx, Node *node, MSize nhash)
TValue **heap = ctx->heap;
MSize i = nhash;
for (;; node--) { /* Build heap. */
if (!tvisnil(&node->val)) {
if (!tvisnil(&node->key)) {
bcwrite_ktabk_heap_insert(heap, --i, nhash, &node->key);
if (i == 0) break;
}
@ -165,7 +163,7 @@ static void bcwrite_ktab(BCWriteCtx *ctx, char *p, const GCtab *t)
MSize i, hmask = t->hmask;
Node *node = noderef(t->node);
for (i = 0; i <= hmask; i++)
nhash += !tvisnil(&node[i].val);
nhash += !tvisnil(&node[i].key);
}
/* Write number of array slots and hash slots. */
p = lj_strfmt_wuleb128(p, narray);
@ -186,7 +184,7 @@ static void bcwrite_ktab(BCWriteCtx *ctx, char *p, const GCtab *t)
} else {
MSize i = nhash;
for (;; node--)
if (!tvisnil(&node->val)) {
if (!tvisnil(&node->key)) {
bcwrite_ktabk(ctx, &node->key, 0);
bcwrite_ktabk(ctx, &node->val, 1);
if (--i == 0) break;

View File

@ -263,7 +263,7 @@ static void *callback_mcode_init(global_State *g, uint32_t *page)
#endif
/* Check for macOS hardened runtime. */
#if LUAJIT_SECURITY_MCODE != 0 && defined(MAP_JIT) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 110000
#if defined(LUAJIT_ENABLE_OSX_HRT) && LUAJIT_SECURITY_MCODE != 0 && defined(MAP_JIT) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 110000
#include <pthread.h>
#define CCMAP_CREATE MAP_JIT
#else

View File

@ -110,6 +110,7 @@ ERRDEF(NOJIT, "no JIT compiler for this architecture (yet)")
ERRDEF(NOJIT, "JIT compiler permanently disabled by build option")
#endif
ERRDEF(JITOPT, "unknown or malformed optimization flag " LUA_QS)
ERRDEF(JITREVM, "Lua VM re-entry is detected while executing the trace")
/* Lexer/parser errors. */
ERRDEF(XMODE, "attempt to load chunk with wrong mode")

View File

@ -99,7 +99,7 @@ static int mcode_setprot(void *p, size_t sz, DWORD prot)
#endif
/* Check for macOS hardened runtime. */
#if LUAJIT_SECURITY_MCODE != 0 && defined(MAP_JIT) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 110000
#if defined(LUAJIT_ENABLE_OSX_HRT) && LUAJIT_SECURITY_MCODE != 0 && defined(MAP_JIT) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 110000
#include <pthread.h>
#define MCMAP_CREATE MAP_JIT
#else
@ -111,6 +111,8 @@ static int mcode_setprot(void *p, size_t sz, DWORD prot)
#define MCPROT_RWX (PROT_READ|PROT_WRITE|PROT_EXEC)
#ifdef PROT_MPROTECT
#define MCPROT_CREATE (PROT_MPROTECT(MCPROT_RWX))
#elif MCMAP_CREATE
#define MCPROT_CREATE PROT_EXEC
#else
#define MCPROT_CREATE 0
#endif

View File

@ -2217,11 +2217,9 @@ LJFOLD(HREF TDUP KNUM)
LJFOLDF(fwd_href_tdup)
{
TValue keyv;
cTValue *val;
lj_ir_kvalue(J->L, &keyv, fright);
val = lj_tab_get(J->L, ir_ktab(IR(fleft->op1)), &keyv);
/* Check for either nil or the nil value marker in the template table. */
if ((tvisnil(val) || tvistab(val)) && lj_opt_fwd_href_nokey(J))
if (lj_tab_get(J->L, ir_ktab(IR(fleft->op1)), &keyv) == niltvg(J2G(J)) &&
lj_opt_fwd_href_nokey(J))
return lj_ir_kkptr(J, niltvg(J2G(J)));
return NEXTFOLD;
}

View File

@ -233,9 +233,7 @@ static TRef fwd_ahload(jit_State *J, IRRef xref)
return lj_ir_knum_u64(J, tv->u64);
else if (tvisint(tv))
return lj_ir_kint(J, intV(tv));
else if (tvistab(tv)) /* Template table nil value marker. */
return TREF_NIL;
else if (tvisstr(tv))
else if (tvisgcv(tv))
return lj_ir_kstr(J, strV(tv));
}
/* Othwerwise: don't intern as a constant. */

View File

@ -1725,7 +1725,7 @@ static void expr_table(LexState *ls, ExpDesc *e)
FuncState *fs = ls->fs;
BCLine line = ls->linenumber;
GCtab *t = NULL;
int vcall = 0, needarr = 0;
int vcall = 0, needarr = 0, fixt = 0;
uint32_t narr = 1; /* First array index. */
uint32_t nhash = 0; /* Number of hash entries. */
BCReg freg = fs->freereg;
@ -1769,10 +1769,9 @@ static void expr_table(LexState *ls, ExpDesc *e)
lj_gc_anybarriert(fs->L, t);
if (expr_isk_nojump(&val)) { /* Add const key/value to template table. */
expr_kvalue(fs, v, &val);
/* Mark nil value with table value itself to preserve the key. */
if (key.k == VKSTR && tvisnil(v)) settabV(fs->L, v, t);
} else { /* Preserve the key for the following non-const store. */
settabV(fs->L, v, t);
} else { /* Otherwise create dummy string key (avoids lj_tab_newkey). */
settabV(fs->L, v, t); /* Preserve key with table itself as value. */
fixt = 1; /* Fix this later, after all resizes. */
goto nonconst;
}
} else {
@ -1814,6 +1813,17 @@ static void expr_table(LexState *ls, ExpDesc *e)
} else {
if (needarr && t->asize < narr)
lj_tab_reasize(fs->L, t, narr-1);
if (fixt) { /* Fix value for dummy keys in template table. */
Node *node = noderef(t->node);
uint32_t i, hmask = t->hmask;
for (i = 0; i <= hmask; i++) {
Node *n = &node[i];
if (tvistab(&n->val)) {
lj_assertFS(tabV(&n->val) == t, "bad dummy key in template table");
setnilV(&n->val); /* Turn value into nil. */
}
}
}
lj_gc_check(fs->L);
}
}

View File

@ -194,7 +194,6 @@ GCtab * LJ_FASTCALL lj_tab_dup(lua_State *L, const GCtab *kt)
Node *next = nextnode(kn);
/* Don't use copyTV here, since it asserts on a copy of a dead key. */
n->val = kn->val; n->key = kn->key;
if (tvistab(&n->val)) setnilV(&n->val); /* Replace nil value marker. */
setmref(n->next, next == NULL? next : (Node *)((char *)next + d));
}
}