From d337cf4a1e8cd40a01362d7f5cf39063d7c9d48a Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 8 Aug 2026 01:04:08 -0700 Subject: [PATCH] fix(network): reject repeats of the do-once handshake packets Seeding and picking a server each happen once per connection. Neither had a guard, so a client pipelining into the recv buffer could re-run the handshake before the queued disconnect drained and reach both handlers again. A second play-server is the one that mattered: this connection may already have spent its auth id on a game login, and the id it would be handed back is the spent one, which nothing will redeem. Rejecting the packet is the honest answer -- the client is out of protocol order, not unlucky. Account login and game login already guarded on SentFirstPacket. Co-Authored-By: Claude Opus 5 (1M context) --- .../Network/Packets/IncomingAccountPackets.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs index 6c1c3277a..236deb362 100644 --- a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs @@ -504,6 +504,14 @@ public static class IncomingAccountPackets public static void PlayServer(NetState state, SpanReader reader) { + // A server is picked once per connection. Picking again would hand back an id this + // connection may already have spent on a game login, so the client could never redeem it. + if (state.AuthId != 0) + { + state.Disconnect("Duplicate play server packet sent."); + return; + } + int index = reader.ReadInt16(); var info = state.ServerInfo; var a = state.Account; @@ -516,7 +524,7 @@ public static class IncomingAccountPackets { var si = info[index]; - state.AuthId = GenerateAuthID(state); + state.AuthId = state.GenerateAuthID(); state.SentFirstPacket = false; state.SendPlayServerAck(si, state.AuthId); @@ -525,6 +533,14 @@ public static class IncomingAccountPackets public static void LoginServerSeed(NetState state, SpanReader reader) { + // Seeding happens once per connection. A second one means the client is restarting a + // handshake it already completed, which no real client does. + if (state.Seeded) + { + state.Disconnect("Duplicate login server seed packet sent."); + return; + } + state.Seed = reader.ReadInt32(); state.Seeded = true;