From 3dbaef5765a1fafb85bf633fc85054eb5a0b5b59 Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Wed, 4 Feb 2026 20:30:41 -0500 Subject: [PATCH] kill misbehaving client scripts via interruptHook --- modules/client/wm/sandbox/init.luau | 19 ++++++++++++++++++- modules/client/wm/sandbox/vm.luau | 16 ++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/modules/client/wm/sandbox/init.luau b/modules/client/wm/sandbox/init.luau index 8a302f3..168ae4d 100644 --- a/modules/client/wm/sandbox/init.luau +++ b/modules/client/wm/sandbox/init.luau @@ -7,6 +7,14 @@ local VM = require("@self/vm") local Rules = require("@self/rules") local Players = game:GetService("Players") +local RunService = game:GetService("RunService") + +local lastSteppedStart = 0 +local SCRIPT_TIMEOUT = 0.1 + +RunService.Stepped:Connect(function() + lastSteppedStart = os.clock() +end) local localPlayer = Players.LocalPlayer @@ -90,7 +98,16 @@ function Module.new(owner: Player, bytecode: buffer, script: BaseScript?): sandb scriptLookup[script] = sandbox end - local main, luau_close = VM.luau_load(bytecode, sandbox) + local main, luau_close = VM.luau_load(bytecode, sandbox, @native function() + if os.clock() - lastSteppedStart >= SCRIPT_TIMEOUT then + error(`Script timeout: exhausted allowed execution time ({SCRIPT_TIMEOUT})`, 2) + + return sandbox.Terminator() + end + + return nil + end) + sandbox.Terminator = luau_close return sandbox, main diff --git a/modules/client/wm/sandbox/vm.luau b/modules/client/wm/sandbox/vm.luau index 9d589b4..519bff4 100644 --- a/modules/client/wm/sandbox/vm.luau +++ b/modules/client/wm/sandbox/vm.luau @@ -541,7 +541,7 @@ local function luau_deserialize(bytecode) end local vmStart = debug_info(function() end, "l") -local function luau_load(module, sandbox) +local function luau_load(module, sandbox, interruptHook: () -> ()) if type(module) ~= "table" then module = luau_deserialize(module) end @@ -561,7 +561,7 @@ local function luau_load(module, sandbox) local function luau_wrapclosure(module, proto, upvals, _env) local info = { proto = proto, sandbox = sandbox } - -- TODO: @native + @native local function luau_execute(...) local runningInfo = { thread = coroutine_running(), pc = 0, closureInfo = info } info.running = runningInfo @@ -735,6 +735,8 @@ local function luau_load(module, sandbox) stack[A] = sb[inst.K] elseif op == 21 then --[[ CALL ]] + interruptHook() + local A, B, C = inst.A, inst.B, inst.C local func = stack[A] @@ -752,6 +754,8 @@ local function luau_load(module, sandbox) table_move(ret_list, 1, ret_num, A, stack) elseif op == 22 then --[[ RETURN ]] + interruptHook() + local A = inst.A local B = inst.B @@ -759,6 +763,8 @@ local function luau_load(module, sandbox) elseif op == 23 then --[[ JUMP ]] pc += inst.D elseif op == 24 then --[[ JUMPBACK ]] + interruptHook() + -- selene: allow(if_same_then_else) pc += inst.D elseif op == 25 then --[[ JUMPIF ]] @@ -919,6 +925,8 @@ local function luau_load(module, sandbox) end end elseif op == 57 then --[[ FORNLOOP ]] + interruptHook() + local A = inst.A local step = stack[A + 1] local index = stack[A + 2] + step @@ -935,6 +943,8 @@ local function luau_load(module, sandbox) end end elseif op == 58 then --[[ FORGLOOP ]] + interruptHook() + local A = inst.A top = A + 6 @@ -1027,6 +1037,8 @@ local function luau_load(module, sandbox) pc += 1 --// adjust for aux elseif op == 67 then --[[ JUMPX ]] + interruptHook() + pc += inst.E elseif op == 68 then --[[ FASTCALL ]] --[[ Skipped ]]