}
-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 norel_path = fs::RemoveRelativePathComponents(path);
// Builtin can access anything
if (mod_name == BUILTIN_MOD_NAME) {
+ if (write_allowed) *write_allowed = true;
return true;
}
// Allow paths in mod path
- const ModSpec *mod = server->getModSpec(mod_name);
- if (mod) {
- str = fs::AbsolutePath(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;
}
}
}
- 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;
+ 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
if (lua_isstring(L, 1)) {
path = lua_tostring(L, 1);
- CHECK_SECURE_PATH(L, path);
+ CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
}
if (!safeLoadFile(L, path)) {
luaL_checktype(L, 1, LUA_TSTRING);
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");
lua_pushvalue(L, 1);
{
if (lua_isstring(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");
{
if (lua_isstring(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");
{
if (lua_isstring(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);
{
luaL_checktype(L, 1, LUA_TSTRING);
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);
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");
lua_pushvalue(L, 1);
{
luaL_checktype(L, 1, LUA_TSTRING);
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");
lua_pushvalue(L, 1);
#include "cpp_api/s_base.h"
-#define CHECK_SECURE_PATH(L, path) \
- if (!ScriptApiSecurity::checkPath(L, path)) { \
- throw LuaError(std::string("Attempt to access external file ") + \
- path + " with mod security on."); \
+#define CHECK_SECURE_PATH_INTERNAL(L, path, write_required, ptr) \
+ if (!ScriptApiSecurity::checkPath(L, path, write_required, ptr)) { \
+ throw LuaError(std::string("Mod security: Blocked attempted ") + \
+ (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)) { \
- 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); \
}
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);
+ // Checks if mods are allowed to read (and optionally write) to the path
+ static bool checkPath(lua_State *L, const char *path, bool write_required,
+ bool *write_allowed=NULL);
private:
// Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name>