mirror of
https://github.com/Mr-X-GTA/YimMenu.git
synced 2026-10-10 03:43:07 +08:00
refactor(LuaModule): isolate configs per module (#199)
* refactor(LuaModule): isolate configs per module - Give each Lua module its own config folder. - Add sandboxed `os.rename` limited to the module's config folder. This allows atomic writes, useful for large config files that may get corrupted due to interruptions mid-write. * fix(os.rename): add documentation and properly handle errors * fix(lua_module): fix make_absolute - Reject absolute user paths. - Canonicalize root path. - Fix `os.rename` docs. - lua_manager: validate module names and skip loading modules with names that contain illegal characters.
This commit is contained in:
+36
-2
@@ -1,4 +1,4 @@
|
||||
#include "lua_manager.hpp"
|
||||
#include "lua_manager.hpp"
|
||||
|
||||
#include "file_manager.hpp"
|
||||
|
||||
@@ -308,6 +308,34 @@ namespace big
|
||||
});
|
||||
}
|
||||
|
||||
static inline bool validate_module_name(const std::string& name)
|
||||
{
|
||||
if (name.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name == "." || name == "..")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.find("/") != std::string::npos || name.find("\\") != std::string::npos || name.find(":") != std::string::npos || name.find("..") != std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned char c : name)
|
||||
{
|
||||
if (std::iscntrl(c))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::weak_ptr<lua_module> lua_manager::load_module(const std::filesystem::path& module_path)
|
||||
{
|
||||
if (!std::filesystem::exists(module_path))
|
||||
@@ -321,7 +349,13 @@ namespace big
|
||||
return {};
|
||||
|
||||
const auto module_name = module_path.filename().string();
|
||||
const auto id = rage::joaat(module_name);
|
||||
if (!validate_module_name(module_name))
|
||||
{
|
||||
LOG(WARNING) << "Module " << module_name << " was not loaded. File name contains illegal characters.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto id = rage::joaat(module_name);
|
||||
|
||||
std::lock_guard guard(m_module_lock);
|
||||
for (const auto& module : m_modules)
|
||||
|
||||
+70
-21
@@ -1,4 +1,4 @@
|
||||
#include "lua_module.hpp"
|
||||
#include "lua_module.hpp"
|
||||
|
||||
#include "bindings/command.hpp"
|
||||
#include "bindings/entities.hpp"
|
||||
@@ -84,11 +84,11 @@ namespace big
|
||||
}
|
||||
|
||||
lua_module::lua_module(const std::filesystem::path& module_path, folder& scripts_folder, bool disabled) :
|
||||
m_state(),
|
||||
m_module_path(module_path),
|
||||
m_module_name(module_path.filename().string()),
|
||||
m_module_id(rage::joaat(m_module_name)),
|
||||
m_disabled(disabled)
|
||||
m_state(),
|
||||
m_module_path(module_path),
|
||||
m_module_name(module_path.filename().string()),
|
||||
m_module_id(rage::joaat(m_module_name)),
|
||||
m_disabled(disabled)
|
||||
{
|
||||
if (!m_disabled)
|
||||
{
|
||||
@@ -163,6 +163,15 @@ namespace big
|
||||
return m_disabled;
|
||||
}
|
||||
|
||||
const std::filesystem::path lua_module::get_config_folder() const
|
||||
{
|
||||
const auto config_path = g_lua_manager->get_scripts_config_folder().get_path() / m_module_name;
|
||||
if (!std::filesystem::exists(config_path))
|
||||
std::filesystem::create_directory(config_path);
|
||||
|
||||
return config_path;
|
||||
}
|
||||
|
||||
void lua_module::set_folder_for_lua_require(folder& scripts_folder)
|
||||
{
|
||||
std::string scripts_search_path = scripts_folder.get_path().string() + "/?.lua;";
|
||||
@@ -172,6 +181,9 @@ namespace big
|
||||
if (!entry.is_directory())
|
||||
continue;
|
||||
|
||||
if (std::filesystem::relative(entry, scripts_folder.get_path()).wstring().contains(L"disabled"))
|
||||
continue;
|
||||
|
||||
scripts_search_path += entry.path().string() + "/?.lua;";
|
||||
}
|
||||
// Remove final ';'
|
||||
@@ -180,6 +192,21 @@ namespace big
|
||||
m_state["package"]["path"] = scripts_search_path;
|
||||
}
|
||||
|
||||
static std::optional<std::filesystem::path> make_absolute(const std::filesystem::path& root, const std::filesystem::path& user_path)
|
||||
{
|
||||
if (user_path.is_absolute())
|
||||
return std::nullopt;
|
||||
|
||||
auto canon_root = std::filesystem::weakly_canonical(root);
|
||||
auto final_path = std::filesystem::weakly_canonical(canon_root / user_path);
|
||||
auto [root_end, nothing] = std::mismatch(canon_root.begin(), canon_root.end(), final_path.begin());
|
||||
|
||||
if (root_end != canon_root.end())
|
||||
return std::nullopt;
|
||||
|
||||
return final_path;
|
||||
};
|
||||
|
||||
void lua_module::sandbox_lua_os_library()
|
||||
{
|
||||
const auto& os = m_state["os"];
|
||||
@@ -189,22 +216,43 @@ namespace big
|
||||
sandbox_os["date"] = os["date"];
|
||||
sandbox_os["difftime"] = os["difftime"];
|
||||
sandbox_os["time"] = os["time"];
|
||||
|
||||
// Lua API: Function
|
||||
// Table: os
|
||||
// Name: rename
|
||||
// Param: oldname: string
|
||||
// Param: newname: string
|
||||
// Returns: boolean, string?: True if the file was successfully renamed, false and an error message otherwise.
|
||||
sandbox_os["rename"] = [this](const std::string& oldname, const std::string& newname) -> sol::object {
|
||||
const auto old_path = make_absolute(get_config_folder(), oldname);
|
||||
const auto new_path = make_absolute(get_config_folder(), newname);
|
||||
|
||||
if (!old_path)
|
||||
{
|
||||
LOG(WARNING) << "os.rename is restricted to the script's config folder, and the filename provided (" << oldname << ") seems to be outside of it.";
|
||||
return sol::make_object(m_state, std::make_tuple(false, "File not found."));
|
||||
}
|
||||
|
||||
if (!new_path)
|
||||
{
|
||||
LOG(WARNING) << "os.rename is restricted to the script's config folder, and the filename provided (" << newname << ") seems to be outside of it.";
|
||||
return sol::make_object(m_state, std::make_tuple(false, "New file name is invalid."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::filesystem::rename(old_path.value(), new_path.value());
|
||||
return sol::make_object(m_state, true);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
return sol::make_object(m_state, std::make_tuple(false, e.what()));
|
||||
}
|
||||
};
|
||||
|
||||
m_state["os"] = sandbox_os;
|
||||
}
|
||||
|
||||
static std::optional<std::filesystem::path> make_absolute(const std::filesystem::path& root, const std::filesystem::path& user_path)
|
||||
{
|
||||
auto final_path = std::filesystem::weakly_canonical(root / user_path);
|
||||
|
||||
auto [root_end, nothing] = std::mismatch(root.begin(), root.end(), final_path.begin());
|
||||
|
||||
if (root_end != root.end())
|
||||
return std::nullopt;
|
||||
|
||||
return final_path;
|
||||
};
|
||||
|
||||
void lua_module::sandbox_lua_io_library()
|
||||
{
|
||||
auto io = m_state["io"];
|
||||
@@ -221,7 +269,7 @@ namespace big
|
||||
// Name: open
|
||||
// Returns: file_handle: file handle or nil if can't read / write to the given path.
|
||||
sandbox_io["open"] = [this](const std::string& filename, const std::string& mode) {
|
||||
const auto scripts_config_sub_path = make_absolute(g_lua_manager->get_scripts_config_folder().get_path(), filename);
|
||||
const auto scripts_config_sub_path = make_absolute(get_config_folder(), filename);
|
||||
if (!scripts_config_sub_path)
|
||||
{
|
||||
LOG(WARNING) << "io.open is restricted to the scripts_config folder, and the filename provided (" << filename << ") is outside of it.";
|
||||
@@ -242,9 +290,10 @@ namespace big
|
||||
// Lua API: Function
|
||||
// Table: io
|
||||
// Name: exists
|
||||
// Param: filename: string
|
||||
// Returns: boolean: True if the passed file path exists
|
||||
sandbox_io["exists"] = [](const std::string& filename) -> bool {
|
||||
const auto scripts_config_sub_path = make_absolute(g_lua_manager->get_scripts_config_folder().get_path(), filename);
|
||||
sandbox_io["exists"] = [this](const std::string& filename) -> bool {
|
||||
const auto scripts_config_sub_path = make_absolute(get_config_folder(), filename);
|
||||
if (!scripts_config_sub_path)
|
||||
{
|
||||
LOG(WARNING) << "io.open is restricted to the scripts_config folder, and the filename provided (" << filename << ") is outside of it.";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "../script.hpp"
|
||||
#include "bindings/gui/gui_element.hpp"
|
||||
#include "core/data/menu_event.hpp"
|
||||
@@ -60,6 +60,7 @@ namespace big
|
||||
|
||||
// used for sandboxing and limiting to only our custom search path for the lua require function
|
||||
void set_folder_for_lua_require(folder& scripts_folder);
|
||||
const std::filesystem::path get_config_folder() const;
|
||||
|
||||
void sandbox_lua_os_library();
|
||||
void sandbox_lua_io_library();
|
||||
|
||||
Reference in New Issue
Block a user