Browse Source

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

red-001 7 years ago
parent
commit
a50d07d39a

+ 4 - 1
builtin/client/register.lua

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

+ 3 - 2
builtin/common/strict.lua

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

+ 1 - 0
builtin/init.lua

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

+ 1 - 1
src/script/clientscripting.cpp

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

+ 160 - 25
src/script/cpp_api/s_security.cpp

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

+ 2 - 0
src/script/cpp_api/s_security.h

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