diff options
author | ShadowNinja <shadowninja@minetest.net> | 2014-09-05 20:08:51 -0400 |
---|---|---|
committer | ShadowNinja <shadowninja@minetest.net> | 2015-05-16 18:32:31 -0400 |
commit | 3a8c7888807e4483bbdb3edd81c9893f3e2f427d (patch) | |
tree | 81f339e5f61b03e8d7842e06f034d09bf59dba96 /src/script | |
parent | f26421228bbd31f02bf16b45a4b82be84f233e52 (diff) | |
download | minetest-3a8c7888807e4483bbdb3edd81c9893f3e2f427d.tar.gz minetest-3a8c7888807e4483bbdb3edd81c9893f3e2f427d.tar.bz2 minetest-3a8c7888807e4483bbdb3edd81c9893f3e2f427d.zip |
Add mod security
Due to compatibility concerns, this is temporarily disabled.
Diffstat (limited to 'src/script')
-rw-r--r-- | src/script/common/c_internal.cpp | 4 | ||||
-rw-r--r-- | src/script/cpp_api/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/script/cpp_api/s_base.cpp | 44 | ||||
-rw-r--r-- | src/script/cpp_api/s_base.h | 15 | ||||
-rw-r--r-- | src/script/cpp_api/s_security.cpp | 603 | ||||
-rw-r--r-- | src/script/cpp_api/s_security.h | 70 | ||||
-rw-r--r-- | src/script/lua_api/l_mapgen.cpp | 7 | ||||
-rw-r--r-- | src/script/lua_api/l_server.cpp | 3 | ||||
-rw-r--r-- | src/script/lua_api/l_settings.cpp | 2 | ||||
-rw-r--r-- | src/script/lua_api/l_util.cpp | 14 | ||||
-rw-r--r-- | src/script/scripting_game.cpp | 7 | ||||
-rw-r--r-- | src/script/scripting_game.h | 18 | ||||
-rw-r--r-- | src/script/scripting_mainmenu.cpp | 2 |
13 files changed, 744 insertions, 48 deletions
diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index fcab98dc6..8cf39dc3a 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -63,10 +63,8 @@ int script_exception_wrapper(lua_State *L, lua_CFunction f) return f(L); // Call wrapped function and return result. } catch (const char *s) { // Catch and convert exceptions. lua_pushstring(L, s); - } catch (std::exception& e) { + } catch (std::exception &e) { lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "caught (...)"); } return lua_error(L); // Rethrow as a Lua error. } diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index 4584962f1..be4d0131e 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -1,4 +1,5 @@ set(common_SCRIPT_CPP_API_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/s_async.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_entity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp @@ -7,8 +8,8 @@ set(common_SCRIPT_CPP_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/s_node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_player.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s_security.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_server.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/s_async.cpp PARENT_SCOPE) set(client_SCRIPT_CPP_API_SRCS diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 71473d215..4fb8411ef 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "cpp_api/s_internal.h" +#include "cpp_api/s_security.h" #include "lua_api/l_object.h" #include "serverobject.h" #include "debug.h" @@ -45,18 +46,18 @@ class ModNameStorer private: lua_State *L; public: - ModNameStorer(lua_State *L_, const std::string &modname): + ModNameStorer(lua_State *L_, const std::string &mod_name): L(L_) { - // Store current modname in registry - lua_pushstring(L, modname.c_str()); - lua_setfield(L, LUA_REGISTRYINDEX, "current_modname"); + // Store current mod name in registry + lua_pushstring(L, mod_name.c_str()); + lua_setfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); } ~ModNameStorer() { - // Clear current modname in registry + // Clear current mod name from registry lua_pushnil(L); - lua_setfield(L, LUA_REGISTRYINDEX, "current_modname"); + lua_setfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); } }; @@ -112,32 +113,31 @@ ScriptApiBase::~ScriptApiBase() lua_close(m_luastack); } -bool ScriptApiBase::loadMod(const std::string &scriptpath, - const std::string &modname) +bool ScriptApiBase::loadMod(const std::string &script_path, + const std::string &mod_name) { - ModNameStorer modnamestorer(getStack(), modname); + ModNameStorer mod_name_storer(getStack(), mod_name); - if (!string_allowed(modname, MODNAME_ALLOWED_CHARS)) { - errorstream<<"Error loading mod \""<<modname - <<"\": modname does not follow naming conventions: " - <<"Only chararacters [a-z0-9_] are allowed."<<std::endl; - return false; - } - - return loadScript(scriptpath); + return loadScript(script_path); } -bool ScriptApiBase::loadScript(const std::string &scriptpath) +bool ScriptApiBase::loadScript(const std::string &script_path) { - verbosestream<<"Loading and running script from "<<scriptpath<<std::endl; + verbosestream << "Loading and running script from " << script_path << std::endl; lua_State *L = getStack(); - int ret = luaL_loadfile(L, scriptpath.c_str()) || lua_pcall(L, 0, 0, m_errorhandler); - if (ret) { + bool ok; + if (m_secure) { + ok = ScriptApiSecurity::safeLoadFile(L, script_path.c_str()); + } else { + ok = !luaL_loadfile(L, script_path.c_str()); + } + ok = ok && !lua_pcall(L, 0, 0, m_errorhandler); + if (!ok) { errorstream << "========== ERROR FROM LUA ===========" << std::endl; errorstream << "Failed to load and run script from " << std::endl; - errorstream << scriptpath << ":" << std::endl; + errorstream << script_path << ":" << std::endl; errorstream << std::endl; errorstream << lua_tostring(L, -1) << std::endl; errorstream << std::endl; diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 4ea3677a9..cf9b7b934 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -35,6 +35,12 @@ extern "C" { #define SCRIPTAPI_LOCK_DEBUG +#define SCRIPT_MOD_NAME_FIELD "current_mod_name" +// MUST be an invalid mod name so that mods can't +// use that name to bypass security! +#define BUILTIN_MOD_NAME "*builtin*" + + class Server; class Environment; class GUIEngine; @@ -42,17 +48,18 @@ class ServerActiveObject; class ScriptApiBase { public: - ScriptApiBase(); virtual ~ScriptApiBase(); - bool loadMod(const std::string &scriptpath, const std::string &modname); - bool loadScript(const std::string &scriptpath); + bool loadMod(const std::string &script_path, const std::string &mod_name); + bool loadScript(const std::string &script_path); /* object */ void addObjectReference(ServerActiveObject *cobj); void removeObjectReference(ServerActiveObject *cobj); + Server* getServer() { return m_server; } + protected: friend class LuaABM; friend class InvRef; @@ -69,7 +76,6 @@ protected: void scriptError(); void stackDump(std::ostream &o); - Server* getServer() { return m_server; } void setServer(Server* server) { m_server = server; } Environment* getEnv() { return m_environment; } @@ -84,6 +90,7 @@ protected: JMutex m_luastackmutex; // Stack index of Lua error handler int m_errorhandler; + bool m_secure; #ifdef SCRIPTAPI_LOCK_DEBUG bool m_locked; #endif diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp new file mode 100644 index 000000000..abe5b3e97 --- /dev/null +++ b/src/script/cpp_api/s_security.cpp @@ -0,0 +1,603 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "cpp_api/s_security.h" + +#include "filesys.h" +#include "porting.h" +#include "server.h" +#include "settings.h" + +#include <cerrno> +#include <string> +#include <iostream> + + +#define SECURE_API(lib, name) \ + lua_pushcfunction(L, sl_##lib##_##name); \ + lua_setfield(L, -2, #name); + + +static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int from=-2, int to=-1) +{ + if (from < 0) from = lua_gettop(L) + from + 1; + if (to < 0) to = lua_gettop(L) + to + 1; + for (unsigned i = 0; i < (len / sizeof(list[0])); i++) { + lua_getfield(L, from, list[i]); + lua_setfield(L, to, list[i]); + } +} + +// Pushes the original version of a library function on the stack, from the old version +static inline void push_original(lua_State *L, const char *lib, const char *func) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup"); + lua_getfield(L, -1, lib); + lua_remove(L, -2); // Remove globals_backup + lua_getfield(L, -1, func); + lua_remove(L, -2); // Remove lib +} + + +void ScriptApiSecurity::initializeSecurity() +{ + static const char *whitelist[] = { + "assert", + "core", + "collectgarbage", + "DIR_DELIM", + "error", + "getfenv", + "getmetatable", + "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", + "exit", + "getenv", + "setlocale", + "time", + "tmpname", + }; + static const char *debug_whitelist[] = { + "gethook", + "traceback", + "getinfo", + "getmetatable", + "setupvalue", + "setmetatable", + "upvalueid", + "upvaluejoin", + "sethook", + "debug", + "getupvalue", + "setlocal", + }; + static const char *package_whitelist[] = { + "config", + "cpath", + "path", + "searchpath", + }; + static const char *jit_whitelist[] = { + "arch", + "flush", + "off", + "on", + "opt", + "os", + "status", + "version", + "version_num", + }; + + m_secure = true; + + lua_State *L = getStack(); + + // Backup globals to the registry + lua_getglobal(L, "_G"); + lua_setfield(L, LUA_REGISTRYINDEX, "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_getfield(L, LUA_REGISTRYINDEX, "globals_backup"); + int old_globals = lua_gettop(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, load); + SECURE_API(g, loadfile); + 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)); + + // And replace unsafe ones + SECURE_API(os, remove); + SECURE_API(os, rename); + + 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 + + + // Copy safe package fields + lua_getfield(L, old_globals, "package"); + lua_newtable(L); + copy_safe(L, package_whitelist, sizeof(package_whitelist)); + lua_setglobal(L, "package"); + lua_pop(L, 1); // Pop old package + + + // 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 + + lua_pop(L, 1); // Pop globals_backup +} + + +bool ScriptApiSecurity::isSecure(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup"); + bool secure = !lua_isnil(L, -1); + lua_pop(L, 1); + return secure; +} + + +#define CHECK_FILE_ERR(ret, fp) \ + if (ret) { \ + if (fp) std::fclose(fp); \ + lua_pushfstring(L, "%s: %s", path, strerror(errno)); \ + return false; \ + } + + +bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path) +{ + FILE *fp; + char *chunk_name; + if (path == NULL) { + fp = stdin; + chunk_name = const_cast<char *>("=stdin"); + } else { + fp = fopen(path, "r"); + if (!fp) { + 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 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); + start = std::ftell(fp); + } + + if (c == LUA_SIGNATURE[0]) { + lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); + return false; + } + + // Read the file + int ret = std::fseek(fp, 0, SEEK_END); + CHECK_FILE_ERR(ret, fp); + if (ret) { + std::fclose(fp); + lua_pushfstring(L, "%s: %s", path, strerror(errno)); + return false; + } + size_t size = std::ftell(fp) - start; + char *code = new char[size]; + ret = std::fseek(fp, start, SEEK_SET); + CHECK_FILE_ERR(ret, fp); + if (ret) { + std::fclose(fp); + lua_pushfstring(L, "%s: %s", path, strerror(errno)); + return false; + } + size_t num_read = std::fread(code, 1, size, fp); + if (path) { + std::fclose(fp); + } + if (num_read != size) { + lua_pushliteral(L, "Error reading file to load."); + return false; + } + + if (luaL_loadbuffer(L, code, size, chunk_name)) { + return false; + } + + if (path) { + delete [] chunk_name; + } + return true; +} + + +bool ScriptApiSecurity::checkPath(lua_State *L, const char *path) +{ + std::string str; // Transient + + std::string norel_path = fs::RemoveRelativePathComponents(path); + std::string abs_path = fs::AbsolutePath(norel_path); + + if (!abs_path.empty()) { + // Don't allow accessing the settings file + str = fs::AbsolutePath(g_settings_path); + if (str == abs_path) return false; + } + + // 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 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); + abs_path = fs::AbsolutePath(cur_path); + } + 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; + + // Get server from registry + lua_getfield(L, LUA_REGISTRYINDEX, "scriptapi"); + ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); + lua_pop(L, 1); + const Server *server = script->getServer(); + + if (!server) return false; + + // Get mod name + lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); + if (lua_isstring(L, -1)) { + std::string mod_name = lua_tostring(L, -1); + + // Builtin can access anything + if (mod_name == BUILTIN_MOD_NAME) { + 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; + } + } + } + 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 all other paths in world path + if (fs::PathStartsWith(abs_path, str)) { + return true; + } + + // Default to disallowing + return false; +} + + +int ScriptApiSecurity::sl_g_dofile(lua_State *L) +{ + int nret = sl_g_loadfile(L); + if (nret != 1) { + return nret; + } + int top_precall = lua_gettop(L); + lua_call(L, 0, LUA_MULTRET); + // Return number of arguments returned by the function, + // adjusting for the function being poped. + return lua_gettop(L) - (top_precall - 1); +} + + +int ScriptApiSecurity::sl_g_load(lua_State *L) +{ + size_t len; + const char *buf; + std::string code; + const char *chunk_name = "=(load)"; + + luaL_checktype(L, 1, LUA_TFUNCTION); + if (!lua_isnone(L, 2)) { + luaL_checktype(L, 2, LUA_TSTRING); + chunk_name = lua_tostring(L, 2); + } + + while (true) { + lua_pushvalue(L, 1); + lua_call(L, 0, 1); + int t = lua_type(L, -1); + if (t == LUA_TNIL) { + break; + } else if (t != LUA_TSTRING) { + lua_pushnil(L); + lua_pushliteral(L, "Loader didn't return a string"); + return 2; + } + buf = lua_tolstring(L, -1, &len); + code += std::string(buf, len); + lua_pop(L, 1); // Pop return value + } + if (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.data(), code.size(), chunk_name)) { + lua_pushnil(L); + lua_insert(L, lua_gettop(L) - 1); + return 2; + } + return 1; +} + + +int ScriptApiSecurity::sl_g_loadfile(lua_State *L) +{ + const char *path = NULL; + + if (lua_isstring(L, 1)) { + path = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path); + } + + if (!safeLoadFile(L, path)) { + lua_pushnil(L); + lua_insert(L, -2); + return 2; + } + + return 1; +} + + +int ScriptApiSecurity::sl_g_loadstring(lua_State *L) +{ + const char *chunk_name = "=(load)"; + + luaL_checktype(L, 1, LUA_TSTRING); + if (!lua_isnone(L, 2)) { + luaL_checktype(L, 2, LUA_TSTRING); + chunk_name = lua_tostring(L, 2); + } + + size_t size; + const char *code = lua_tolstring(L, 1, &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)) { + lua_pushnil(L); + lua_insert(L, lua_gettop(L) - 1); + return 2; + } + return 1; +} + + +int ScriptApiSecurity::sl_g_require(lua_State *L) +{ + lua_pushliteral(L, "require() is disabled when mod security is on."); + return lua_error(L); +} + + +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); + + push_original(L, "io", "open"); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_call(L, 2, 2); + return 2; +} + + +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); + } + + push_original(L, "io", "input"); + lua_pushvalue(L, 1); + lua_call(L, 1, 1); + return 1; +} + + +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); + } + + push_original(L, "io", "output"); + lua_pushvalue(L, 1); + lua_call(L, 1, 1); + return 1; +} + + +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); + } + + push_original(L, "io", "lines"); + lua_pushvalue(L, 1); + int top_precall = lua_gettop(L); + lua_call(L, 1, LUA_MULTRET); + // Return number of arguments returned by the function, + // adjusting for the function being poped. + return lua_gettop(L) - (top_precall - 1); +} + + +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); + + luaL_checktype(L, 2, LUA_TSTRING); + const char *path2 = lua_tostring(L, 2); + CHECK_SECURE_PATH(L, path2); + + push_original(L, "os", "rename"); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_call(L, 2, 2); + return 2; +} + + +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); + + push_original(L, "os", "remove"); + lua_pushvalue(L, 1); + lua_call(L, 1, 2); + return 2; +} + diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h new file mode 100644 index 000000000..4a4389cf5 --- /dev/null +++ b/src/script/cpp_api/s_security.h @@ -0,0 +1,70 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef S_SECURITY_H +#define S_SECURITY_H + +#include "cpp_api/s_base.h" + + +#define CHECK_SECURE_PATH(L, path) \ + if (!ScriptApiSecurity::checkPath(L, path)) { \ + lua_pushstring(L, (std::string("Attempt to access external file ") + \ + path + " with mod security on.").c_str()); \ + lua_error(L); \ + } +#define CHECK_SECURE_PATH_OPTIONAL(L, path) \ + if (ScriptApiSecurity::isSecure(L)) { \ + CHECK_SECURE_PATH(L, path); \ + } + + +class ScriptApiSecurity : virtual public ScriptApiBase +{ +public: + // Sets up security on the ScriptApi's Lua state + void initializeSecurity(); + // 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). + static bool safeLoadFile(lua_State *L, const char *path); + // Checks if mods are allowed to read and write to the path + static bool checkPath(lua_State *L, const char *path); + +private: + // Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name> + // (sl stands for Secure Lua) + + static int sl_g_dofile(lua_State *L); + static int sl_g_load(lua_State *L); + static int sl_g_loadfile(lua_State *L); + static int sl_g_loadstring(lua_State *L); + static int sl_g_require(lua_State *L); + + static int sl_io_open(lua_State *L); + static int sl_io_input(lua_State *L); + static int sl_io_output(lua_State *L); + static int sl_io_lines(lua_State *L); + + static int sl_os_rename(lua_State *L); + static int sl_os_remove(lua_State *L); +}; + +#endif + diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index d94f902c4..dc3644e1c 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_vmanip.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "cpp_api/s_security.h" #include "util/serialize.h" #include "server.h" #include "environment.h" @@ -1031,6 +1032,10 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) int ModApiMapgen::l_create_schematic(lua_State *L) { INodeDefManager *ndef = getServer(L)->getNodeDefManager(); + + const char *filename = luaL_checkstring(L, 4); + CHECK_SECURE_PATH_OPTIONAL(L, filename); + Map *map = &(getEnv(L)->getMap()); Schematic schem; @@ -1069,8 +1074,6 @@ int ModApiMapgen::l_create_schematic(lua_State *L) } } - const char *filename = luaL_checkstring(L, 4); - if (!schem.getSchematicFromMap(map, p1, p2)) { errorstream << "create_schematic: failed to get schematic " "from map" << std::endl; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 99e73b03e..0d8926317 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "cpp_api/s_base.h" #include "server.h" #include "environment.h" #include "player.h" @@ -342,7 +343,7 @@ int ModApiServer::l_show_formspec(lua_State *L) int ModApiServer::l_get_current_modname(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_getfield(L, LUA_REGISTRYINDEX, "current_modname"); + lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); return 1; } diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 9c88a3e05..35b82b435 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_settings.h" #include "lua_api/l_internal.h" +#include "cpp_api/s_security.h" #include "settings.h" #include "log.h" @@ -188,6 +189,7 @@ int LuaSettings::create_object(lua_State* L) { NO_MAP_LOCK_REQUIRED; const char* filename = luaL_checkstring(L, 1); + CHECK_SECURE_PATH_OPTIONAL(L, filename); LuaSettings* o = new LuaSettings(filename); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 283cca01f..151d449d5 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -92,12 +92,19 @@ int ModApiUtil::l_log(lua_State *L) return 0; } +#define CHECK_SECURE_SETTING(L, name) \ + if (name.compare(0, 7, "secure.") == 0) {\ + lua_pushliteral(L, "Attempt to set secure setting.");\ + lua_error(L);\ + } + // setting_set(name, value) int ModApiUtil::l_setting_set(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char *name = luaL_checkstring(L, 1); - const char *value = luaL_checkstring(L, 2); + std::string name = luaL_checkstring(L, 1); + std::string value = luaL_checkstring(L, 2); + CHECK_SECURE_SETTING(L, name); g_settings->set(name, value); return 0; } @@ -120,8 +127,9 @@ int ModApiUtil::l_setting_get(lua_State *L) int ModApiUtil::l_setting_setbool(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char *name = luaL_checkstring(L, 1); + std::string name = luaL_checkstring(L, 1); bool value = lua_toboolean(L, 2); + CHECK_SECURE_SETTING(L, name); g_settings->setBool(name, value); return 0; } diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp index 5bcd2a33d..9321c38a9 100644 --- a/src/script/scripting_game.cpp +++ b/src/script/scripting_game.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "scripting_game.h" #include "server.h" #include "log.h" +#include "settings.h" #include "cpp_api/s_internal.h" #include "lua_api/l_base.h" #include "lua_api/l_craft.h" @@ -49,10 +50,12 @@ GameScripting::GameScripting(Server* server) // setEnv(env) is called by ScriptApiEnv::initializeEnvironment() // once the environment has been created - //TODO add security - SCRIPTAPI_PRECHECKHEADER + if (g_settings->getBool("secure.enable_security")) { + initializeSecurity(); + } + lua_getglobal(L, "core"); int top = lua_gettop(L); diff --git a/src/script/scripting_game.h b/src/script/scripting_game.h index 14dbd9170..16d5dcb37 100644 --- a/src/script/scripting_game.h +++ b/src/script/scripting_game.h @@ -27,19 +27,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_node.h" #include "cpp_api/s_player.h" #include "cpp_api/s_server.h" +#include "cpp_api/s_security.h" /*****************************************************************************/ /* Scripting <-> Game Interface */ /*****************************************************************************/ -class GameScripting - : virtual public ScriptApiBase, - public ScriptApiDetached, - public ScriptApiEntity, - public ScriptApiEnv, - public ScriptApiNode, - public ScriptApiPlayer, - public ScriptApiServer +class GameScripting : + virtual public ScriptApiBase, + public ScriptApiDetached, + public ScriptApiEntity, + public ScriptApiEnv, + public ScriptApiNode, + public ScriptApiPlayer, + public ScriptApiServer, + public ScriptApiSecurity { public: GameScripting(Server* server); diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 54b3133c5..c74c18edc 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -38,8 +38,6 @@ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine) { setGuiEngine(guiengine); - //TODO add security - SCRIPTAPI_PRECHECKHEADER lua_getglobal(L, "core"); |