Add mod security

Due to compatibility concerns, this is temporarily disabled.
This commit is contained in:
ShadowNinja 2014-09-05 20:08:51 -04:00
parent f26421228b
commit 3a8c788880
22 changed files with 812 additions and 83 deletions

@ -262,6 +262,7 @@ LOCAL_SRC_FILES += \
jni/src/script/common/c_converter.cpp \ jni/src/script/common/c_converter.cpp \
jni/src/script/common/c_internal.cpp \ jni/src/script/common/c_internal.cpp \
jni/src/script/common/c_types.cpp \ jni/src/script/common/c_types.cpp \
jni/src/script/cpp_api/s_async.cpp \
jni/src/script/cpp_api/s_base.cpp \ jni/src/script/cpp_api/s_base.cpp \
jni/src/script/cpp_api/s_entity.cpp \ jni/src/script/cpp_api/s_entity.cpp \
jni/src/script/cpp_api/s_env.cpp \ jni/src/script/cpp_api/s_env.cpp \
@ -271,8 +272,8 @@ LOCAL_SRC_FILES += \
jni/src/script/cpp_api/s_node.cpp \ jni/src/script/cpp_api/s_node.cpp \
jni/src/script/cpp_api/s_nodemeta.cpp \ jni/src/script/cpp_api/s_nodemeta.cpp \
jni/src/script/cpp_api/s_player.cpp \ jni/src/script/cpp_api/s_player.cpp \
jni/src/script/cpp_api/s_security.cpp \
jni/src/script/cpp_api/s_server.cpp \ jni/src/script/cpp_api/s_server.cpp \
jni/src/script/cpp_api/s_async.cpp \
jni/src/script/lua_api/l_base.cpp \ jni/src/script/lua_api/l_base.cpp \
jni/src/script/lua_api/l_craft.cpp \ jni/src/script/lua_api/l_craft.cpp \
jni/src/script/lua_api/l_env.cpp \ jni/src/script/lua_api/l_env.cpp \

@ -1825,8 +1825,12 @@ Call these functions only at load time!
### Setting-related ### Setting-related
* `minetest.setting_set(name, value)` * `minetest.setting_set(name, value)`
* Setting names can't contain whitespace or any of `="{}#`.
* Setting values can't contain the sequence `\n"""`.
* Setting names starting with "secure." can't be set.
* `minetest.setting_get(name)`: returns string or `nil` * `minetest.setting_get(name)`: returns string or `nil`
* `minetest.setting_setbool(name, value)` * `minetest.setting_setbool(name, value)`
* See documentation on `setting_set` for restrictions.
* `minetest.setting_getbool(name)`: returns boolean or `nil` * `minetest.setting_getbool(name)`: returns boolean or `nil`
* `minetest.setting_get_pos(name)`: returns position or nil * `minetest.setting_get_pos(name)`: returns position or nil
* `minetest.setting_save()`, returns `nil`, save all settings to config file * `minetest.setting_save()`, returns `nil`, save all settings to config file

@ -569,3 +569,6 @@
#mgv7_np_cave1 = 0, 12, (100, 100, 100), 52534, 4, 0.5, 2.0 #mgv7_np_cave1 = 0, 12, (100, 100, 100), 52534, 4, 0.5, 2.0
#mgv7_np_cave2 = 0, 12, (100, 100, 100), 10325, 4, 0.5, 2.0 #mgv7_np_cave2 = 0, 12, (100, 100, 100), 10325, 4, 0.5, 2.0
# Prevent mods from doing insecure things like running shell commands.
#secure.enable_security = false

@ -272,6 +272,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("emergequeue_limit_diskonly", "32"); settings->setDefault("emergequeue_limit_diskonly", "32");
settings->setDefault("emergequeue_limit_generate", "32"); settings->setDefault("emergequeue_limit_generate", "32");
settings->setDefault("num_emerge_threads", "1"); settings->setDefault("num_emerge_threads", "1");
settings->setDefault("secure.enable_security", "false");
// physics stuff // physics stuff
settings->setDefault("movement_acceleration_default", "3"); settings->setDefault("movement_acceleration_default", "3");

