]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/script/cpp_api/s_security.cpp
[CSM] Add `on_punchnode` callback
[dragonfireclient.git] / src / script / cpp_api / s_security.cpp
index c9816f89bd512fafbf105476577b61901d8c6f1e..c6aad71b8e896321f3058de1cf8e9d5bb18b4d38 100644 (file)
@@ -99,7 +99,6 @@ void ScriptApiSecurity::initializeSecurity()
                "clock",
                "date",
                "difftime",
-               "exit",
                "getenv",
                "setlocale",
                "time",
@@ -140,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
@@ -224,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)) {
@@ -233,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)
 {
@@ -336,12 +470,15 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path)
 }
 
 
-bool ScriptApiSecurity::checkPath(lua_State *L, const char *path)
+bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
+               bool write_required, bool *write_allowed)
 {
+       if (write_allowed)
+               *write_allowed = false;
+
        std::string str;  // Transient
 
-       std::string norel_path = fs::RemoveRelativePathComponents(path);
-       std::string abs_path = fs::AbsolutePath(norel_path);
+       std::string abs_path = fs::AbsolutePath(path);
 
        if (!abs_path.empty()) {
                // Don't allow accessing the settings file
@@ -352,26 +489,37 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path)
        // If we couldn't find the absolute path (path doesn't exist) then
        // try removing the last components until it works (to allow
        // non-existent files/folders for mkdir).
-       std::string cur_path = norel_path;
+       std::string cur_path = path;
        std::string removed;
        while (abs_path.empty() && !cur_path.empty()) {
-               std::string tmp_rmed;
-               cur_path = fs::RemoveLastPathComponent(cur_path, &tmp_rmed);
-               removed = tmp_rmed + (removed.empty() ? "" : DIR_DELIM + removed);
+               std::string component;
+               cur_path = fs::RemoveLastPathComponent(cur_path, &component);
+               if (component == "..") {
+                       // Parent components can't be allowed or we could allow something like
+                       // /home/user/minetest/worlds/foo/noexist/../../../../../../etc/passwd.
+                       // If we have previous non-relative elements in the path we might be
+                       // able to remove them so that things like worlds/foo/noexist/../auth.txt
+                       // could be allowed, but those paths will be interpreted as nonexistent
+                       // by the operating system anyways.
+                       return false;
+               }
+               removed = component + (removed.empty() ? "" : DIR_DELIM + removed);
                abs_path = fs::AbsolutePath(cur_path);
        }
-       if (abs_path.empty()) return false;
+       if (abs_path.empty())
+               return false;
        // Add the removed parts back so that you can't, eg, create a
        // directory in worldmods if worldmods doesn't exist.
-       if (!removed.empty()) abs_path += DIR_DELIM + removed;
+       if (!removed.empty())
+               abs_path += DIR_DELIM + removed;
 
        // Get server from registry
        lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
        ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1);
        lua_pop(L, 1);
-       const Server *server = script->getServer();
-
-       if (!server) return false;
+       const IGameDef *gamedef = script->getGameDef();
+       if (!gamedef)
+               return false;
 
        // Get mod name
        lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
@@ -380,32 +528,53 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path)
 
                // Builtin can access anything
                if (mod_name == BUILTIN_MOD_NAME) {
+                       if (write_allowed) *write_allowed = true;
                        return true;
                }
 
                // Allow paths in mod path
-               const ModSpec *mod = server->getModSpec(mod_name);
-               if (mod) {
-                       str = fs::AbsolutePath(mod->path);
-                       if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
-                               return true;
+               // Don't bother if write access isn't important, since it will be handled later
+               if (write_required || write_allowed != NULL) {
+                       const ModSpec *mod = gamedef->getModSpec(mod_name);
+                       if (mod) {
+                               str = fs::AbsolutePath(mod->path);
+                               if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
+                                       if (write_allowed) *write_allowed = true;
+                                       return true;
+                               }
                        }
                }
        }
        lua_pop(L, 1);  // Pop mod name
 
-       str = fs::AbsolutePath(server->getWorldPath());
-       if (str.empty()) return false;
-       // Don't allow access to world mods.  We add to the absolute path
-       // of the world instead of getting the absolute paths directly
-       // because that won't work if they don't exist.
-       if (fs::PathStartsWith(abs_path, str + DIR_DELIM + "worldmods") ||
-                       fs::PathStartsWith(abs_path, str + DIR_DELIM + "game")) {
-               return false;
+       // Allow read-only access to all mod directories
+       if (!write_required) {
+               const std::vector<ModSpec> mods = gamedef->getMods();
+               for (size_t i = 0; i < mods.size(); ++i) {
+                       str = fs::AbsolutePath(mods[i].path);
+                       if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
+                               return true;
+                       }
+               }
        }
