]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/script/cpp_api/s_security.cpp
Merge branch 'master' of https://github.com/minetest/minetest
[dragonfireclient.git] / src / script / cpp_api / s_security.cpp
index ec3a52e8e8afdbad67da1f82f6bb1fa3305d1600..37c5b61dcf89a7841b35a1c817628a76c7a3cb57 100644 (file)
@@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "filesys.h"
 #include "porting.h"
 #include "server.h"
+#include "client/client.h"
 #include "settings.h"
 
 #include <cerrno>
@@ -89,6 +90,7 @@ void ScriptApiSecurity::initializeSecurity()
                "math",
        };
        static const char *io_whitelist[] = {
+               "open",
                "close",
                "flush",
                "read",
@@ -112,7 +114,6 @@ void ScriptApiSecurity::initializeSecurity()
                "setupvalue",
                "setmetatable",
                "upvalueid",
-               "upvaluejoin",
                "sethook",
                "debug",
                "setlocal",
@@ -140,8 +141,18 @@ 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);
 
-       int old_globals = backupGlobals(L);
+       // Replace the global environment with an empty one
+       int thread = getThread(L);
+       createEmptyEnv(L);
+       setLuaEnv(L, thread);
+
+       // Get old globals
+       lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
+       int old_globals = lua_gettop(L);
 
 
        // Copy safe base functions
@@ -163,7 +174,7 @@ void ScriptApiSecurity::initializeSecurity()
        copy_safe(L, io_whitelist, sizeof(io_whitelist));
 
        // And replace unsafe ones
-       SECURE_API(io, open);
+       //SECURE_API(io, open);
        SECURE_API(io, input);
        SECURE_API(io, output);
        SECURE_API(io, lines);
@@ -233,6 +244,7 @@ void ScriptApiSecurity::initializeSecurityClient()
                "rawset",
                "select",
                "setfenv",
+               // getmetatable can be used to escape the sandbox
                "setmetatable",
                "tonumber",
                "tostring",
@@ -249,14 +261,13 @@ void ScriptApiSecurity::initializeSecurityClient()
        static const char *os_whitelist[] = {
                "clock",
                "date",
-               "difftime",     
-               "time",
-               "setlocale",
+               "difftime",
+               "time"
        };
        static const char *debug_whitelist[] = {
                "getinfo",
+               "traceback"
        };
-
 #if USE_LUAJIT
        static const char *jit_whitelist[] = {
                "arch",
@@ -274,80 +285,86 @@ void ScriptApiSecurity::initializeSecurityClient()
        m_secure = true;
 
        lua_State *L = getStack();
+       int thread = getThread(L);
 
+       // Backup globals to the registry
+       lua_getglobal(L, "_G");
+       lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
 
-       int old_globals = backupGlobals(L);
-
+       // create an empty environment
+       createEmptyEnv(L);
 
        // Copy safe base functions
        lua_getglobal(L, "_G");
+       lua_getfield(L, -2, "_G");
        copy_safe(L, whitelist, sizeof(whitelist));
 
        // And replace unsafe ones
        SECURE_API(g, dofile);
+       SECURE_API(g, load);
+       SECURE_API(g, loadfile);
        SECURE_API(g, loadstring);
        SECURE_API(g, require);
-       lua_pop(L, 1);
-
-
+       lua_pop(L, 2);
 
        // Copy safe OS functions
-       lua_getfield(L, old_globals, "os");
+       lua_getglobal(L, "os");
        lua_newtable(L);
        copy_safe(L, os_whitelist, sizeof(os_whitelist));
-       lua_setglobal(L, "os");
+       lua_setfield(L, -3, "os");
        lua_pop(L, 1);  // Pop old OS
 
 
        // Copy safe debug functions
-       lua_getfield(L, old_globals, "debug");
+       lua_getglobal(L, "debug");
        lua_newtable(L);
        copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
-       lua_setglobal(L, "debug");
+       lua_setfield(L, -3, "debug");
        lua_pop(L, 1);  // Pop old debug
+       
 
 #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_getglobal(L, "jit");
+       lua_newtable(L);
+       copy_safe(L, jit_whitelist, sizeof(jit_whitelist));
+       lua_setfield(L, -3, "jit");
        lua_pop(L, 1);  // Pop old jit
 #endif
 
-       lua_pop(L, 1); // Pop globals_backup
+       // Set the environment to the one we created earlier
+       setLuaEnv(L, thread);
 }
 
