Skip to content

Commit 03c20ac

Browse files
authored
fix: Detection and graceful handling of corrupt packets (#2419)
* fix: Detection and graceful handling of corrupt packets Adds multiple ways to detect and handle corrupt packets that could otherwise cause issues and put the game into a bad state, or potentially cause crashes: - A "magic" value at the start of each packet helps when looking at packet dumps to see where potential start-of-packet is if the data gets offset (as was seen in the recent BatchSendQueue corruption bug) - A "size in bytes" value helps us detect if the packet has been truncated or if garbage has been added to the end - A "hash" value helps to detect when the other values are correct but one or more bytes within the packet have been corrupted Additionally, code has been added to refuse to process ConnectionRequestMessage or ConnectionAcceptedMessage on an alredy-established connection, or either of those being received by the wrong side, as those could otherwise be used as a vector for attack by a malicious actor and render the framework completely broken. In all cases, if these issues occur, the log requests the user to file an issue with us with the full hex dump of the packet that was received.
1 parent 5840a69 commit 03c20ac

File tree

10 files changed

+533
-28
lines changed

10 files changed

+533
-28
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13+
- Added detection and graceful handling of corrupt packets for additional safety. (#2419)
14+
1315
### Changed
1416

1517
- The UTP component UI has been updated to be more user-friendly for new users by adding a simple toggle to switch between local-only (127.0.0.1) and remote (0.0.0.0) binding modes, using the toggle "Allow Remote Connections" (#2408)

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,55 @@ public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelive
143143

144144
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
145145
{
146-
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
147-
(client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
146+
if (m_NetworkManager.IsServer)
148147
{
149-
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
148+
if (messageType == typeof(ConnectionApprovedMessage))
150149
{
151-
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted");
150+
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
151+
{
152+
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
153+
}
154+
return false;
152155
}
156+
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
157+
(client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
158+
{
159+
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
160+
{
161+
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted");
162+
}
153163

154-
return false;
164+
return false;
165+
}
166+
167+
if (m_NetworkManager.ConnectedClients.TryGetValue(senderId, out NetworkClient connectedClient) && messageType == typeof(ConnectionRequestMessage))
168+
{
169+
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
170+
{
171+
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
172+
}
173+
174+
return false;
175+
}
176+
}
177+
else
178+
{
179+
if (messageType == typeof(ConnectionRequestMessage))
180+
{
181+
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
182+
{
183+
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
184+
}
185+
return false;
186+
}
187+
if (m_NetworkManager.IsConnectedClient && messageType == typeof(ConnectionApprovedMessage))
188+
{
189+
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
190+
{
191+
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
192+
}
193+
return false;
194+
}
155195
}
156196

157197
return !m_NetworkManager.m_StopProcessingMessages;

com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,26 @@ namespace Unity.Netcode
55
/// </summary>
66
internal struct BatchHeader : INetworkSerializeByMemcpy
77
{
8+
internal const ushort MagicValue = 0x1160;
9+
/// <summary>
10+
/// A magic number to detect corrupt messages.
11+
/// Always set to k_MagicValue
12+
/// </summary>
13+
public ushort Magic;
14+
15+
/// <summary>
16+
/// Total number of bytes in the batch.
17+
/// </summary>
18+
public int BatchSize;
19+
20+
/// <summary>
21+
/// Hash of the message to detect corrupt messages.
22+
/// </summary>
23+
public ulong BatchHash;
24+
825
/// <summary>
926
/// Total number of messages in the batch.
1027
/// </summary>
11-
public ushort BatchSize;
28+
public ushort BatchCount;
1229
}
1330
}

com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Runtime.CompilerServices;
5+
using System.Text;
56
using Unity.Collections;
67
using Unity.Collections.LowLevel.Unsafe;
78
using UnityEngine;
@@ -41,7 +42,7 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA
4142
{
4243
Writer = new FastBufferWriter(writerSize, writerAllocator, maxWriterSize);
4344
NetworkDelivery = delivery;
44-
BatchHeader = default;
45+
BatchHeader = new BatchHeader { Magic = BatchHeader.MagicValue };
4546
}
4647
}
4748

@@ -204,6 +205,17 @@ public int GetLocalVersion(Type messageType)
204205
return m_LocalVersions[messageType];
205206
}
206207

208+
internal static string ByteArrayToString(byte[] ba, int offset, int count)
209+
{
210+
var hex = new StringBuilder(ba.Length * 2);
211+
for (int i = offset; i < offset + count; ++i)
212+
{
213+
hex.AppendFormat("{0:x2} ", ba[i]);
214+
}
215+
216+
return hex.ToString();
217+
}
218+
207219
internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float receiveTime)
208220
{
209221
unsafe
@@ -214,18 +226,38 @@ internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float
214226
new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
215227
if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
216228
{
217-
NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it.");
229+
NetworkLog.LogError("Received a packet too small to contain a BatchHeader. Ignoring it.");
218230
return;
219231
}
220232

221233
batchReader.ReadValue(out BatchHeader batchHeader);
222234

235+
if (batchHeader.Magic != BatchHeader.MagicValue)
236+
{
237+
NetworkLog.LogError($"Received a packet with an invalid Magic Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Offset: {data.Offset}, Size: {data.Count}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
238+
return;
239+
}
240+
241+
if (batchHeader.BatchSize != data.Count)
242+
{
243+
NetworkLog.LogError($"Received a packet with an invalid Batch Size Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Offset: {data.Offset}, Size: {data.Count}, Expected Size: {batchHeader.BatchSize}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
244+
return;
245+
}
246+
247+
var hash = XXHash.Hash64(batchReader.GetUnsafePtrAtCurrentPosition(), batchReader.Length - batchReader.Position);
248+
249+
if (hash != batchHeader.BatchHash)
250+
{
251+
NetworkLog.LogError($"Received a packet with an invalid Hash Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Received Hash: {batchHeader.BatchHash}, Calculated Hash: {hash}, Offset: {data.Offset}, Size: {data.Count}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
252+
return;
253+
}
254+
223255
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
224256
{
225-
m_Hooks[hookIdx].OnBeforeReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length);
257+
m_Hooks[hookIdx].OnBeforeReceiveBatch(clientId, batchHeader.BatchCount, batchReader.Length);
226258
}
227259

228-
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
260+
for (var messageIdx = 0; messageIdx < batchHeader.BatchCount; ++messageIdx)
229261
{
230262

231263
var messageHeader = new MessageHeader();
@@ -237,15 +269,15 @@ internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float
237269
}
238270
catch (OverflowException)
239271
{
240-
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
272+
NetworkLog.LogError("Received a batch that didn't have enough data for all of its batches, ending early!");
241273
throw;
242274
}
243275

244276
var receivedHeaderSize = batchReader.Position - position;
245277

246278
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
247279
{
248-
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
280+
NetworkLog.LogError("Received a message that claimed a size larger than the packet, ending early!");
249281
return;
250282
}
251283
m_IncomingMessageQueue.Add(new ReceiveQueueItem
@@ -263,7 +295,7 @@ internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float
263295
}
264296
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
265297
{
266-
m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length);
298+
m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchCount, batchReader.Length);
267299
}
268300
}
269301
}
@@ -650,7 +682,7 @@ internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter t
650682

651683
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
652684
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
653-
writeQueueItem.BatchHeader.BatchSize++;
685+
writeQueueItem.BatchHeader.BatchCount++;
654686
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
655687
{
656688
m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length);
@@ -745,31 +777,36 @@ internal unsafe void ProcessSendQueues()
745777
for (var i = 0; i < sendQueueItem.Length; ++i)
746778
{
747779
ref var queueItem = ref sendQueueItem.ElementAt(i);
748-
if (queueItem.BatchHeader.BatchSize == 0)
780+
if (queueItem.BatchHeader.BatchCount == 0)
749781
{
750782
queueItem.Writer.Dispose();
751783
continue;
752784
}
753785

754786
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
755787
{
756-
m_Hooks[hookIdx].OnBeforeSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
788+
m_Hooks[hookIdx].OnBeforeSendBatch(clientId, queueItem.BatchHeader.BatchCount, queueItem.Writer.Length, queueItem.NetworkDelivery);
757789
}
758790

759791
queueItem.Writer.Seek(0);
760792
#if UNITY_EDITOR || DEVELOPMENT_BUILD
761793
// Skipping the Verify and sneaking the write mark in because we know it's fine.
762-
queueItem.Writer.Handle->AllowedWriteMark = 2;
794+
queueItem.Writer.Handle->AllowedWriteMark = sizeof(BatchHeader);
763795
#endif
796+
queueItem.BatchHeader.BatchHash = XXHash.Hash64(queueItem.Writer.GetUnsafePtr() + sizeof(BatchHeader), queueItem.Writer.Length - sizeof(BatchHeader));
797+
798+
queueItem.BatchHeader.BatchSize = queueItem.Writer.Length;
799+
764800
queueItem.Writer.WriteValue(queueItem.BatchHeader);
765801

802+
766803
try
767804
{
768805
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
769806

770807
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
771808
{
772-
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
809+
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchCount, queueItem.Writer.Length, queueItem.NetworkDelivery);
773810
}
774811
}
775812
finally

0 commit comments

Comments
 (0)