-       // Allow all other paths in world path
-       if (fs::PathStartsWith(abs_path, str)) {
-               return true;
+
+       str = fs::AbsolutePath(gamedef->getWorldPath());
+       if (!str.empty()) {
+               // Don't allow access to other paths in the world mod/game path.
+               // These have to be blocked so you can't override a trusted mod
+               // by creating a mod with the same name in a world mod directory.
+               // We add to the absolute path of the world instead of getting
+               // the absolute paths directly because that won't work if they
+               // don't exist.
+               if (fs::PathStartsWith(abs_path, str + DIR_DELIM + "worldmods") ||
+                               fs::PathStartsWith(abs_path, str + DIR_DELIM + "game")) {
+                       return false;
+               }
+               // Allow all other paths in world path
+               if (fs::PathStartsWith(abs_path, str)) {
+                       if (write_allowed) *write_allowed = true;
+                       return true;
+               }
        }
 
        // Default to disallowing
@@ -476,7 +645,7 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
 
        if (lua_isstring(L, 1)) {
                path = lua_tostring(L, 1);
-               CHECK_SECURE_PATH(L, path);
+               CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
        }
 
        if (!safeLoadFile(L, path)) {
@@ -529,7 +698,16 @@ int ScriptApiSecurity::sl_io_open(lua_State *L)
 
        luaL_checktype(L, 1, LUA_TSTRING);
        const char *path = lua_tostring(L, 1);
-       CHECK_SECURE_PATH(L, path);
+
+       bool write_requested = false;
+       if (with_mode) {
+               luaL_checktype(L, 2, LUA_TSTRING);
+               const char *mode = lua_tostring(L, 2);
+               write_requested = strchr(mode, 'w') != NULL ||
+                       strchr(mode, '+') != NULL ||
+                       strchr(mode, 'a') != NULL;
+       }
+       CHECK_SECURE_PATH_INTERNAL(L, path, write_requested, NULL);
 
        push_original(L, "io", "open");
        lua_pushvalue(L, 1);
@@ -546,7 +724,7 @@ int ScriptApiSecurity::sl_io_input(lua_State *L)
 {
        if (lua_isstring(L, 1)) {
                const char *path = lua_tostring(L, 1);
-               CHECK_SECURE_PATH(L, path);
+               CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
        }
 
        push_original(L, "io", "input");
@@ -560,7 +738,7 @@ int ScriptApiSecurity::sl_io_output(lua_State *L)
 {
        if (lua_isstring(L, 1)) {
                const char *path = lua_tostring(L, 1);
-               CHECK_SECURE_PATH(L, path);
+               CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL);
        }
 
        push_original(L, "io", "output");
@@ -574,7 +752,7 @@ int ScriptApiSecurity::sl_io_lines(lua_State *L)
 {
        if (lua_isstring(L, 1)) {
                const char *path = lua_tostring(L, 1);
-               CHECK_SECURE_PATH(L, path);
+               CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
        }
 
        int top_precall = lua_gettop(L);
@@ -591,11 +769,11 @@ int ScriptApiSecurity::sl_os_rename(lua_State *L)
 {
        luaL_checktype(L, 1, LUA_TSTRING);
        const char *path1 = lua_tostring(L, 1);
-       CHECK_SECURE_PATH(L, path1);
+       CHECK_SECURE_PATH_INTERNAL(L, path1, true, NULL);
 
        luaL_checktype(L, 2, LUA_TSTRING);
        const char *path2 = lua_tostring(L, 2);
-       CHECK_SECURE_PATH(L, path2);
+       CHECK_SECURE_PATH_INTERNAL(L, path2, true, NULL);
 
        push_original(L, "os", "rename");
        lua_pushvalue(L, 1);
@@ -609,7 +787,7 @@ int ScriptApiSecurity::sl_os_remove(lua_State *L)
 {
        luaL_checktype(L, 1, LUA_TSTRING);
        const char *path = lua_tostring(L, 1);
-       CHECK_SECURE_PATH(L, path);
+       CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL);
 
        push_original(L, "os", "remove");
        lua_pushvalue(L, 1);