Compare commits

...

14 Commits

Author SHA1 Message Date
Tixx 43e86a1e22 Change to luajit 2026-02-19 22:08:06 +01:00
Tixx 70fc8436ed Remove lua version lock 2026-01-18 23:32:54 +01:00
Tixx 6f196aca64 Decrease likelyhood of lua stack corruption (#462)
Decreases the likelyhood of lua stack corruption.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-01-18 21:56:31 +01:00
Tixx 99476a2c77 Revert "Add stack trace to server lua engine (#350)" 2026-01-15 14:44:13 +01:00
Tixx 9fa9974159 Lua version change to 5.3.5 (#458)
Update Lua version to make plugin development easier across platforms
and simplify Windows builds by using vcpkg.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-01-10 21:39:52 +01:00
boubouleuh 3ef816845d Lua version change to 5.3.5 2026-01-10 21:09:05 +01:00
Tixx 6ec4106ec7 refactor: optimize string operations and improve code clarity (#451)
- Avoid redundant substr() calls in packet parsing hot-path
(TServer.cpp)
  The previous code called substr(3) twice per packet, creating
  unnecessary temporary strings. Now stores the result once.

- Replace .size() == 0 with .empty() for idiomatic C++
  (TConsole.cpp, TLuaEngine.cpp)
2026-01-02 00:42:03 +01:00
Tixx b094e35f8c Skip invalid socket when accept() fails (#457)
When Acceptor.accept() returns an error (e.g., "Too many open files"),
the server was continuing to process an invalid socket, causing resource
leaks and potential infinite error loops.
Add continue statement to skip processing when accept() fails, allowing
the server to retry on the next iteration instead of crashing.
Fixes resource exhaustion DoS vulnerability where server would enter
error loop instead of handling gracefully.
<img width="1233" height="199" alt="image"
src="https://github.com/user-attachments/assets/bad8f559-6ef2-47ee-b1c1-3e6020cdfb77"
/>

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-12-29 17:38:53 +01:00
wadyankaw 83afafc0c3 Skip invalid socket when accept() fails
When Acceptor.accept() returns an error (e.g., "Too many open files"),
the server was continuing to process an invalid socket, causing resource
leaks and potential infinite error loops.

Add continue statement to skip processing when accept() fails, allowing
the server to retry on the next iteration instead of crashing.

Fixes resource exhaustion DoS vulnerability where server would enter
error loop instead of handling gracefully.
2025-12-28 03:54:55 +03:00
Kipstz 31122abe10 Merge branch 'BeamMP:minor' into refactor-string-optimizations 2025-12-28 00:56:24 +01:00
Tixx 0615b57a37 Add message length validation for chat messages (#456)
Right now server only checks if chat message is empty but doesnt check
the max length. Client limits to 500 chars but if someone modifies the
client they can send huge messages. I added a check on server side to
reject messages longer than 500 characters, same as client limit. I used
translator because I don't know English well

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-12-28 00:35:43 +01:00
wadyankaw 4a8378427a Add message length validation for chat messages
Right now server only checks if chat message is empty but doesnt check the max length. Client limits to 500 chars but if someone modifies the client they can send huge messages.
I added a check on server side to reject messages longer than 500 characters, same as client limit.
I used translator because I don't know English well
2025-12-28 02:24:08 +03:00
Kipstz b74f0c7ca8 refactor: optimize string operations and improve code clarity
- Avoid redundant substr() calls in packet parsing hot-path (TServer.cpp)
  The previous code called substr(3) twice per packet, creating
  unnecessary temporary strings. Now stores the result once.

- Replace .size() == 0 with .empty() for idiomatic C++
  (TConsole.cpp, TLuaEngine.cpp)
2025-12-27 23:46:45 +01:00
Tixx 420c64f6cf Fix build (#444)
By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-12-20 20:13:15 +01:00
7 changed files with 29 additions and 44 deletions
+6 -3
View File
@@ -80,14 +80,15 @@ set(PRJ_SOURCES
src/ChronoWrapper.cpp
)
find_package(Lua REQUIRED)
# fix for sol2
find_path(LUAJIT_INCLUDE_DIR lua.hpp PATH_SUFFIXES luajit)
# set the source file containing main()
set(PRJ_MAIN src/main.cpp)
# set the source file containing the test's main
set(PRJ_TEST_MAIN test/test_main.cpp)
# set include paths not part of libraries
set(PRJ_INCLUDE_DIRS ${LUA_INCLUDE_DIR})
set(PRJ_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIR})
# set compile features (e.g. standard version)
set(PRJ_COMPILE_FEATURES cxx_std_20)
# set #defines (test enable/disable not included here)
@@ -105,9 +106,11 @@ set(PRJ_LIBRARIES
libzip::zip
OpenSSL::SSL OpenSSL::Crypto
CURL::libcurl
${LUA_LIBRARIES}
lua51
)
link_directories(${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib)
# add dependency find_package calls and similar here
find_package(fmt CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)
+1 -3
View File
@@ -192,9 +192,7 @@ public:
for (const auto& Event : mLuaEvents.at(EventName)) {
for (const auto& Function : Event.second) {
if (Event.first != IgnoreId) {
auto Result = EnqueueFunctionCall(Event.first, Function, Arguments, EventName);
Results.push_back(Result);
AddResultToCheck(Result);
Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments, EventName));
}
}
}
+5 -6
View File
@@ -160,7 +160,7 @@ void TConsole::ChangeToRegularConsole() {
}
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) {
if (n == 0 && args.size() != 0) {
if (n == 0 && !args.empty()) {
Application::Console().WriteRaw("This command expects no arguments.");
return false;
} else if (args.size() != n) {
@@ -198,7 +198,7 @@ void TConsole::Command_Lua(const std::string&, const std::vector<std::string>& a
} else {
Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua.");
}
} else if (args.size() == 0) {
} else if (args.empty()) {
ChangeToLuaConsole(mDefaultStateId);
}
}
@@ -260,8 +260,7 @@ void TConsole::Command_Version(const std::string& cmd, const std::vector<std::st
Application::Console().WriteRaw("Platform: " + platform);
Application::Console().WriteRaw("Server: v" + Application::ServerVersionString());
std::string lua_version = fmt::format("Lua: v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
Application::Console().WriteRaw(lua_version);
Application::Console().WriteRaw(LUA_RELEASE);
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
Application::Console().WriteRaw(openssl_version);
}
@@ -423,7 +422,7 @@ void TConsole::Command_Settings(const std::string&, const std::vector<std::strin
settings set <category> <setting> <value> sets specified setting to value
)";
if (args.size() == 0) {
if (args.empty()) {
beammp_errorf("No arguments specified for command 'settings'!");
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
return;
@@ -705,7 +704,7 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
}
}
}
if (NonNilFutures.size() == 0) {
if (NonNilFutures.empty()) {
if (!IgnoreNotACommand) {
Application::Console().WriteRaw("Error: Unknown command: '" + cmd + "'. Type 'help' to see a list of valid commands.");
}
+5 -28
View File
@@ -30,16 +30,12 @@
#include <condition_variable>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include <optional>
#include <random>
#include <sol/stack_core.hpp>
#include <thread>
#include <tuple>
TLuaEngine* LuaAPI::MP::Engine;
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn);
TLuaEngine::TLuaEngine()
: mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") {
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
@@ -69,7 +65,7 @@ TEST_CASE("TLuaEngine ctor & dtor") {
void TLuaEngine::operator()() {
RegisterThread("LuaEngine");
// lua engine main thread
beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
beammp_infof(LUA_RELEASE);
CollectAndInitPlugins();
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
@@ -131,7 +127,7 @@ void TLuaEngine::operator()() {
}
}
}
if (mLuaStates.size() == 0) {
if (mLuaStates.empty()) {
beammp_trace("No Lua states, event loop running extremely sparsely");
Application::SleepSafeSeconds(10);
} else {
@@ -496,7 +492,6 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
sol::variadic_results LocalArgs = JsonStringToArray(Str);
for (const auto& Handler : MyHandlers) {
auto Fn = mStateView[Handler];
Fn = AddTraceback(mStateView, Fn);
if (Fn.valid()) {
auto LuaResult = Fn(LocalArgs);
auto Result = std::make_shared<TLuaResult>();
@@ -505,9 +500,7 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
Result->Result = LuaResult;
} else {
Result->Error = true;
sol::error Err = LuaResult;
Result->ErrorMessage = Err.what();
beammp_errorf("An error occured while executing local event handler \"{}\" for event \"{}\": {}", Handler, EventName, Result->ErrorMessage);
Result->ErrorMessage = "Function result in TriggerGlobalEvent was invalid";
}
Result->MarkAsReady();
Return.push_back(Result);
@@ -1089,21 +1082,6 @@ void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, co
mEngine->RegisterEvent(EventName, mStateId, FunctionName);
}
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn) {
StateView["INTERNAL_ERROR_HANDLER"] = [](lua_State *L) {
auto Error = sol::stack::get<std::optional<std::string>>(L);
std::string ErrorString = "<Unknown error>";
if (Error.has_value()) {
ErrorString = Error.value();
}
auto DebugTracebackFn = sol::state_view(L).globals().get<sol::table>("debug").get<sol::protected_function>("traceback");
// 2 = start collecting the trace one above the current function (1=current function)
std::string Traceback = DebugTracebackFn(ErrorString, 2);
return sol::stack::push(L, Traceback);
};
return sol::protected_function(RawFn, StateView["INTERNAL_ERROR_HANDLER"]);
}
void TLuaEngine::StateThreadData::operator()() {
RegisterThread("Lua:" + mStateId);
while (!Application::IsShuttingDown()) {
@@ -1168,8 +1146,8 @@ void TLuaEngine::StateThreadData::operator()() {
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
Result->StateId = mStateId;
sol::state_view StateView(mState);
auto RawFn = StateView[FnName];
if (RawFn.valid() && RawFn.get_type() == sol::type::function) {
auto Fn = StateView[FnName];
if (Fn.valid() && Fn.get_type() == sol::type::function) {
std::vector<sol::object> LuaArgs;
for (const auto& Arg : Args) {
if (Arg.valueless_by_exception()) {
@@ -1204,7 +1182,6 @@ void TLuaEngine::StateThreadData::operator()() {
break;
}
}
auto Fn = AddTraceback(StateView, RawFn);
auto Res = Fn(sol::as_args(LuaArgs));
if (Res.valid()) {
Result->Error = false;
+2 -1
View File
@@ -243,10 +243,11 @@ void TNetwork::TCPServerMain() {
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
if (ec) {
beammp_errorf("Failed to accept() new client: {}", ec.message());
continue;
}
TConnection Conn { std::move(ClientSocket), ClientEp };
std::thread ID(&TNetwork::Identify, this, std::move(Conn));
ID.detach(); // TODO: Add to a queue and attempt to join periodically
ID.detach();
} catch (const std::exception& e) {
beammp_errorf("Exception in accept routine: {}", e.what());
}
+8 -2
View File
@@ -198,7 +198,8 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
int PID = -1;
int VID = -1;
auto MaybePidVid = GetPidVid(StringPacket.substr(3).substr(0, StringPacket.substr(3).find(':', 1)));
auto pidVidPart = StringPacket.substr(3);
auto MaybePidVid = GetPidVid(pidVidPart.substr(0, pidVidPart.find(':')));
if (MaybePidVid) {
std::tie(PID, VID) = MaybePidVid.value();
}
@@ -256,6 +257,10 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
beammp_debugf("Empty chat message received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (Message.size() > 500) {
beammp_debugf("Chat message too long from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message);
TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
@@ -289,7 +294,8 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
int PID = -1;
int VID = -1;
auto MaybePidVid = GetPidVid(StringPacket.substr(3).substr(0, StringPacket.substr(3).find(':', 1)));
auto pidVidPart = StringPacket.substr(3);
auto MaybePidVid = GetPidVid(pidVidPart.substr(0, pidVidPart.find(':')));
if (MaybePidVid) {
std::tie(PID, VID) = MaybePidVid.value();
}
+2 -1
View File
@@ -14,7 +14,8 @@
"openssl",
"rapidjson",
"sol2",
"curl"
"curl",
"luajit"
],
"overrides": [
{