Security report (responsible disclosure)
Tunnel response resolver ignores source — cross-player forgery of pending server→client RPC results
Affected: vrp/lib/Tunnel.lua:125-144, vrp/lib/IDManager.lua:16-29
RegisterLocalEvent(name..":"..identifier..":tunnel_res") -- RegisterServerEvent on the server
AddEventHandler(name..":"..identifier..":tunnel_res", function(rid, args)
local callback = callbacks[rid]
if callback then
ids:free(rid)
callbacks[rid] = nil
callback(table.unpack(args, 1, args.n))
end
end)
Two compounding weaknesses at the framework transport layer:
- The handler never checks
source — any client can answer a request addressed to anyone.
- All core interfaces share one identifier (
GetCurrentResourceName()) and request IDs come from a small sequential free-list pool (0, 1, 2, …), making pending rids trivially guessable.
Impact
Whenever the server awaits a tunnel reply addressed to any client — police weapon checks awaiting PlayerState.remote.getWeapons(tuser.source) (modules/police.lua:229), garage store/despawn awaiting another user's client (modules/garage.lua:249-254, :511), fperms like !inside/!in_owned_vehicle resolved through the target's client (base.lua:183-186) — an attacker sprays forged responses with guessed rids. Forged handcuff/interior/vehicle-possession answers feed directly into ownership transitions and permission-gated logic (vehicle store/despawn desync, repair-duplicate abuse). Consent dialogs are not affected (GUI.tunnel:requestResult does validate source — showing the correct pattern exists).
Suggested fix
Capture source in the handler, tag each pending callback with its destination player, and reject mismatches; randomize the rid space instead of sequential reuse.
Security report (responsible disclosure)
Tunnel response resolver ignores
source— cross-player forgery of pending server→client RPC resultsAffected:
vrp/lib/Tunnel.lua:125-144,vrp/lib/IDManager.lua:16-29Two compounding weaknesses at the framework transport layer:
source— any client can answer a request addressed to anyone.GetCurrentResourceName()) and request IDs come from a small sequential free-list pool (0, 1, 2, …), making pendingrids trivially guessable.Impact
Whenever the server awaits a tunnel reply addressed to any client — police weapon checks awaiting
PlayerState.remote.getWeapons(tuser.source)(modules/police.lua:229), garage store/despawn awaiting another user's client (modules/garage.lua:249-254,:511), fperms like!inside/!in_owned_vehicleresolved through the target's client (base.lua:183-186) — an attacker sprays forged responses with guessed rids. Forged handcuff/interior/vehicle-possession answers feed directly into ownership transitions and permission-gated logic (vehicle store/despawn desync, repair-duplicate abuse). Consent dialogs are not affected (GUI.tunnel:requestResultdoes validate source — showing the correct pattern exists).Suggested fix
Capture
sourcein the handler, tag each pending callback with its destination player, and reject mismatches; randomize the rid space instead of sequential reuse.