From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtpng3.m.smailru.net (smtpng3.m.smailru.net [94.100.177.149]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 227564696C5 for ; Wed, 15 Apr 2020 03:41:34 +0300 (MSK) From: Igor Munkin Date: Wed, 15 Apr 2020 03:34:26 +0300 Message-Id: <09d6cfaa4373079feaa26659aef609365d211d83.1586906667.git.imun@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH v2 luajit 2/3] jit: abort trace recording and execution for C API List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Vladislav Shpilevoy , Sergey Ostanevich Cc: tarantool-patches@dev.tarantool.org JIT recording semantics assumes FFI calls are atomic regarding the LuaJIT VM: if the execution exited Lua world through FFI it is not re-entering Lua world again. However, there are ways to break this atomicity via FFI: one can re-enter LuaJIT VM via Lua C API inside the C routine called via FFI. As a result the following stack mix is created: | Lua-FFI -> C routine -> Lua-C API -> Lua VM This sort of re-entrancy is explicitly not supported by LuaJIT compiler machinery. @mraleph named such layout an "FFI sandwich" and I'm strictly against eating it. E.g. there is a trigger machinery in Tarantool calling Lua functions from C space. As a result of FFI function call with a trigger underneath the "FFI sandwich" is created. This changeset introduces the mechanism for Lua-C API callbacks similar to the one existing for Lua-FFI: trace recording is aborted when the execution re-enters LuaJIT VM. If re-entrancy is detected while running mcode the platform finishes its execution with EXIT_FAILURE code and calls panic routine prior to the exit. Relates to tarantool/tarantool#4427 Co-authored-by: Vyacheslav Egorov Co-authored-by: Sergey Ostanevich Reviewed-by: Sergey Ostanevich Signed-off-by: Igor Munkin --- src/lj_api.c | 44 ++++++++++++++---- src/lj_errmsg.h | 1 + test/gh-4427-ffi-sandwich.skipcond | 7 +++ test/gh-4427-ffi-sandwich.test.lua | 49 ++++++++++++++++++++ test/gh-4427-ffi-sandwich/CMakeLists.txt | 1 + test/gh-4427-ffi-sandwich/libsandwich.c | 59 ++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 test/gh-4427-ffi-sandwich.skipcond create mode 100755 test/gh-4427-ffi-sandwich.test.lua create mode 100644 test/gh-4427-ffi-sandwich/CMakeLists.txt create mode 100644 test/gh-4427-ffi-sandwich/libsandwich.c diff --git a/src/lj_api.c b/src/lj_api.c index a5e2465..c9b5d22 100644 --- a/src/lj_api.c +++ b/src/lj_api.c @@ -76,6 +76,18 @@ static GCtab *getcurrenv(lua_State *L) return fn->c.gct == ~LJ_TFUNC ? tabref(fn->c.env) : tabref(L->env); } +static void jit_secure_call(lua_State *L, TValue *base, int nres) { + global_State *g = G(L); + /* Forbid Lua world re-entrancy while running the trace */ + if (tvref(g->jit_base)) { + setstrV(L, L->top++, lj_err_str(L, LJ_ERR_JITCALL)); + if (g->panic) g->panic(L); + exit(EXIT_FAILURE); + } + lj_trace_abort(g); /* Never record across Lua VM entrance */ + lj_vm_call(L, base, nres); +} + /* -- Miscellaneous API functions ----------------------------------------- */ LUA_API int lua_status(lua_State *L) @@ -300,7 +312,7 @@ LUA_API int lua_equal(lua_State *L, int idx1, int idx2) return (int)(uintptr_t)base; } else { L->top = base+2; - lj_vm_call(L, base, 1+1); + jit_secure_call(L, base, 1+1); L->top -= 2+LJ_FR2; return tvistruecond(L->top+1+LJ_FR2); } @@ -323,7 +335,7 @@ LUA_API int lua_lessthan(lua_State *L, int idx1, int idx2) return (int)(uintptr_t)base; } else { L->top = base+2; - lj_vm_call(L, base, 1+1); + jit_secure_call(L, base, 1+1); L->top -= 2+LJ_FR2; return tvistruecond(L->top+1+LJ_FR2); } @@ -775,7 +787,7 @@ LUA_API void lua_concat(lua_State *L, int n) } n -= (int)(L->top - top); L->top = top+2; - lj_vm_call(L, top, 1+1); + jit_secure_call(L, top, 1+1); L->top -= 1+LJ_FR2; copyTV(L, L->top-1, L->top+LJ_FR2); } while (--n > 0); @@ -795,7 +807,7 @@ LUA_API void lua_gettable(lua_State *L, int idx) v = lj_meta_tget(L, t, L->top-1); if (v == NULL) { L->top += 2; - lj_vm_call(L, L->top-2, 1+1); + jit_secure_call(L, L->top-2, 1+1); L->top -= 2+LJ_FR2; v = L->top+1+LJ_FR2; } @@ -811,7 +823,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; - lj_vm_call(L, L->top-2, 1+1); + jit_secure_call(L, L->top-2, 1+1); L->top -= 2+LJ_FR2; v = L->top+1+LJ_FR2; } @@ -966,7 +978,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; - lj_vm_call(L, base, 0+1); + jit_secure_call(L, base, 0+1); L->top -= 3+LJ_FR2; } } @@ -987,7 +999,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; - lj_vm_call(L, base, 0+1); + jit_secure_call(L, base, 0+1); L->top -= 2+LJ_FR2; } } @@ -1118,7 +1130,7 @@ LUA_API void lua_call(lua_State *L, int nargs, int nresults) { api_check(L, L->status == LUA_OK || L->status == LUA_ERRERR); api_checknelems(L, nargs+1); - lj_vm_call(L, api_call_base(L, nargs), nresults+1); + jit_secure_call(L, api_call_base(L, nargs), nresults+1); } LUA_API int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc) @@ -1136,6 +1148,13 @@ LUA_API int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc) api_checkvalidindex(L, o); ef = savestack(L, o); } + /* Forbid Lua world re-entrancy while running the trace */ + if (tvref(g->jit_base)) { + setstrV(L, L->top++, lj_err_str(L, LJ_ERR_JITCALL)); + if (g->panic) g->panic(L); + exit(EXIT_FAILURE); + } + lj_trace_abort(g); /* Never record across Lua VM entrance */ status = lj_vm_pcall(L, api_call_base(L, nargs), nresults+1, ef); if (status) hook_restore(g, oldh); return status; @@ -1160,6 +1179,13 @@ LUA_API int lua_cpcall(lua_State *L, lua_CFunction func, void *ud) uint8_t oldh = hook_save(g); int status; api_check(L, L->status == LUA_OK || L->status == LUA_ERRERR); + /* Forbid Lua world re-entrancy while running the trace */ + if (tvref(g->jit_base)) { + setstrV(L, L->top++, lj_err_str(L, LJ_ERR_JITCALL)); + if (g->panic) g->panic(L); + exit(EXIT_FAILURE); + } + lj_trace_abort(g); /* Never record across Lua VM entrance */ status = lj_vm_cpcall(L, func, ud, cpcall); if (status) hook_restore(g, oldh); return status; @@ -1172,7 +1198,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; - lj_vm_call(L, top-1, 1+1); + jit_secure_call(L, top-1, 1+1); return 1; } return 0; diff --git a/src/lj_errmsg.h b/src/lj_errmsg.h index 060a9f8..1580385 100644 --- a/src/lj_errmsg.h +++ b/src/lj_errmsg.h @@ -112,6 +112,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(JITCALL, "Lua VM re-entrancy is detected while executing the trace") /* Lexer/parser errors. */ ERRDEF(XMODE, "attempt to load chunk with wrong mode") diff --git a/test/gh-4427-ffi-sandwich.skipcond b/test/gh-4427-ffi-sandwich.skipcond new file mode 100644 index 0000000..2a2ec4d --- /dev/null +++ b/test/gh-4427-ffi-sandwich.skipcond @@ -0,0 +1,7 @@ +import platform + +# Disabled on FreeBSD due to #4819. +if platform.system() == 'FreeBSD': + self.skip = 1 + +# vim: set ft=python: diff --git a/test/gh-4427-ffi-sandwich.test.lua b/test/gh-4427-ffi-sandwich.test.lua new file mode 100755 index 0000000..9d5e50f --- /dev/null +++ b/test/gh-4427-ffi-sandwich.test.lua @@ -0,0 +1,49 @@ +#!/usr/bin/env tarantool + +if #arg == 0 then + require('utils').selfrun(arg, { + { + arg = { + 1, -- hotloop (arg[1]) + 1, -- trigger (arg[2]) + }, + res = tostring(3), -- hotloop + trigger + 1 + msg = 'Trace is aborted', + }, + { + arg = { + 1, -- hotloop (arg[1]) + 2, -- trigger (arg[2]) + }, + res = 'Lua VM re-entrancy is detected while executing the trace', + msg = 'Trace is recorded', + }, + }) +end + +local cfg = { + hotloop = arg[1] or 1, + trigger = arg[2] or 1, +} + +local ffi = require('ffi') +local ffisandwich = ffi.load('libsandwich') +ffi.cdef('int increment(struct sandwich *state, int i)') + +-- Save the current coroutine and set the value to trigger +-- call the Lua routine instead of C implementation. +local sandwich = require('libsandwich')(cfg.trigger) + +-- Depending on trigger and hotloop values the following contexts +-- are possible: +-- * if trigger <= hotloop -> trace recording is aborted +-- * if trigger > hotloop -> trace is recorded but execution +-- leads to panic +jit.opt.start("3", string.format("hotloop=%d", cfg.hotloop)) + +local res +for i = 0, cfg.trigger + cfg.hotloop do + res = ffisandwich.increment(sandwich, i) +end +-- Check the resulting value if panic didn't occur earlier. +print(res) diff --git a/test/gh-4427-ffi-sandwich/CMakeLists.txt b/test/gh-4427-ffi-sandwich/CMakeLists.txt new file mode 100644 index 0000000..995c6bb --- /dev/null +++ b/test/gh-4427-ffi-sandwich/CMakeLists.txt @@ -0,0 +1 @@ +build_lualib(libsandwich libsandwich.c) diff --git a/test/gh-4427-ffi-sandwich/libsandwich.c b/test/gh-4427-ffi-sandwich/libsandwich.c new file mode 100644 index 0000000..029758b --- /dev/null +++ b/test/gh-4427-ffi-sandwich/libsandwich.c @@ -0,0 +1,59 @@ +#include +#include + +struct sandwich { + lua_State *L; /* Coroutine saved for a Lua call */ + int ref; /* Anchor to the Lua function to be run */ + int trigger; /* Trigger for switching to Lua call */ +}; + +int increment(struct sandwich *state, int i) +{ + if (i < state->trigger) + return i + 1; + + /* Sandwich is triggered and Lua increment function is called */ + lua_pushnumber(state->L, state->ref); + lua_gettable(state->L, LUA_REGISTRYINDEX); + lua_pushnumber(state->L, i); + lua_call(state->L, 1, 1); + return lua_tonumber(state->L, -1); +} + +#define STRUCT_SANDWICH_MT "struct sandwich" + +static int init(lua_State *L) +{ + struct sandwich *state = lua_newuserdata(L, sizeof(struct sandwich)); + + luaL_getmetatable(L, STRUCT_SANDWICH_MT); + lua_setmetatable(L, -2); + + /* Lua increment function to be called when sandwich is triggered */ + if (luaL_dostring(L, "return function(i) return i + 1 end")) + luaL_error(L, "failed to translate Lua increment function"); + + state->ref = luaL_ref(L, LUA_REGISTRYINDEX); + state->L = L; + state->trigger = lua_tonumber(L, 1); + return 1; +} + +static int fin(lua_State *L) +{ + struct sandwich *state = luaL_checkudata(L, 1, STRUCT_SANDWICH_MT); + + /* Release the anchored increment function */ + luaL_unref(L, LUA_REGISTRYINDEX, state->ref); + return 0; +} + +LUA_API int luaopen_libsandwich(lua_State *L) +{ + luaL_newmetatable(L, STRUCT_SANDWICH_MT); + lua_pushcfunction(L, fin); + lua_setfield(L, -2, "__gc"); + + lua_pushcfunction(L, init); + return 1; +} -- 2.25.0