forked from Mirrorlandia_minetest/minetest
Mod security: Allow read-only access to all mod paths
This commit is contained in:
@ -336,8 +336,12 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool ScriptApiSecurity::checkPath(lua_State *L, const char *path)
|
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 str; // Transient
|
||||||
|
|
||||||
std::string norel_path = fs::RemoveRelativePathComponents(path);
|
std::string norel_path = fs::RemoveRelativePathComponents(path);
|
||||||
@ -380,32 +384,53 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path)
|
|||||||
|
|
||||||
// Builtin can access anything
|
// Builtin can access anything
|
||||||
if (mod_name == BUILTIN_MOD_NAME) {
|
if (mod_name == BUILTIN_MOD_NAME) {
|
||||||
|
if (write_allowed) *write_allowed = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow paths in mod path
|
// Allow paths in mod path
|
||||||
const ModSpec *mod = server->getModSpec(mod_name);
|
// Don't bother if write access isn't important, since it will be handled later
|
||||||
if (mod) {
|
if (write_required || write_allowed != NULL) {
|
||||||
str = fs::AbsolutePath(mod->path);
|
const ModSpec *mod = server->getModSpec(mod_name);
|
||||||
if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
|
if (mod) {
|
||||||
return true;
|
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
|
lua_pop(L, 1); // Pop mod name
|
||||||
|
|
||||||
str = fs::AbsolutePath(server->getWorldPath());
|
// Allow read-only access to all mod directories
|
||||||
if (str.empty()) return false;
|
if (!write_required) {
|
||||||
// Don't allow access to world mods. We add to the absolute path
|
const std::vector<ModSpec> mods = server->getMods();
|
||||||
// of the world instead of getting the absolute paths directly
|
for (size_t i = 0; i < mods.size(); ++i) {
|
||||||
// because that won't work if they don't exist.
|
str = fs::AbsolutePath(mods[i].path);
|
||||||
if (fs::PathStartsWith(abs_path, str + DIR_DELIM + "worldmods") ||
|
if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
|
||||||
fs::PathStartsWith(abs_path, str + DIR_DELIM + "game")) {
|
return true;
|
||||||
return false;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Allow all other paths in world path
|
|
||||||
if (fs::PathStartsWith(abs_path, str)) {
|
str = fs::AbsolutePath(server->getWorldPath());
|
||||||
return true;
|
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
|
// Default to disallowing
|
||||||
@ -476,7 +501,7 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
|
|||||||
|
|
||||||
if (lua_isstring(L, 1)) {
|
if (lua_isstring(L, 1)) {
|
||||||
path = lua_tostring(L, 1);
|
path = lua_tostring(L, 1);
|
||||||
CHECK_SECURE_PATH(L, path);
|
CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!safeLoadFile(L, path)) {
|
if (!safeLoadFile(L, path)) {
|
||||||
@ -529,7 +554,16 @@ int ScriptApiSecurity::sl_io_open(lua_State *L)
|
|||||||
|
|
||||||
luaL_checktype(L, 1, LUA_TSTRING);
|
luaL_checktype(L, 1, LUA_TSTRING);
|
||||||
const char *path = lua_tostring(L, 1);
|
const char *path = lua_tostring(L, 1);
|
||||||
CHECK_SECURE_PATH(L, path);
|
|
||||||
|
bool write_requested = false;
|
||||||
|
if (with_mode) {
|
||||||
|
luaL_checktype(L, 2, LUA_TSTRING);
|
||||||
|
const char *mode = lua_tostring(L, 2);
|
||||||
|
write_requested = strchr(mode, 'w') != NULL ||
|
||||||
|
strchr(mode, '+') != NULL ||
|
||||||
|
strchr(mode, 'a') != NULL;
|
||||||
|
}
|
||||||
|
CHECK_SECURE_PATH_INTERNAL(L, path, write_requested, NULL);
|
||||||
|
|
||||||
push_original(L, "io", "open");
|
push_original(L, "io", "open");
|
||||||
lua_pushvalue(L, 1);
|
lua_pushvalue(L, 1);
|
||||||
@ -546,7 +580,7 @@ int ScriptApiSecurity::sl_io_input(lua_State *L)
|
|||||||
{
|
{
|
||||||
if (lua_isstring(L, 1)) {
|
if (lua_isstring(L, 1)) {
|
||||||
const char *path = lua_tostring(L, 1);
|
const char *path = lua_tostring(L, 1);
|
||||||
CHECK_SECURE_PATH(L, path);
|
CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
push_original(L, "io", "input");
|
push_original(L, "io", "input");
|
||||||
@ -560,7 +594,7 @@ int ScriptApiSecurity::sl_io_output(lua_State *L)
|
|||||||
{
|
{
|
||||||
if (lua_isstring(L, 1)) {
|
if (lua_isstring(L, 1)) {
|
||||||
const char *path = lua_tostring(L, 1);
|
const char *path = lua_tostring(L, 1);
|
||||||
CHECK_SECURE_PATH(L, path);
|
CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
push_original(L, "io", "output");
|
push_original(L, "io", "output");
|
||||||
@ -574,7 +608,7 @@ int ScriptApiSecurity::sl_io_lines(lua_State *L)
|
|||||||
{
|
{
|
||||||
if (lua_isstring(L, 1)) {
|
if (lua_isstring(L, 1)) {
|
||||||
const char *path = lua_tostring(L, 1);
|
const char *path = lua_tostring(L, 1);
|
||||||
CHECK_SECURE_PATH(L, path);
|
CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int top_precall = lua_gettop(L);
|
int top_precall = lua_gettop(L);
|
||||||
@ -591,11 +625,11 @@ int ScriptApiSecurity::sl_os_rename(lua_State *L)
|
|||||||
{
|
{
|
||||||
luaL_checktype(L, 1, LUA_TSTRING);
|
luaL_checktype(L, 1, LUA_TSTRING);
|
||||||
const char *path1 = lua_tostring(L, 1);
|
const char *path1 = lua_tostring(L, 1);
|
||||||
CHECK_SECURE_PATH(L, path1);
|
CHECK_SECURE_PATH_INTERNAL(L, path1, true, NULL);
|
||||||
|
|
||||||
luaL_checktype(L, 2, LUA_TSTRING);
|
luaL_checktype(L, 2, LUA_TSTRING);
|
||||||
const char *path2 = lua_tostring(L, 2);
|
const char *path2 = lua_tostring(L, 2);
|
||||||
CHECK_SECURE_PATH(L, path2);
|
CHECK_SECURE_PATH_INTERNAL(L, path2, true, NULL);
|
||||||
|
|
||||||
push_original(L, "os", "rename");
|
push_original(L, "os", "rename");
|
||||||
lua_pushvalue(L, 1);
|
lua_pushvalue(L, 1);
|
||||||
@ -609,7 +643,7 @@ int ScriptApiSecurity::sl_os_remove(lua_State *L)
|
|||||||
{
|
{
|
||||||
luaL_checktype(L, 1, LUA_TSTRING);
|
luaL_checktype(L, 1, LUA_TSTRING);
|
||||||
const char *path = lua_tostring(L, 1);
|
const char *path = lua_tostring(L, 1);
|
||||||
CHECK_SECURE_PATH(L, path);
|
CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL);
|
||||||
|
|
||||||
push_original(L, "os", "remove");
|
push_original(L, "os", "remove");
|
||||||
lua_pushvalue(L, 1);
|
lua_pushvalue(L, 1);
|
||||||
|
@ -23,14 +23,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "cpp_api/s_base.h"
|
#include "cpp_api/s_base.h"
|
||||||
|
|
||||||
|
|
||||||
#define CHECK_SECURE_PATH(L, path) \
|
#define CHECK_SECURE_PATH_INTERNAL(L, path, write_required, ptr) \
|
||||||
if (!ScriptApiSecurity::checkPath(L, path)) { \
|
if (!ScriptApiSecurity::checkPath(L, path, write_required, ptr)) { \
|
||||||
throw LuaError(std::string("Attempt to access external file ") + \
|
throw LuaError(std::string("Mod security: Blocked attempted ") + \
|
||||||
path + " with mod security on."); \
|
(write_required ? "write to " : "read from ") + path); \
|
||||||
}
|
}
|
||||||
#define CHECK_SECURE_PATH_OPTIONAL(L, path) \
|
#define CHECK_SECURE_PATH(L, path, write_required) \
|
||||||
if (ScriptApiSecurity::isSecure(L)) { \
|
if (ScriptApiSecurity::isSecure(L)) { \
|
||||||
CHECK_SECURE_PATH(L, path); \
|
CHECK_SECURE_PATH_INTERNAL(L, path, write_required, NULL); \
|
||||||
|
}
|
||||||
|
#define CHECK_SECURE_PATH_POSSIBLE_WRITE(L, path, ptr) \
|
||||||
|
if (ScriptApiSecurity::isSecure(L)) { \
|
||||||
|
CHECK_SECURE_PATH_INTERNAL(L, path, false, ptr); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -43,8 +47,9 @@ public:
|
|||||||
static bool isSecure(lua_State *L);
|
static bool isSecure(lua_State *L);
|
||||||
// Loads a file as Lua code safely (doesn't allow bytecode).
|
// Loads a file as Lua code safely (doesn't allow bytecode).
|
||||||
static bool safeLoadFile(lua_State *L, const char *path);
|
static bool safeLoadFile(lua_State *L, const char *path);
|
||||||
// Checks if mods are allowed to read and write to the path
|
// Checks if mods are allowed to read (and optionally write) to the path
|
||||||
static bool checkPath(lua_State *L, const char *path);
|
static bool checkPath(lua_State *L, const char *path, bool write_required,
|
||||||
|
bool *write_allowed=NULL);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name>
|
// Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name>
|
||||||
|
@ -263,7 +263,7 @@ int LuaAreaStore::l_to_file(lua_State *L)
|
|||||||
AreaStore *ast = o->as;
|
AreaStore *ast = o->as;
|
||||||
|
|
||||||
const char *filename = luaL_checkstring(L, 2);
|
const char *filename = luaL_checkstring(L, 2);
|
||||||
CHECK_SECURE_PATH_OPTIONAL(L, filename);
|
CHECK_SECURE_PATH(L, filename, true);
|
||||||
|
|
||||||
std::ostringstream os(std::ios_base::binary);
|
std::ostringstream os(std::ios_base::binary);
|
||||||
ast->serialize(os);
|
ast->serialize(os);
|
||||||
@ -294,7 +294,7 @@ int LuaAreaStore::l_from_file(lua_State *L)
|
|||||||
LuaAreaStore *o = checkobject(L, 1);
|
LuaAreaStore *o = checkobject(L, 1);
|
||||||
|
|
||||||
const char *filename = luaL_checkstring(L, 2);
|
const char *filename = luaL_checkstring(L, 2);
|
||||||
CHECK_SECURE_PATH_OPTIONAL(L, filename);
|
CHECK_SECURE_PATH(L, filename, false);
|
||||||
|
|
||||||
std::ifstream is(filename, std::ios::binary);
|
std::ifstream is(filename, std::ios::binary);
|
||||||
return deserialization_helper(L, o->as, is);
|
return deserialization_helper(L, o->as, is);
|
||||||
|
@ -1295,7 +1295,7 @@ int ModApiMapgen::l_create_schematic(lua_State *L)
|
|||||||
INodeDefManager *ndef = getServer(L)->getNodeDefManager();
|
INodeDefManager *ndef = getServer(L)->getNodeDefManager();
|
||||||
|
|
||||||
const char *filename = luaL_checkstring(L, 4);
|
const char *filename = luaL_checkstring(L, 4);
|
||||||
CHECK_SECURE_PATH_OPTIONAL(L, filename);
|
CHECK_SECURE_PATH(L, filename, true);
|
||||||
|
|
||||||
Map *map = &(getEnv(L)->getMap());
|
Map *map = &(getEnv(L)->getMap());
|
||||||
Schematic schem;
|
Schematic schem;
|
||||||
|
@ -118,6 +118,11 @@ int LuaSettings::l_write(lua_State* L)
|
|||||||
NO_MAP_LOCK_REQUIRED;
|
NO_MAP_LOCK_REQUIRED;
|
||||||
LuaSettings* o = checkobject(L, 1);
|
LuaSettings* o = checkobject(L, 1);
|
||||||
|
|
||||||
|
if (!o->m_write_allowed) {
|
||||||
|
throw LuaError("Settings: writing " + o->m_filename +
|
||||||
|
" not allowed with mod security on.");
|
||||||
|
}
|
||||||
|
|
||||||
bool success = o->m_settings->updateConfigFile(o->m_filename.c_str());
|
bool success = o->m_settings->updateConfigFile(o->m_filename.c_str());
|
||||||
lua_pushboolean(L, success);
|
lua_pushboolean(L, success);
|
||||||
|
|
||||||
@ -142,8 +147,9 @@ int LuaSettings::l_to_table(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaSettings::LuaSettings(const char* filename)
|
LuaSettings::LuaSettings(const char* filename, bool write_allowed)
|
||||||
{
|
{
|
||||||
|
m_write_allowed = write_allowed;
|
||||||
m_filename = std::string(filename);
|
m_filename = std::string(filename);
|
||||||
|
|
||||||
m_settings = new Settings();
|
m_settings = new Settings();
|
||||||
@ -188,9 +194,10 @@ void LuaSettings::Register(lua_State* L)
|
|||||||
int LuaSettings::create_object(lua_State* L)
|
int LuaSettings::create_object(lua_State* L)
|
||||||
{
|
{
|
||||||
NO_MAP_LOCK_REQUIRED;
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
bool write_allowed;
|
||||||
const char* filename = luaL_checkstring(L, 1);
|
const char* filename = luaL_checkstring(L, 1);
|
||||||
CHECK_SECURE_PATH_OPTIONAL(L, filename);
|
CHECK_SECURE_PATH_POSSIBLE_WRITE(L, filename, &write_allowed);
|
||||||
LuaSettings* o = new LuaSettings(filename);
|
LuaSettings* o = new LuaSettings(filename, write_allowed);
|
||||||
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
|
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
|
||||||
luaL_getmetatable(L, className);
|
luaL_getmetatable(L, className);
|
||||||
lua_setmetatable(L, -2);
|
lua_setmetatable(L, -2);
|
||||||
|
@ -53,11 +53,12 @@ private:
|
|||||||
// to_table(self) -> {[key1]=value1,...}
|
// to_table(self) -> {[key1]=value1,...}
|
||||||
static int l_to_table(lua_State* L);
|
static int l_to_table(lua_State* L);
|
||||||
|
|
||||||
|
bool m_write_allowed;
|
||||||
Settings* m_settings;
|
Settings* m_settings;
|
||||||
std::string m_filename;
|
std::string m_filename;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LuaSettings(const char* filename);
|
LuaSettings(const char* filename, bool write_allowed);
|
||||||
~LuaSettings();
|
~LuaSettings();
|
||||||
|
|
||||||
// LuaSettings(filename)
|
// LuaSettings(filename)
|
||||||
|
@ -388,7 +388,7 @@ int ModApiUtil::l_mkdir(lua_State *L)
|
|||||||
{
|
{
|
||||||
NO_MAP_LOCK_REQUIRED;
|
NO_MAP_LOCK_REQUIRED;
|
||||||
const char *path = luaL_checkstring(L, 1);
|
const char *path = luaL_checkstring(L, 1);
|
||||||
CHECK_SECURE_PATH_OPTIONAL(L, path);
|
CHECK_SECURE_PATH(L, path, true);
|
||||||
lua_pushboolean(L, fs::CreateAllDirs(path));
|
lua_pushboolean(L, fs::CreateAllDirs(path));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -400,7 +400,7 @@ int ModApiUtil::l_get_dir_list(lua_State *L)
|
|||||||
const char *path = luaL_checkstring(L, 1);
|
const char *path = luaL_checkstring(L, 1);
|
||||||
short is_dir = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : -1;
|
short is_dir = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : -1;
|
||||||
|
|
||||||
CHECK_SECURE_PATH_OPTIONAL(L, path);
|
CHECK_SECURE_PATH(L, path, false);
|
||||||
|
|
||||||
std::vector<fs::DirListNode> list = fs::GetDirListing(path);
|
std::vector<fs::DirListNode> list = fs::GetDirListing(path);
|
||||||
|
|
||||||
|
@ -298,6 +298,7 @@ public:
|
|||||||
IWritableNodeDefManager* getWritableNodeDefManager();
|
IWritableNodeDefManager* getWritableNodeDefManager();
|
||||||
IWritableCraftDefManager* getWritableCraftDefManager();
|
IWritableCraftDefManager* getWritableCraftDefManager();
|
||||||
|
|
||||||
|
const std::vector<ModSpec> &getMods() const { return m_mods; }
|
||||||
const ModSpec* getModSpec(const std::string &modname) const;
|
const ModSpec* getModSpec(const std::string &modname) const;
|
||||||
void getModNames(std::vector<std::string> &modlist);
|
void getModNames(std::vector<std::string> &modlist);
|
||||||
std::string getBuiltinLuaPath();
|
std::string getBuiltinLuaPath();
|
||||||
|
Reference in New Issue
Block a user