@ -662,6 +662,19 @@ std::string RemoveRelativePathComponents(std::string path)
return path.substr(0, pos); return path.substr(0, pos);
} }
std::string AbsolutePath(const std::string &path)
{
#ifdef _WIN32
char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH);
#else
char *abs_path = realpath(path.c_str(), NULL);
#endif
if (!abs_path) return "";
std::string abs_path_str(abs_path);
free(abs_path);
return abs_path_str;
}
const char *GetFilenameFromPath(const char *path) const char *GetFilenameFromPath(const char *path)
{ {
const char *filename = strrchr(path, DIR_DELIM_CHAR); const char *filename = strrchr(path, DIR_DELIM_CHAR);

@ -103,13 +103,17 @@ std::string RemoveLastPathComponent(const std::string &path,
// this does not resolve symlinks and check for existence of directories. // this does not resolve symlinks and check for existence of directories.
std::string RemoveRelativePathComponents(std::string path); std::string RemoveRelativePathComponents(std::string path);
// Return the filename from a path or the entire path if no directory delimiter // Returns the absolute path for the passed path, with "." and ".." path
// is found. // components and symlinks removed. Returns "" on error.
std::string AbsolutePath(const std::string &path);
// Returns the filename from a path or the entire path if no directory
// delimiter is found.
const char *GetFilenameFromPath(const char *path); const char *GetFilenameFromPath(const char *path);
bool safeWriteToFile(const std::string &path, const std::string &content); bool safeWriteToFile(const std::string &path, const std::string &content);
}//fs } // namespace fs
#endif #endif

@ -63,10 +63,8 @@ int script_exception_wrapper(lua_State *L, lua_CFunction f)
return f(L); // Call wrapped function and return result. return f(L); // Call wrapped function and return result.
} catch (const char *s) { // Catch and convert exceptions. } catch (const char *s) { // Catch and convert exceptions.
lua_pushstring(L, s); lua_pushstring(L, s);
} catch (std::exception& e) { } catch (std::exception &e) {
lua_pushstring(L, e.what()); lua_pushstring(L, e.what());
} catch (...) {
lua_pushliteral(L, "caught (...)");
} }
return lua_error(L); // Rethrow as a Lua error. return lua_error(L); // Rethrow as a Lua error.
} }

