# HG changeset patch # User Jonathan G Rennison # Date 2024-01-02 18:02:12 # Node ID 9db411cc3b19e33ccd498f60c3a5ab6f50c2617f # Parent d7ceb84d9d5aace4a75c5e1252e492673fc27502 Change: Limit total script ops that can be consumed by a list valuate (#11670) diff --git a/src/3rdparty/squirrel/squirrel/sqvm.cpp b/src/3rdparty/squirrel/squirrel/sqvm.cpp --- a/src/3rdparty/squirrel/squirrel/sqvm.cpp +++ b/src/3rdparty/squirrel/squirrel/sqvm.cpp @@ -116,6 +116,8 @@ SQVM::SQVM(SQSharedState *ss) _can_suspend = false; _in_stackoverflow = false; _ops_till_suspend = 0; + _ops_till_suspend_error_threshold = INT64_MIN; + _ops_till_suspend_error_label = nullptr; _callsstack = nullptr; _callsstacksize = 0; _alloccallsstacksize = 0; @@ -744,6 +746,10 @@ exception_restore: { DecreaseOps(1); if (ShouldSuspend()) { _suspended = SQTrue; _suspended_traps = traps; return true; } + if (IsOpsTillSuspendError()) { + Raise_Error(fmt::format("excessive CPU usage in {}", _ops_till_suspend_error_label)); + SQ_THROW(); + } const SQInstruction &_i_ = *ci->_ip++; #ifdef _DEBUG_DUMP diff --git a/src/3rdparty/squirrel/squirrel/sqvm.h b/src/3rdparty/squirrel/squirrel/sqvm.h --- a/src/3rdparty/squirrel/squirrel/sqvm.h +++ b/src/3rdparty/squirrel/squirrel/sqvm.h @@ -168,6 +168,8 @@ public: SQBool _can_suspend; SQInteger _ops_till_suspend; + SQInteger _ops_till_suspend_error_threshold; + const char *_ops_till_suspend_error_label; SQBool _in_stackoverflow; bool ShouldSuspend() @@ -175,6 +177,11 @@ public: return _can_suspend && _ops_till_suspend <= 0; } + bool IsOpsTillSuspendError() + { + return _ops_till_suspend < _ops_till_suspend_error_threshold; + } + void DecreaseOps(SQInteger amount) { if (_ops_till_suspend - amount < _ops_till_suspend) _ops_till_suspend -= amount; diff --git a/src/script/api/script_list.cpp b/src/script/api/script_list.cpp --- a/src/script/api/script_list.cpp +++ b/src/script/api/script_list.cpp @@ -11,7 +11,9 @@ #include "script_list.hpp" #include "script_controller.hpp" #include "../../debug.h" +#include "../../core/backup_type.hpp" #include "../../script/squirrel.hpp" +#include <../squirrel/sqvm.h> #include "../../safeguards.h" @@ -866,6 +868,14 @@ SQInteger ScriptList::Valuate(HSQUIRRELV bool backup_allow = ScriptObject::GetAllowDoCommand(); ScriptObject::SetAllowDoCommand(false); + /* Limit the total number of ops that can be consumed by a valuate operation */ + SQInteger new_ops_error_threshold = vm->_ops_till_suspend_error_threshold; + if (vm->_ops_till_suspend_error_threshold == INT64_MIN) { + new_ops_error_threshold = vm->_ops_till_suspend - MAX_VALUATE_OPS; + vm->_ops_till_suspend_error_label = "valuator function"; + } + AutoRestoreBackup ops_error_threshold_backup(vm->_ops_till_suspend_error_threshold, new_ops_error_threshold); + /* Push the function to call */ sq_push(vm, 2); diff --git a/src/script/api/script_list.hpp b/src/script/api/script_list.hpp --- a/src/script/api/script_list.hpp +++ b/src/script/api/script_list.hpp @@ -13,6 +13,9 @@ #include "script_object.hpp" +/** Maximum number of operations allowed for valuating a list. */ +static const int MAX_VALUATE_OPS = 500000; + class ScriptListSorter; /** diff --git a/src/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -15,6 +15,8 @@ #include "../../depot_map.h" #include "../../vehicle_base.h" #include "../../train.h" +#include "../../core/backup_type.hpp" +#include <../squirrel/sqvm.h> #include "../../safeguards.h" @@ -41,6 +43,14 @@ ScriptVehicleList::ScriptVehicleList(HSQ bool backup_allow = ScriptObject::GetAllowDoCommand(); ScriptObject::SetAllowDoCommand(false); + /* Limit the total number of ops that can be consumed by a filter operation, if a filter function is present */ + SQInteger new_ops_error_threshold = vm->_ops_till_suspend_error_threshold; + if (nparam >= 1 && vm->_ops_till_suspend_error_threshold == INT64_MIN) { + new_ops_error_threshold = vm->_ops_till_suspend - MAX_VALUATE_OPS; + vm->_ops_till_suspend_error_label = "vehicle filter function"; + } + AutoRestoreBackup ops_error_threshold_backup(vm->_ops_till_suspend_error_threshold, new_ops_error_threshold); + for (const Vehicle *v : Vehicle::Iterate()) { if (v->owner != ScriptObject::GetCompany() && !ScriptCompanyMode::IsDeity()) continue; if (!v->IsPrimaryVehicle() && !(v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())) continue;