From d0822bf3e8a9c999bdf468c311dd2882906ad068 Mon Sep 17 00:00:00 2001 From: SAMURAI <66764345+xesdoog@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:03:46 +0100 Subject: [PATCH 1/4] 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-wtire. --- src/lua/lua_module.cpp | 64 ++++++++++++++++++++++++++++-------------- src/lua/lua_module.hpp | 3 +- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/lua/lua_module.cpp b/src/lua/lua_module.cpp index 8cef3ca..386f95d 100644 --- a/src/lua/lua_module.cpp +++ b/src/lua/lua_module.cpp @@ -1,4 +1,4 @@ -#include "lua_module.hpp" +#include "lua_module.hpp" #include "bindings/command.hpp" #include "bindings/entities.hpp" @@ -85,11 +85,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_directories(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;"; @@ -183,6 +192,18 @@ namespace big m_state["package"]["path"] = scripts_search_path; } + static std::optional 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_os_library() { const auto& os = m_state["os"]; @@ -192,22 +213,23 @@ namespace big sandbox_os["date"] = os["date"]; sandbox_os["difftime"] = os["difftime"]; sandbox_os["time"] = os["time"]; + 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); + 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(sol::lua_nil, e.what())); + } + }; m_state["os"] = sandbox_os; } - static std::optional 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"]; @@ -220,7 +242,7 @@ namespace big // Table for file manipulation. Modified for security purposes. 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."; @@ -243,8 +265,8 @@ namespace big // Name: exists // Param: filename: string // Returns: boolean: exists: 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."; diff --git a/src/lua/lua_module.hpp b/src/lua/lua_module.hpp index e69a68a..a1f5d53 100644 --- a/src/lua/lua_module.hpp +++ b/src/lua/lua_module.hpp @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../script.hpp" #include "bindings/gui.hpp" #include "bindings/gui/gui_element.hpp" @@ -58,6 +58,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(); From 31006f9b4c659c5fac266e9444e45ac8c01d37fb Mon Sep 17 00:00:00 2001 From: SAMURAI <66764345+xesdoog@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:13:10 +0100 Subject: [PATCH 2/4] fix(os.rename): check optional return --- src/lua/lua_module.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lua/lua_module.cpp b/src/lua/lua_module.cpp index 386f95d..36af8de 100644 --- a/src/lua/lua_module.cpp +++ b/src/lua/lua_module.cpp @@ -216,6 +216,11 @@ namespace big 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 || !new_path) // I'm too lazy to make separate error messages + { + return sol::make_object(m_state, std::make_tuple(false, "invalid path")); + } + try { std::filesystem::rename(old_path.value(), new_path.value()); From 3db172c22fc96dd792f5d02292919cfa0df2d846 Mon Sep 17 00:00:00 2001 From: SAMURAI <66764345+xesdoog@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:54:09 +0100 Subject: [PATCH 3/4] fix(os.rename): add documentation and properly handle errors --- src/lua/lua_module.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lua/lua_module.cpp b/src/lua/lua_module.cpp index 36af8de..04e5915 100644 --- a/src/lua/lua_module.cpp +++ b/src/lua/lua_module.cpp @@ -167,7 +167,7 @@ namespace big { 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_directories(config_path); + std::filesystem::create_directory(config_path); return config_path; } @@ -213,12 +213,26 @@ 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, optional: 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 || !new_path) // I'm too lazy to make separate error messages + 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) { - return sol::make_object(m_state, std::make_tuple(false, "invalid 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 From 226b220d2902ee4aeeb0429584e400b70eadbd38 Mon Sep 17 00:00:00 2001 From: SAMURAI <66764345+xesdoog@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:45:21 +0100 Subject: [PATCH 4/4] 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. --- src/lua/lua_manager.cpp | 33 +++++++++++++++++++++++++++++++-- src/lua/lua_module.cpp | 15 +++++++++------ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/lua/lua_manager.cpp b/src/lua/lua_manager.cpp index 569d4e2..dd558c2 100644 --- a/src/lua/lua_manager.cpp +++ b/src/lua/lua_manager.cpp @@ -1,4 +1,4 @@ -#include "lua_manager.hpp" +#include "lua_manager.hpp" #include "file_manager.hpp" @@ -398,6 +398,29 @@ 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_manager::load_module(const std::filesystem::path& module_path) { if (!std::filesystem::exists(module_path)) @@ -411,7 +434,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) diff --git a/src/lua/lua_module.cpp b/src/lua/lua_module.cpp index 04e5915..697b573 100644 --- a/src/lua/lua_module.cpp +++ b/src/lua/lua_module.cpp @@ -194,11 +194,14 @@ namespace big static std::optional make_absolute(const std::filesystem::path& root, const std::filesystem::path& user_path) { - auto final_path = std::filesystem::weakly_canonical(root / user_path); + if (user_path.is_absolute()) + return std::nullopt; - auto [root_end, nothing] = std::mismatch(root.begin(), root.end(), final_path.begin()); + 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 != root.end()) + if (root_end != canon_root.end()) return std::nullopt; return final_path; @@ -219,10 +222,11 @@ namespace big // Name: rename // Param: oldname: string // Param: newname: string - // Returns: boolean, optional: True if the file was successfully renamed, false and an error message otherwise. + // 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."; @@ -242,7 +246,7 @@ namespace big } catch (const std::exception& e) { - return sol::make_object(m_state, std::make_tuple(sol::lua_nil, e.what())); + return sol::make_object(m_state, std::make_tuple(false, e.what())); } }; @@ -265,7 +269,6 @@ namespace big 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."; - return sol::reference(sol::lua_nil); }