aboutsummaryrefslogtreecommitdiff
path: root/src/script/cpp_api/s_security.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/script/cpp_api/s_security.cpp')
-rw-r--r--src/script/cpp_api/s_security.cpp131
1 files changed, 107 insertions, 24 deletions
diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp
index 63058d7c3..a6c5114b2 100644
--- a/src/script/cpp_api/s_security.cpp
+++ b/src/script/cpp_api/s_security.cpp
@@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "cpp_api/s_security.h"
-
+#include "lua_api/l_base.h"
#include "filesys.h"
#include "porting.h"
#include "server.h"
@@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cerrno>
#include <string>
+#include <algorithm>
#include <iostream>
@@ -45,6 +46,21 @@ static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int
}
}
+static void shallow_copy_table(lua_State *L, int from=-2, int to=-1)
+{
+ if (from < 0) from = lua_gettop(L) + from + 1;
+ if (to < 0) to = lua_gettop(L) + to + 1;
+ lua_pushnil(L);
+ while (lua_next(L, from) != 0) {
+ assert(lua_type(L, -1) != LUA_TTABLE);
+ // duplicate key and value for lua_rawset
+ lua_pushvalue(L, -2);
+ lua_pushvalue(L, -2);
+ lua_rawset(L, to);
+ lua_pop(L, 1);
+ }
+}
+
// 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)
{
@@ -83,11 +99,15 @@ void ScriptApiSecurity::initializeSecurity()
"unpack",
"_VERSION",
"xpcall",
- // Completely safe libraries
+ };
+ static const char *whitelist_tables[] = {
+ // These libraries are completely safe BUT we need to duplicate their table
+ // to ensure the sandbox can't affect the insecure env
"coroutine",
"string",
"table",
"math",
+ "bit"
};
static const char *io_whitelist[] = {
"close",
@@ -101,21 +121,17 @@ void ScriptApiSecurity::initializeSecurity()
"date",
"difftime",
"getenv",
- "setlocale",
"time",
- "tmpname",
};
static const char *debug_whitelist[] = {
"gethook",
"traceback",
"getinfo",
"getmetatable",
- "setupvalue",
"setmetatable",
"upvalueid",
"sethook",
"debug",
- "setlocal",
};
static const char *package_whitelist[] = {
"config",
@@ -167,6 +183,17 @@ void ScriptApiSecurity::initializeSecurity()
lua_pop(L, 1);
+ // Copy safe libraries
+ for (const char *libname : whitelist_tables) {
+ lua_getfield(L, old_globals, libname);
+ lua_newtable(L);
+ shallow_copy_table(L);
+
+ lua_setglobal(L, libname);
+ lua_pop(L, 1);
+ }
+
+
// Copy safe IO functions
lua_getfield(L, old_globals, "io");
lua_newtable(L);
@@ -190,6 +217,7 @@ void ScriptApiSecurity::initializeSecurity()
// And replace unsafe ones
SECURE_API(os, remove);
SECURE_API(os, rename);
+ SECURE_API(os, setlocale);
lua_setglobal(L, "os");
lua_pop(L, 1); // Pop old OS
@@ -221,7 +249,25 @@ void ScriptApiSecurity::initializeSecurity()
lua_pop(L, 1); // Pop old jit
#endif
+ // Get rid of 'core' in the old globals, we don't want anyone thinking it's
+ // safe or even usable.
+ lua_pushnil(L);
+ lua_setfield(L, old_globals, "core");
+
lua_pop(L, 1); // Pop globals_backup
+
+
+ /*
+ * In addition to copying the tables in whitelist_tables, we also need to
+ * replace the string metatable. Otherwise old_globals.string would
+ * be accessible via getmetatable("").__index from inside the sandbox.
+ */
+ lua_pushliteral(L, "");
+ lua_newtable(L);
+ lua_getglobal(L, "string");
+ lua_setfield(L, -2, "__index");
+ lua_setmetatable(L, -2);
+ lua_pop(L, 1); // Pop empty string
}
void ScriptApiSecurity::initializeSecurityClient()
@@ -243,7 +289,7 @@ void ScriptApiSecurity::initializeSecurityClient()
"rawset",
"select",
"setfenv",
- // getmetatable can be used to escape the sandbox
+ // getmetatable can be used to escape the sandbox <- ???
"setmetatable",
"tonumber",
"tostring",
@@ -256,6 +302,7 @@ void ScriptApiSecurity::initializeSecurityClient()
"string",
"table",
"math",
+ "bit",
};
static const char *os_whitelist[] = {
"clock",
@@ -264,7 +311,7 @@ void ScriptApiSecurity::initializeSecurityClient()
"time"
};
static const char *debug_whitelist[] = {
- "getinfo",
+ "getinfo", // used by builtin and unset before mods load
"traceback"
};
@@ -496,15 +543,8 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
if (!removed.empty())
abs_path += DIR_DELIM + removed;
- // Get server from registry
- lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
- 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);
+ // Get gamedef from registry
+ ScriptApiBase *script = ModApiBase::getScriptApiBase(L);
const IGameDef *gamedef = script->getGameDef();
if (!gamedef)
return false;
@@ -569,6 +609,38 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
return false;
}
+bool ScriptApiSecurity::checkWhitelisted(lua_State *L, const std::string &setting)
+{
+ assert(str_starts_with(setting, "secure."));
+
+ // We have to make sure that this function is being called directly by
+ // a mod, otherwise a malicious mod could override this function and
+ // steal its return value.
+ lua_Debug info;
+
+ // Make sure there's only one item below this function on the stack...
+ if (lua_getstack(L, 2, &info))
+ return false;
+ FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed");
+ FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed");
+
+ // ...and that that item is the main file scope.
+ if (strcmp(info.what, "main") != 0)
+ return false;
+
+ // Mod must be listed in secure.http_mods or secure.trusted_mods
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
+ if (!lua_isstring(L, -1))
+ return false;
+ std::string mod_name = readParam<std::string>(L, -1);
+
+ std::string value = g_settings->get(setting);
+ value.erase(std::remove(value.begin(), value.end(), ' '), value.end());
+ auto mod_list = str_split(value, ',');
+
+ return CONTAINS(mod_list, mod_name);
+}
+
int ScriptApiSecurity::sl_g_dofile(lua_State *L)
{
@@ -627,13 +699,7 @@ int ScriptApiSecurity::sl_g_load(lua_State *L)
int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
{
#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);
+ ScriptApiBase *script = ModApiBase::getScriptApiBase(L);
// Client implementation
if (script->getType() == ScriptingType::Client) {
@@ -806,3 +872,20 @@ int ScriptApiSecurity::sl_os_remove(lua_State *L)
return 2;
}
+
+int ScriptApiSecurity::sl_os_setlocale(lua_State *L)
+{
+ const bool cat = lua_gettop(L) > 1;
+ // Don't allow changes
+ if (!lua_isnoneornil(L, 1)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ push_original(L, "os", "setlocale");
+ lua_pushnil(L);
+ if (cat)
+ lua_pushvalue(L, 2);
+ lua_call(L, cat ? 2 : 1, 1);
+ return 1;
+}