[CSM] Improve security for client-sided mods (#5100)
authorred-001 <red-001@outlook.ie>
Sat, 28 Jan 2017 16:24:25 +0000 (16:24 +0000)
committerLoïc Blot <nerzhul@users.noreply.github.com>
Mon, 13 Mar 2017 22:56:05 +0000 (23:56 +0100)
builtin/client/register.lua
builtin/common/strict.lua
builtin/init.lua
src/script/clientscripting.cpp
src/script/cpp_api/s_security.cpp
src/script/cpp_api/s_security.h

index 1e6ac434226fe8715c43d198f5eb20d604b32b91..c932fb9f81fdbaecce92a6208a60b3eb783d13f9 100644 (file)
@@ -1,6 +1,9 @@
 
 core.callback_origins = {}
 
+local getinfo = debug.getinfo
+debug.getinfo = nil
+
 function core.run_callbacks(callbacks, mode, ...)
        assert(type(callbacks) == "table")
        local cb_len = #callbacks
@@ -47,7 +50,7 @@ local function make_registration()
                t[#t + 1] = func
                core.callback_origins[func] = {
                        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]
                --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
index 23ba3d727a7398a9c0ad0c2f3cfe54669484c889..ccde9676b3af202bbba0f13b6b11b306a0e70287 100644 (file)
@@ -3,6 +3,7 @@
 -- This ignores mod namespaces (variables with the same name as the current mod).
 local WARN_INIT = false
 
+local getinfo = debug.getinfo
 
 function core.global_exists(name)
        if type(name) ~= "string" then
@@ -18,7 +19,7 @@ local declared = {}
 local warned = {}
 
 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)
        if not declared[name] then
                local warn_key = ("%s\0%d\0%s"):format(info.source,
@@ -42,7 +43,7 @@ end
 
 
 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)
        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")
index 590f7fa8cf8da804e4899a85005c2d8b276c0702..c9fa70fc7b086d3251ec00ac9758e5a1f45a8531 100644 (file)
@@ -47,6 +47,7 @@ elseif INIT == "mainmenu" then
 elseif INIT == "async" then
        dofile(asyncpath .. "init.lua")
 elseif INIT == "client" then
+       os.setlocale = nil
        dofile(clientpath .. "init.lua")
 else
        error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
index 37032443358867f2920b58481eba14e89e7d2542..390d21a3acedd5a326595fe989c4c67df9748688 100644 (file)
@@ -33,7 +33,7 @@ ClientScripting::ClientScripting(Client *client):
        SCRIPTAPI_PRECHECKHEADER
 
        // Security is mandatory client side
-       initializeSecurity();
+       initializeSecurityClient();
 
        lua_getglobal(L, "core");
        int top = lua_gettop(L);
index f85cd0c9c37364de6a9211be9a57da54631ed455..c6aad71b8e896321f3058de1cf8e9d5bb18b4d38 100644 (file)
@@ -139,32 +139,8 @@ void ScriptApiSecurity::initializeSecurity()
 
        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
-#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);
+       int old_globals = backupGlobals(L);
 
 
        // Copy safe base functions
@@ -223,7 +199,136 @@ void ScriptApiSecurity::initializeSecurity()
        lua_setglobal(L, "package");
        lua_pop(L, 1);  // Pop old 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
+}
+
+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)) {
@@ -232,10 +337,40 @@ void ScriptApiSecurity::initializeSecurity()
                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)
 {
index 6876108e8f6fcf2ff4fa1ff3e0ae923181f75d16..f0eef00bbdab9a77feb6aa6c7b0eaeb675ec7518 100644 (file)
@@ -41,8 +41,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 class ScriptApiSecurity : virtual public ScriptApiBase
 {
 public:
+       int backupGlobals(lua_State *L);
        // Sets up security on the ScriptApi's Lua state
        void initializeSecurity();
+       void initializeSecurityClient();
        // Checks if the Lua state has been secured
        static bool isSecure(lua_State *L);
        // Loads a file as Lua code safely (doesn't allow bytecode).