@ -1,4 +1,5 @@
set(common_SCRIPT_CPP_API_SRCS 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_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_entity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_entity.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_env.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_node.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_player.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_server.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_async.cpp
PARENT_SCOPE) PARENT_SCOPE)
set(client_SCRIPT_CPP_API_SRCS set(client_SCRIPT_CPP_API_SRCS

@ -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_base.h"
#include "cpp_api/s_internal.h" #include "cpp_api/s_internal.h"
#include "cpp_api/s_security.h"
#include "lua_api/l_object.h" #include "lua_api/l_object.h"
#include "serverobject.h" #include "serverobject.h"
#include "debug.h" #include "debug.h"
@ -45,18 +46,18 @@ class ModNameStorer
private: private:
lua_State *L; lua_State *L;
public: public:
ModNameStorer(lua_State *L_, const std::string &modname): ModNameStorer(lua_State *L_, const std::string &mod_name):
L(L_) L(L_)
{ {
// Store current modname in registry // Store current mod name in registry
lua_pushstring(L, modname.c_str()); lua_pushstring(L, mod_name.c_str());
lua_setfield(L, LUA_REGISTRYINDEX, "current_modname"); lua_setfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD);
} }
~ModNameStorer() ~ModNameStorer()
{ {
// Clear current modname in registry // Clear current mod name from registry
lua_pushnil(L); 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); lua_close(m_luastack);
} }
bool ScriptApiBase::loadMod(const std::string &scriptpath, bool ScriptApiBase::loadMod(const std::string &script_path,
const std::string &modname) const std::string &mod_name)
{ {
ModNameStorer modnamestorer(getStack(), modname); ModNameStorer mod_name_storer(getStack(), mod_name);
if (!string_allowed(modname, MODNAME_ALLOWED_CHARS)) { return loadScript(script_path);
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);
} }
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(); lua_State *L = getStack();
int ret = luaL_loadfile(L, scriptpath.c_str()) || lua_pcall(L, 0, 0, m_errorhandler); bool ok;
if (ret) { 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 << "========== ERROR FROM LUA ===========" << std::endl;
errorstream << "Failed to load and run script from " << 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 << std::endl;
errorstream << lua_tostring(L, -1) << std::endl; errorstream << lua_tostring(L, -1) << std::endl;
errorstream << std::endl; errorstream << std::endl;

@ -35,6 +35,12 @@ extern "C" {
#define SCRIPTAPI_LOCK_DEBUG #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 Server;
class Environment; class Environment;
class GUIEngine; class GUIEngine;
@ -42,17 +48,18 @@ class ServerActiveObject;
class ScriptApiBase { class ScriptApiBase {
public: public:
ScriptApiBase(); ScriptApiBase();
virtual ~ScriptApiBase(); virtual ~ScriptApiBase();
bool loadMod(const std::string &scriptpath, const std::string &modname); bool loadMod(const std::string &script_path, const std::string &mod_name);
bool loadScript(const std::string &scriptpath); bool loadScript(const std::string &script_path);
/* object */ /* object */
void addObjectReference(ServerActiveObject *cobj); void addObjectReference(ServerActiveObject *cobj);
void removeObjectReference(ServerActiveObject *cobj); void removeObjectReference(ServerActiveObject *cobj);
Server* getServer() { return m_server; }
protected: protected:
friend class LuaABM; friend class LuaABM;
friend class InvRef; friend class InvRef;
@ -69,7 +76,6 @@ protected:
void scriptError(); void scriptError();
void stackDump(std::ostream &o); void stackDump(std::ostream &o);
Server* getServer() { return m_server; }
void setServer(Server* server) { m_server = server; } void setServer(Server* server) { m_server = server; }
Environment* getEnv() { return m_environment; } Environment* getEnv() { return m_environment; }
@ -84,6 +90,7 @@ protected:
JMutex m_luastackmutex; JMutex m_luastackmutex;
// Stack index of Lua error handler // Stack index of Lua error handler
int m_errorhandler; int m_errorhandler;
bool m_secure;
#ifdef SCRIPTAPI_LOCK_DEBUG #ifdef SCRIPTAPI_LOCK_DEBUG
bool m_locked; bool m_locked;
#endif #endif

@ -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;
}

@ -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

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_vmanip.h" #include "lua_api/l_vmanip.h"
#include "common/c_converter.h" #include "common/c_converter.h"
#include "common/c_content.h" #include "common/c_content.h"
#include "cpp_api/s_security.h"
#include "util/serialize.h" #include "util/serialize.h"
#include "server.h" #include "server.h"
#include "environment.h" #include "environment.h"
@ -1031,6 +1032,10 @@ int ModApiMapgen::l_generate_decorations(lua_State *L)
int ModApiMapgen::l_create_schematic(lua_State *L) 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);
CHECK_SECURE_PATH_OPTIONAL(L, filename);
Map *map = &(getEnv(L)->getMap()); Map *map = &(getEnv(L)->getMap());
Schematic schem; 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)) { if (!schem.getSchematicFromMap(map, p1, p2)) {
errorstream << "create_schematic: failed to get schematic " errorstream << "create_schematic: failed to get schematic "
"from map" << std::endl; "from map" << std::endl;

@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_internal.h" #include "lua_api/l_internal.h"
#include "common/c_converter.h" #include "common/c_converter.h"
#include "common/c_content.h" #include "common/c_content.h"
#include "cpp_api/s_base.h"
#include "server.h" #include "server.h"
#include "environment.h" #include "environment.h"
#include "player.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) int ModApiServer::l_get_current_modname(lua_State *L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
lua_getfield(L, LUA_REGISTRYINDEX, "current_modname"); lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD);
return 1; return 1;
} }

