mirror of
https://github.com/minetest/minetest.git
synced 2024-11-27 10:03:45 +01:00
[CSM] Improve security for client-sided mods (#5100)
This commit is contained in:
parent
92b45b2a18
commit
a50d07d39a
@ -1,6 +1,9 @@
|
|||||||
|
|
||||||
core.callback_origins = {}
|
core.callback_origins = {}
|
||||||
|
|
||||||
|
local getinfo = debug.getinfo
|
||||||
|
debug.getinfo = nil
|
||||||
|
|
||||||
function core.run_callbacks(callbacks, mode, ...)
|
function core.run_callbacks(callbacks, mode, ...)
|
||||||
assert(type(callbacks) == "table")
|
assert(type(callbacks) == "table")
|
||||||
local cb_len = #callbacks
|
local cb_len = #callbacks
|
||||||
@ -47,7 +50,7 @@ local function make_registration()
|
|||||||
t[#t + 1] = func
|
t[#t + 1] = func
|
||||||
core.callback_origins[func] = {
|
core.callback_origins[func] = {
|
||||||
mod = core.get_current_modname() or "??",
|
mod = core.get_current_modname() or "??",
|
||||||
name = debug.getinfo(1, "n").name or "??"
|
name = getinfo(1, "n").name or "??"
|
||||||
}
|
}
|
||||||
--local origin = core.callback_origins[func]
|
--local origin = core.callback_origins[func]
|
||||||
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
|
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
-- This ignores mod namespaces (variables with the same name as the current mod).
|
-- This ignores mod namespaces (variables with the same name as the current mod).
|
||||||
local WARN_INIT = false
|
local WARN_INIT = false
|
||||||
|
|
||||||
|
local getinfo = debug.getinfo
|
||||||
|
|
||||||
function core.global_exists(name)
|
function core.global_exists(name)
|
||||||
if type(name) ~= "string" then
|
if type(name) ~= "string" then
|
||||||
@ -18,7 +19,7 @@ local declared = {}
|
|||||||
local warned = {}
|
local warned = {}
|
||||||
|
|
||||||
function meta:__newindex(name, value)
|
function meta:__newindex(name, value)
|
||||||
local info = debug.getinfo(2, "Sl")
|
local info = getinfo(2, "Sl")
|
||||||
local desc = ("%s:%d"):format(info.short_src, info.currentline)
|
local desc = ("%s:%d"):format(info.short_src, info.currentline)
|
||||||
if not declared[name] then
|
if not declared[name] then
|
||||||
local warn_key = ("%s\0%d\0%s"):format(info.source,
|
local warn_key = ("%s\0%d\0%s"):format(info.source,
|
||||||
@ -42,7 +43,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
function meta:__index(name)
|
function meta:__index(name)
|
||||||
local info = debug.getinfo(2, "Sl")
|
local info = getinfo(2, "Sl")
|
||||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||||
if not declared[name] and not warned[warn_key] and info.what ~= "C" then
|
if not declared[name] and not warned[warn_key] and info.what ~= "C" then
|
||||||
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")
|
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")
|
||||||
|
@ -47,6 +47,7 @@ elseif INIT == "mainmenu" then
|
|||||||
elseif INIT == "async" then
|
elseif INIT == "async" then
|
||||||
dofile(asyncpath .. "init.lua")
|
dofile(asyncpath .. "init.lua")
|
||||||
elseif INIT == "client" then
|
elseif INIT == "client" then
|
||||||
|
os.setlocale = nil
|
||||||
dofile(clientpath .. "init.lua")
|
dofile(clientpath .. "init.lua")
|
||||||
else
|
else
|
||||||
error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
|
error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
|
||||||
|
@ -33,7 +33,7 @@ ClientScripting::ClientScripting(Client *client):
|
|||||||
SCRIPTAPI_PRECHECKHEADER
|
SCRIPTAPI_PRECHECKHEADER
|
||||||
|
|
||||||
// Security is mandatory client side
|
// Security is mandatory client side
|
||||||
initializeSecurity();
|
initializeSecurityClient();
|
||||||
|
|
||||||
lua_getglobal(L, "core");
|
lua_getglobal(L, "core");
|
||||||
int top = lua_gettop(L);
|
int top = lua_gettop(L);
|
||||||
|
@ -139,32 +139,8 @@ void ScriptApiSecurity::initializeSecurity()
|
|||||||
|
|
||||||
lua_State *L = getStack();
|
lua_State *L = getStack();
|
||||||
|
|
||||||
// Backup globals to the registry
|
|
||||||
lua_getglobal(L, "_G");
|
|
||||||
lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
|
|
||||||
|
|
||||||
// Replace the global environment with an empty one
|
int old_globals = backupGlobals(L);
|
||||||
#if LUA_VERSION_NUM <= 501
|
|
||||||
int is_main = lua_pushthread(L); // Push the main thread
|
|
||||||
FATAL_ERROR_IF(!is_main, "Security: ScriptApi's Lua state "
|
|
||||||
"isn't the main Lua thread!");
|
|
||||||
#endif
|
|
||||||
lua_newtable(L); // Create new environment
|
|
||||||
lua_pushvalue(L, -1);
|
|
||||||
lua_setfield(L, -2, "_G"); // Set _G of new environment
|
|
||||||
#if LUA_VERSION_NUM >= 502 // Lua >= 5.2
|
|
||||||
// Set the global environment
|
|
||||||
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
|
|
||||||
#else // Lua <= 5.1
|
|
||||||
// Set the environment of the main thread
|
|
||||||
FATAL_ERROR_IF(!lua_setfenv(L, -2), "Security: Unable to set "
|
|
||||||
"environment of the main Lua thread!");
|
|
||||||
lua_pop(L, 1); // Pop thread
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Get old globals
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
|
|
||||||
int old_globals = lua_gettop(L);
|
|
||||||
|
|
||||||
|
|
||||||
// Copy safe base functions
|
// Copy safe base functions
|
||||||
@ -223,7 +199,7 @@ void ScriptApiSecurity::initializeSecurity()
|
|||||||
lua_setglobal(L, "package");
|
lua_setglobal(L, "package");
|
||||||
lua_pop(L, 1); // Pop old package
|
lua_pop(L, 1); // Pop old package
|
||||||
|
|
||||||
|
#if USE_LUAJIT
|
||||||
// Copy safe jit functions, if they exist
|
// Copy safe jit functions, if they exist
|
||||||
lua_getfield(L, -1, "jit");
|
lua_getfield(L, -1, "jit");
|
||||||
if (!lua_isnil(L, -1)) {
|
if (!lua_isnil(L, -1)) {
|
||||||
@ -232,10 +208,169 @@ void ScriptApiSecurity::initializeSecurity()
|
|||||||
lua_setglobal(L, "jit");
|
lua_setglobal(L, "jit");
|
||||||
}
|
}
|
||||||
lua_pop(L, 1); // Pop old jit
|
lua_pop(L, 1); // Pop old jit
|
||||||
|
#endif
|
||||||
|
|
||||||
lua_pop(L, 1); // Pop globals_backup
|
lua_pop(L, 1); // Pop globals_backup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptApiSecurity::initializeSecurityClient()
|
||||||
|
{
|
||||||
|
static const char *whitelist[] = {
|
||||||
|
"assert",
|
||||||
|
"core",
|
||||||
|
"collectgarbage",
|
||||||
|
"DIR_DELIM",
|
||||||
|
"error",
|
||||||
|
"getfenv",
|
||||||
|
"ipairs",
|
||||||
|
"next",
|
||||||
|
"pairs",
|
||||||
|
"pcall",
|
||||||
|
"print",
|
||||||
|
"rawequal",
|
||||||
|
"rawget",
|
||||||
|
"rawset",
|
||||||
|
"select",
|
||||||
|
"setfenv",
|
||||||
|
"setmetatable",
|
||||||
|
"tonumber",
|
||||||
|
"tostring",
|
||||||
|
"type",
|
||||||
|
"unpack",
|
||||||
|
"_VERSION",
|
||||||
|
"xpcall",
|
||||||
|
// Completely safe libraries
|
||||||
|
"coroutine",
|
||||||
|
"string",
|
||||||
|
"table",
|
||||||
|
"math",
|
||||||
|
};
|
||||||
|
static const char *io_whitelist[] = {
|
||||||
|
"close",
|
||||||
|
"flush",
|
||||||
|
"read",
|
||||||
|
"type",
|
||||||
|
"write",
|
||||||
|
};
|
||||||
|
static const char *os_whitelist[] = {
|
||||||
|
"clock",
|
||||||
|
"date",
|
||||||
|
"difftime",
|
||||||
|
"time",
|
||||||
|
"setlocale",
|
||||||
|
};
|
||||||
|
static const char *debug_whitelist[] = {
|
||||||
|
"getinfo",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *jit_whitelist[] = {
|
||||||
|
"arch",
|
||||||
|
"flush",
|
||||||
|
"off",
|
||||||
|
"on",
|
||||||
|
"opt",
|
||||||
|
"os",
|
||||||
|
"status",
|
||||||
|
"version",
|
||||||
|
"version_num",
|
||||||
|
};
|
||||||
|
|
||||||
|
m_secure = true;
|
||||||
|
|
||||||
|
lua_State *L = getStack();
|
||||||
|
|
||||||
|
|
||||||
|
int old_globals = backupGlobals(L);
|
||||||
|
|
||||||
|
|
||||||
|
// Copy safe base functions
|
||||||
|
lua_getglobal(L, "_G");
|
||||||
|
copy_safe(L, whitelist, sizeof(whitelist));
|
||||||
|
|
||||||
|
// And replace unsafe ones
|
||||||
|
SECURE_API(g, dofile);
|
||||||
|
SECURE_API(g, loadstring);
|
||||||
|
SECURE_API(g, require);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
|
||||||
|
// Copy safe IO functions
|
||||||
|
lua_getfield(L, old_globals, "io");
|
||||||
|
lua_newtable(L);
|
||||||
|
copy_safe(L, io_whitelist, sizeof(io_whitelist));
|
||||||
|
|
||||||
|
// And replace unsafe ones
|
||||||
|
SECURE_API(io, open);
|
||||||
|
SECURE_API(io, input);
|
||||||
|
SECURE_API(io, output);
|
||||||
|
SECURE_API(io, lines);
|
||||||
|
|
||||||
|
lua_setglobal(L, "io");
|
||||||
|
lua_pop(L, 1); // Pop old IO
|
||||||
|
|
||||||
|
|
||||||
|
// Copy safe OS functions
|
||||||
|
lua_getfield(L, old_globals, "os");
|
||||||
|
lua_newtable(L);
|
||||||
|
copy_safe(L, os_whitelist, sizeof(os_whitelist));
|
||||||
|
lua_setglobal(L, "os");
|
||||||
|
lua_pop(L, 1); // Pop old OS
|
||||||
|
|
||||||
|
|
||||||
|
// Copy safe debug functions
|
||||||
|
lua_getfield(L, old_globals, "debug");
|
||||||
|
lua_newtable(L);
|
||||||
|
copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
|
||||||
|
lua_setglobal(L, "debug");
|
||||||
|
lua_pop(L, 1); // Pop old debug
|
||||||
|
|
||||||
|
// Remove all of package
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_setglobal(L, "package");
|
||||||
|
|
||||||
|
#if USE_LUAJIT
|
||||||
|
// Copy safe jit functions, if they exist
|
||||||
|
lua_getfield(L, -1, "jit");
|
||||||
|
if (!lua_isnil(L, -1)) {
|
||||||
|
lua_newtable(L);
|
||||||
|
copy_safe(L, jit_whitelist, sizeof(jit_whitelist));
|
||||||
|
lua_setglobal(L, "jit");
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // Pop old jit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
lua_pop(L, 1); // Pop globals_backup
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScriptApiSecurity::backupGlobals(lua_State *L)
|
||||||
|
{
|
||||||
|
// Backup globals to the registry
|
||||||
|
lua_getglobal(L, "_G");
|
||||||
|
lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
|
||||||
|
|
||||||
|
// Replace the global environment with an empty one
|
||||||
|
#if LUA_VERSION_NUM <= 501
|
||||||
|
int is_main = lua_pushthread(L); // Push the main thread
|
||||||
|
FATAL_ERROR_IF(!is_main, "Security: ScriptApi's Lua state "
|
||||||
|
"isn't the main Lua thread!");
|
||||||
|
#endif
|
||||||
|
lua_newtable(L); // Create new environment
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_setfield(L, -2, "_G"); // Set _G of new environment
|
||||||
|
#if LUA_VERSION_NUM >= 502 // Lua >= 5.2
|
||||||
|
// Set the global environment
|
||||||
|
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
|
||||||
|
#else // Lua <= 5.1
|
||||||
|
// Set the environment of the main thread
|
||||||
|
FATAL_ERROR_IF(!lua_setfenv(L, -2), "Security: Unable to set "
|
||||||
|
"environment of the main Lua thread!");
|
||||||
|
lua_pop(L, 1); // Pop thread
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Get old globals
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
|
||||||
|
return lua_gettop(L);
|
||||||
|
}
|
||||||
|
|
||||||
bool ScriptApiSecurity::isSecure(lua_State *L)
|
bool ScriptApiSecurity::isSecure(lua_State *L)
|
||||||
{
|
{
|
||||||
|
@ -41,8 +41,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
class ScriptApiSecurity : virtual public ScriptApiBase
|
class ScriptApiSecurity : virtual public ScriptApiBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
int backupGlobals(lua_State *L);
|
||||||
// Sets up security on the ScriptApi's Lua state
|
// Sets up security on the ScriptApi's Lua state
|
||||||
void initializeSecurity();
|
void initializeSecurity();
|
||||||
|
void initializeSecurityClient();
|
||||||
// Checks if the Lua state has been secured
|
// Checks if the Lua state has been secured
|
||||||
static bool isSecure(lua_State *L);
|
static bool isSecure(lua_State *L);
|
||||||
// Loads a file as Lua code safely (doesn't allow bytecode).
|
// Loads a file as Lua code safely (doesn't allow bytecode).
|
||||||
|
Loading…
Reference in New Issue
Block a user