-int ScriptApiSecurity::backupGlobals(lua_State *L)
+int ScriptApiSecurity::getThread(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!");
+       return lua_gettop(L);
 #endif
+       return 0;
+}
+
+void ScriptApiSecurity::createEmptyEnv(lua_State *L)
+{
        lua_newtable(L);  // Create new environment
        lua_pushvalue(L, -1);
-       lua_setfield(L, -2, "_G");  // Set _G of new environment
+       lua_setfield(L, -2, "_G");  // Create the _G loop
+}
+
+void ScriptApiSecurity::setLuaEnv(lua_State *L, int thread)
+{
 #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 "
+       FATAL_ERROR_IF(!lua_setfenv(L, thread), "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)
@@ -358,20 +375,24 @@ bool ScriptApiSecurity::isSecure(lua_State *L)
        return secure;
 }
 
-
-#define CHECK_FILE_ERR(ret, fp) \
-       if (ret) { \
-               lua_pushfstring(L, "%s: %s", path, strerror(errno)); \
-               if (fp) std::fclose(fp); \
-               return false; \
+bool ScriptApiSecurity::safeLoadString(lua_State *L, const std::string &code, const char *chunk_name)
+{
+       if (code.size() > 0 && code[0] == LUA_SIGNATURE[0]) {
+               lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
+               return false;
        }
+       if (luaL_loadbuffer(L, code.data(), code.size(), chunk_name))
+               return false;
+       return true;
+}
 
-
-bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path)
+bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char *display_name)
 {
        FILE *fp;
        char *chunk_name;
-       if (path == NULL) {
+       if (!display_name)
+               display_name = path;
+       if (!path) {
                fp = stdin;
                chunk_name = const_cast<char *>("=stdin");
        } else {
@@ -380,71 +401,58 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path)
                        lua_pushfstring(L, "%s: %s", path, strerror(errno));
                        return false;
                }
-               chunk_name = new char[strlen(path) + 2];
-               chunk_name[0] = '@';
-               chunk_name[1] = '\0';
-               strcat(chunk_name, path);
+               size_t len = strlen(display_name) + 2;
+               chunk_name = new char[len];
+               snprintf(chunk_name, len, "@%s", display_name);
        }
 
        size_t start = 0;
        int c = std::getc(fp);
        if (c == '#') {
                // Skip the first line
-               while ((c = std::getc(fp)) != EOF && c != '\n');
-               if (c == '\n') c = std::getc(fp);
+               while ((c = std::getc(fp)) != EOF && c != '\n') {}
+               if (c == '\n')
+                       std::getc(fp);
                start = std::ftell(fp);
        }
 
-       if (c == LUA_SIGNATURE[0]) {
-               lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
-               std::fclose(fp);
+       // Read the file
+       int ret = std::fseek(fp, 0, SEEK_END);
+       if (ret) {
+               lua_pushfstring(L, "%s: %s", path, strerror(errno));
                if (path) {
+                       std::fclose(fp);
                        delete [] chunk_name;
                }
                return false;
        }
 
-       // Read the file
-       int ret = std::fseek(fp, 0, SEEK_END);
-       CHECK_FILE_ERR(ret, fp);
-
        size_t size = std::ftell(fp) - start;
-       char *code = new char[size];
+       std::string code(size, '\0');
        ret = std::fseek(fp, start, SEEK_SET);
        if (ret) {
                lua_pushfstring(L, "%s: %s", path, strerror(errno));
-               std::fclose(fp);
-               delete [] code;
                if (path) {
+                       std::fclose(fp);
                        delete [] chunk_name;
                }
                return false;
        }
 
-       size_t num_read = std::fread(code, 1, size, fp);
-       if (path) {
+       size_t num_read = std::fread(&code[0], 1, size, fp);
+       if (path)
                std::fclose(fp);
-       }
        if (num_read != size) {
                lua_pushliteral(L, "Error reading file to load.");
-               delete [] code;
-               if (path) {
+               if (path)
                        delete [] chunk_name;
-               }
                return false;
        }
 
-       if (luaL_loadbuffer(L, code, size, chunk_name)) {
-               delete [] code;
-               return false;
-       }
-
-       delete [] code;
-
-       if (path) {
+       bool result = safeLoadString(L, code, chunk_name);
+       if (path)
                delete [] chunk_name;
-       }
-       return true;
+       return result;
 }
 
 
