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; }
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 abs_path = fs::AbsolutePath(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 = path; std::string removed; while (abs_path.empty() && !cur_path.empty()) { 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; // 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_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; // 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); // Builtin can access anything if (mod_name == BUILTIN_MOD_NAME) { if (write_allowed) *write_allowed = true; return true; } // Allow paths in mod path // Don't bother if write access isn't important, since it will be handled later if (write_required || write_allowed != NULL) { const ModSpec *mod = server->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 // Allow read-only access to all mod directories if (!write_required) { const std::vector<ModSpec> mods = server->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; } } } str = fs::AbsolutePath(server->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 return false; }