[CSM] Improve security for client-sided mods (#5100)

This commit is contained in:
red-001 2017-01-28 16:24:25 +00:00 committed by Loïc Blot
parent 92b45b2a18
commit a50d07d39a
6 changed files with 172 additions and 30 deletions

@ -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).