@@ -481,7 +489,7 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
                        // by the operating system anyways.
                        return false;
                }
-               removed = component + (removed.empty() ? "" : DIR_DELIM + removed);
+               removed.append(component).append(removed.empty() ? "" : DIR_DELIM + removed);
                abs_path = fs::AbsolutePath(cur_path);
        }
        if (abs_path.empty())
@@ -493,7 +501,12 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
 
        // Get server from registry
        lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
-       ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1);
+       ScriptApiBase *script;
+#if INDIRECT_SCRIPTAPI_RIDX
+       script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1));
+#else
+       script = (ScriptApiBase *) lua_touserdata(L, -1);
+#endif
        lua_pop(L, 1);
        const IGameDef *gamedef = script->getGameDef();
        if (!gamedef)
@@ -502,7 +515,7 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
        // Get mod name
        lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
        if (lua_isstring(L, -1)) {
-               std::string mod_name = lua_tostring(L, -1);
+               std::string mod_name = readParam<std::string>(L, -1);
 
                // Builtin can access anything
                if (mod_name == BUILTIN_MOD_NAME) {
@@ -527,9 +540,9 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
 
        // 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);
+               const std::vector<ModSpec> &mods = gamedef->getMods();
+               for (const ModSpec &mod : mods) {
+                       str = fs::AbsolutePath(mod.path);
                        if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
                                return true;
                        }
@@ -594,7 +607,9 @@ int ScriptApiSecurity::sl_g_load(lua_State *L)
                int t = lua_type(L, -1);
                if (t == LUA_TNIL) {
                        break;
-               } else if (t != LUA_TSTRING) {
+               }
+
+               if (t != LUA_TSTRING) {
                        lua_pushnil(L);
                        lua_pushliteral(L, "Loader didn't return a string");
                        return 2;
@@ -603,14 +618,9 @@ int ScriptApiSecurity::sl_g_load(lua_State *L)
                code += std::string(buf, len);
                lua_pop(L, 1); // Pop return value
        }
-       if (code[0] == LUA_SIGNATURE[0]) {
+       if (!safeLoadString(L, code, chunk_name)) {
                lua_pushnil(L);
-               lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
-               return 2;
-       }
-       if (luaL_loadbuffer(L, code.data(), code.size(), chunk_name)) {
-               lua_pushnil(L);
-               lua_insert(L, lua_gettop(L) - 1);
+               lua_insert(L, -2);
                return 2;
        }
        return 1;
@@ -619,8 +629,38 @@ int ScriptApiSecurity::sl_g_load(lua_State *L)
 
 int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
 {
-       const char *path = NULL;
+#ifndef SERVER
+       lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
+#if INDIRECT_SCRIPTAPI_RIDX
+       ScriptApiBase *script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1));
+#else
+       ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1);
+#endif
+       lua_pop(L, 1);
+
+       // Client implementation
+       if (script->getType() == ScriptingType::Client) {
+               std::string path = readParam<std::string>(L, 1);
+               const std::string *contents = script->getClient()->getModFile(path);
+               if (!contents) {
+                       std::string error_msg = "Coudln't find script called: " + path;
+                       lua_pushnil(L);
+                       lua_pushstring(L, error_msg.c_str());
+                       return 2;
+               }
+
+               std::string chunk_name = "@" + path;
+               if (!safeLoadString(L, *contents, chunk_name.c_str())) {
+                       lua_pushnil(L);
+                       lua_insert(L, -2);
+                       return 2;
+               }
+               return 1;
+       }
+#endif
 
+       // Server implementation
+       const char *path = NULL;
        if (lua_isstring(L, 1)) {
                path = lua_tostring(L, 1);
                CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
@@ -648,15 +688,11 @@ int ScriptApiSecurity::sl_g_loadstring(lua_State *L)
 
        size_t size;
        const char *code = lua_tolstring(L, 1, &size);
+       std::string code_s(code, size);
 
-       if (size > 0 && code[0] == LUA_SIGNATURE[0]) {
-               lua_pushnil(L);
-               lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
-               return 2;
-       }
-       if (luaL_loadbuffer(L, code, size, chunk_name)) {
+       if (!safeLoadString(L, code_s, chunk_name)) {
                lua_pushnil(L);
-               lua_insert(L, lua_gettop(L) - 1);
+               lua_insert(L, -2);
                return 2;
        }
        return 1;