Harden the mod against hostile servers and players

- PacketReader for all network data: lengths are checked against the real data
  (Mono allocated the claimed size first - a fake 2 GB length crashed the game)
- FsmSync: remote players can no longer switch arbitrary FSMs - never PLAYER /
  Systems / MSCO objects, CLICK only on FSMs that wait for a click, bolt/assembly
  events only along a transition the FSM really has for that event
- Time/weather (msco.world) and '* ' system-style chat only from the host in P2P
  (the dedicated server filters them itself); other msco.* ids are reserved

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 18:44:03 +02:00
co-authored by Claude Opus 5.5
parent 9c933ed486
commit a499577b7c
7 changed files with 88 additions and 9 deletions
+13 -6
View File
@@ -1731,7 +1731,13 @@ namespace MSCO
string modId = r.ReadString();
int len = r.ReadInt32();
if (len < 0 || len > MaxModMessageBytes) return;
if (modId == WorldAdmin.MessageId) { WorldAdmin.Receive(r.ReadBytes(len)); return; } // Admin: Uhrzeit/Wetter
if (modId == WorldAdmin.MessageId)
{
// Admin: Uhrzeit/Wetter. Der dedizierte Server lässt das nur von Admins durch; im P2P-Spiel nur vom Host
if (dedicated || from == NetSession.HostId) WorldAdmin.Receive(r.ReadBytes(len));
return;
}
if (modId.StartsWith("msco.")) return; // reserviert für MSCO selbst
ModLoader.Message(modId, from, r.ReadBytes(len));
return;
}
@@ -1740,7 +1746,8 @@ namespace MSCO
string text = r.ReadString();
PlayerInfo sender;
if (dedicated && from == NetSession.HostId) overlays.AddChat("", text, true); // Server/Admin-Meldung
else if (text.StartsWith("* ")) overlays.AddChat("", text.Substring(2), true);
// "* ..." = Meldung (z.B. Admin-Aktion): auf dem dedizierten Server filtert der Server, im P2P-Spiel nur vom Host
else if (text.StartsWith("* ") && (dedicated || from == NetSession.HostId)) overlays.AddChat("", text.Substring(2), true);
else overlays.AddChat(players.TryGetValue(from, out sender) ? sender.Name : "Player " + from, text);
return;
}
@@ -2368,7 +2375,7 @@ namespace MSCO
/// </summary>
System.Collections.IEnumerator ApplyWorldSnapshot(byte[] snapshot)
{
var r = new BinaryReader(new MemoryStream(snapshot));
var r = new PacketReader(snapshot);
if (r.ReadBoolean())
{
@@ -2401,7 +2408,7 @@ namespace MSCO
for (int i = 0; i < eventCount; i++)
{
byte[] payload = r.ReadBytes(r.ReadInt32());
FsmSync.Apply(FsmEventMsg.Deserialize(new BinaryReader(new MemoryStream(payload))));
FsmSync.Apply(FsmEventMsg.Deserialize(new PacketReader(payload)));
if ((i + 1) % SnapshotEventsPerFrame == 0) yield return null; // nicht alles in einem Frame
}
@@ -2426,7 +2433,7 @@ namespace MSCO
for (int i = 0; i < spawns; i++)
{
byte[] payload = r.ReadBytes(r.ReadInt32());
try { if (ApplySpawn(new BinaryReader(new MemoryStream(payload)))) spawned++; }
try { if (ApplySpawn(new PacketReader(payload))) spawned++; }
catch (System.Exception e) { MscoLog.Warning("Could not spawn a saved item: " + e.Message); }
}
}
@@ -2439,7 +2446,7 @@ namespace MSCO
for (int i = 0; i < appeared; i++)
{
byte[] payload = r.ReadBytes(r.ReadInt32());
try { ApplyAppeared(new BinaryReader(new MemoryStream(payload))); }
try { ApplyAppeared(new PacketReader(payload)); }
catch (System.Exception e) { MscoLog.Warning("Could not restore a bought item: " + e.Message); }
}
}