@ -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_settings.h"
#include "lua_api/l_internal.h" #include "lua_api/l_internal.h"
#include "cpp_api/s_security.h"
#include "settings.h" #include "settings.h"
#include "log.h" #include "log.h"
@ -188,6 +189,7 @@ int LuaSettings::create_object(lua_State* L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
const char* filename = luaL_checkstring(L, 1); const char* filename = luaL_checkstring(L, 1);
CHECK_SECURE_PATH_OPTIONAL(L, filename);
LuaSettings* o = new LuaSettings(filename); LuaSettings* o = new LuaSettings(filename);
*(void **)(lua_newuserdata(L, sizeof(void *))) = o; *(void **)(lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className); luaL_getmetatable(L, className);

@ -92,12 +92,19 @@ int ModApiUtil::l_log(lua_State *L)
return 0; 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) // setting_set(name, value)
int ModApiUtil::l_setting_set(lua_State *L) int ModApiUtil::l_setting_set(lua_State *L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
const char *name = luaL_checkstring(L, 1); std::string name = luaL_checkstring(L, 1);
const char *value = luaL_checkstring(L, 2); std::string value = luaL_checkstring(L, 2);
CHECK_SECURE_SETTING(L, name);
g_settings->set(name, value); g_settings->set(name, value);
return 0; return 0;
} }
@ -120,8 +127,9 @@ int ModApiUtil::l_setting_get(lua_State *L)
int ModApiUtil::l_setting_setbool(lua_State *L) int ModApiUtil::l_setting_setbool(lua_State *L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
const char *name = luaL_checkstring(L, 1); std::string name = luaL_checkstring(L, 1);
bool value = lua_toboolean(L, 2); bool value = lua_toboolean(L, 2);
CHECK_SECURE_SETTING(L, name);
g_settings->setBool(name, value); g_settings->setBool(name, value);
return 0; return 0;
} }

@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "scripting_game.h" #include "scripting_game.h"
#include "server.h" #include "server.h"
#include "log.h" #include "log.h"
#include "settings.h"
#include "cpp_api/s_internal.h" #include "cpp_api/s_internal.h"
#include "lua_api/l_base.h" #include "lua_api/l_base.h"
#include "lua_api/l_craft.h" #include "lua_api/l_craft.h"
@ -49,10 +50,12 @@ GameScripting::GameScripting(Server* server)
// setEnv(env) is called by ScriptApiEnv::initializeEnvironment() // setEnv(env) is called by ScriptApiEnv::initializeEnvironment()
// once the environment has been created // once the environment has been created
//TODO add security
SCRIPTAPI_PRECHECKHEADER SCRIPTAPI_PRECHECKHEADER
if (g_settings->getBool("secure.enable_security")) {
initializeSecurity();
}
lua_getglobal(L, "core"); lua_getglobal(L, "core");
int top = lua_gettop(L); int top = lua_gettop(L);

