From 4827ee1258ac9d68808ca4e2a9cb88bf49473e6b Mon Sep 17 00:00:00 2001
From: ShadowNinja <shadowninja@minetest.net>
Date: Thu, 18 Feb 2016 16:06:07 -0500
Subject: [PATCH] Require request_insecure_environment to be called from the
 mod's main scope

Previously you could steal a secure environment from a trusted mod by wrapping
request_insecure_environment with some code like this:

local rie_cp = minetest.request_insecure_environment
local stolen_ie
function minetest.request_insecure_environment()
	local ie = rie_cp()
	stolen_ie = stolen_ie or ie
	return ie
end
---
 doc/lua_api.txt               |  2 +-
 src/script/lua_api/l_util.cpp | 32 +++++++++++++++++++++++++++-----
 2 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index d9a8bea97..2df0cac7c 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -2437,7 +2437,7 @@ These functions return the leftover itemstack.
 * `minetest.request_insecure_environment()`: returns an environment containing
   insecure functions if the calling mod has been listed as trusted in the
   `secure.trusted_mods` setting or security is disabled, otherwise returns `nil`.
-    * Only works at init time.
+    * Only works at init time and must be called from the mod's main scope (not from a function).
     * **DO NOT ALLOW ANY OTHER MODS TO ACCESS THE RETURNED ENVIRONMENT, STORE IT IN
       A LOCAL VARIABLE!**
 
diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp
index 39863b987..c1e883a98 100644
--- a/src/script/lua_api/l_util.cpp
+++ b/src/script/lua_api/l_util.cpp
@@ -357,22 +357,44 @@ int ModApiUtil::l_get_dir_list(lua_State *L)
 int ModApiUtil::l_request_insecure_environment(lua_State *L)
 {
 	NO_MAP_LOCK_REQUIRED;
+
+	// Just return _G if security is disabled
 	if (!ScriptApiSecurity::isSecure(L)) {
 		lua_getglobal(L, "_G");
 		return 1;
 	}
+
+	// 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 0;
+	}
+	assert(lua_getstack(L, 1, &info));
+	assert(lua_getinfo(L, "S", &info));
+	// ...and that that item is the main file scope.
+	if (strcmp(info.what, "main") != 0) {
+		return 0;
+	}
+
+	// Get mod name
 	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
 	if (!lua_isstring(L, -1)) {
-		lua_pushnil(L);
-		return 1;
+		return 0;
 	}
+
+	// Check secure.trusted_mods
 	const char *mod_name = lua_tostring(L, -1);
 	std::string trusted_mods = g_settings->get("secure.trusted_mods");
 	std::vector<std::string> mod_list = str_split(trusted_mods, ',');
-	if (std::find(mod_list.begin(), mod_list.end(), mod_name) == mod_list.end()) {
-		lua_pushnil(L);
-		return 1;
+	if (std::find(mod_list.begin(), mod_list.end(), mod_name) ==
+			mod_list.end()) {
+		return 0;
 	}
+
+	// Push insecure environment
 	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
 	return 1;
 }
-- 
GitLab