@ -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_node.h"
#include "cpp_api/s_player.h" #include "cpp_api/s_player.h"
#include "cpp_api/s_server.h" #include "cpp_api/s_server.h"
#include "cpp_api/s_security.h"
/*****************************************************************************/ /*****************************************************************************/
/* Scripting <-> Game Interface */ /* Scripting <-> Game Interface */
/*****************************************************************************/ /*****************************************************************************/
class GameScripting class GameScripting :
: virtual public ScriptApiBase, virtual public ScriptApiBase,
public ScriptApiDetached, public ScriptApiDetached,
public ScriptApiEntity, public ScriptApiEntity,
public ScriptApiEnv, public ScriptApiEnv,
public ScriptApiNode, public ScriptApiNode,
public ScriptApiPlayer, public ScriptApiPlayer,
public ScriptApiServer public ScriptApiServer,
public ScriptApiSecurity
{ {
public: public:
GameScripting(Server* server); GameScripting(Server* server);

@ -38,8 +38,6 @@ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine)
{ {
setGuiEngine(guiengine); setGuiEngine(guiengine);
//TODO add security
SCRIPTAPI_PRECHECKHEADER SCRIPTAPI_PRECHECKHEADER
lua_getglobal(L, "core"); lua_getglobal(L, "core");

@ -295,31 +295,37 @@ Server::Server(
m_script = new GameScripting(this); m_script = new GameScripting(this);
std::string scriptpath = getBuiltinLuaPath() + DIR_DELIM "init.lua"; std::string script_path = getBuiltinLuaPath() + DIR_DELIM "init.lua";
if (!m_script->loadScript(scriptpath)) if (!m_script->loadMod(script_path, BUILTIN_MOD_NAME)) {
throw ModError("Failed to load and run " + scriptpath); throw ModError("Failed to load and run " + script_path);
// Print 'em
infostream<<"Server: Loading mods: ";
for(std::vector<ModSpec>::iterator i = m_mods.begin();
i != m_mods.end(); i++){
const ModSpec &mod = *i;
infostream<<mod.name<<" ";
} }
infostream<<std::endl;
// Load and run "mod" scripts // Print mods
infostream << "Server: Loading mods: ";
for(std::vector<ModSpec>::iterator i = m_mods.begin(); for(std::vector<ModSpec>::iterator i = m_mods.begin();
i != m_mods.end(); i++){ i != m_mods.end(); i++){
const ModSpec &mod = *i; const ModSpec &mod = *i;
std::string scriptpath = mod.path + DIR_DELIM + "init.lua"; infostream << mod.name << " ";
infostream<<" ["<<padStringRight(mod.name, 12)<<"] [\"" }
<<scriptpath<<"\"]"<<std::endl; infostream << std::endl;
bool success = m_script->loadMod(scriptpath, mod.name); // Load and run "mod" scripts
if(!success){ for (std::vector<ModSpec>::iterator i = m_mods.begin();
errorstream<<"Server: Failed to load and run " i != m_mods.end(); i++) {
<<scriptpath<<std::endl; const ModSpec &mod = *i;
throw ModError("Failed to load and run "+scriptpath); if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
errorstream << "Error loading mod \"" << mod.name
<< "\": mod_name does not follow naming conventions: "
<< "Only chararacters [a-z0-9_] are allowed." << std::endl;
throw ModError("Mod \"" + mod.name + "\" does not follow naming conventions.");
}
std::string script_path = mod.path + DIR_DELIM "init.lua";
infostream << " [" << padStringRight(mod.name, 12) << "] [\""
<< script_path << "\"]" << std::endl;
if (!m_script->loadMod(script_path, mod.name)) {
errorstream << "Server: Failed to load and run "
<< script_path << std::endl;
throw ModError("Failed to load and run " + script_path);
} }
} }
@ -3206,9 +3212,9 @@ IWritableCraftDefManager* Server::getWritableCraftDefManager()
return m_craftdef; return m_craftdef;
} }
const ModSpec* Server::getModSpec(const std::string &modname) const ModSpec* Server::getModSpec(const std::string &modname) const
{ {
for(std::vector<ModSpec>::iterator i = m_mods.begin(); for(std::vector<ModSpec>::const_iterator i = m_mods.begin();
i != m_mods.end(); i++){ i != m_mods.end(); i++){
const ModSpec &mod = *i; const ModSpec &mod = *i;
if(mod.name == modname) if(mod.name == modname)

@ -322,10 +322,10 @@ public:
IWritableNodeDefManager* getWritableNodeDefManager(); IWritableNodeDefManager* getWritableNodeDefManager();
IWritableCraftDefManager* getWritableCraftDefManager(); IWritableCraftDefManager* getWritableCraftDefManager();
const ModSpec* getModSpec(const std::string &modname); 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();
inline std::string getWorldPath() inline std::string getWorldPath() const
{ return m_path_world; } { return m_path_world; }
inline bool isSingleplayer() inline bool isSingleplayer()

@ -68,10 +68,11 @@ Settings & Settings::operator = (const Settings &other)
bool Settings::checkNameValid(const std::string &name) bool Settings::checkNameValid(const std::string &name)
{ {
size_t pos = name.find_first_of("\t\n\v\f\r\b =\"{}#"); bool valid = name.find_first_of("=\"{}#") == std::string::npos;
if (pos != std::string::npos) { if (valid) valid = trim(name) == name;
errorstream << "Invalid character '" << name[pos] if (!valid) {
<< "' found in setting name" << std::endl; errorstream << "Invalid setting name \"" << name << "\""
<< std::endl;
return false; return false;
} }
return true; return true;
@ -83,7 +84,7 @@ bool Settings::checkValueValid(const std::string &value)
if (value.substr(0, 3) == "\"\"\"" || if (value.substr(0, 3) == "\"\"\"" ||
value.find("\n\"\"\"") != std::string::npos) { value.find("\n\"\"\"") != std::string::npos) {
errorstream << "Invalid character sequence '\"\"\"' found in" errorstream << "Invalid character sequence '\"\"\"' found in"
" setting value" << std::endl; " setting value!" << std::endl;
return false; return false;
} }
return true; return true;
@ -92,9 +93,9 @@ bool Settings::checkValueValid(const std::string &value)
std::string Settings::sanitizeName(const std::string &name) std::string Settings::sanitizeName(const std::string &name)
{ {
std::string n(name); std::string n = trim(name);
for (const char *s = "\t\n\v\f\r\b =\"{}#"; *s; s++) for (const char *s = "=\"{}#"; *s; s++)
n.erase(std::remove(n.begin(), n.end(), *s), n.end()); n.erase(std::remove(n.begin(), n.end(), *s), n.end());
return n; return n;