Skip to content

fix: NetworkBehaviour and NetworkVariableDelta length safety checks #3405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: develop-2.0.0
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed issues with the `NetworkBehaviour` and `NetworkVariable` length safety checks. (#3405)
- Fixed issue where root level in-scene placed `NetworkObject`s would only allow the ownership permission to be no less than distributable or sessionowner. (#3407)

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ internal class SessionConfig
public const uint BypassFeatureCompatible = 1;
public const uint ServerDistributionCompatible = 2;
public const uint SessionStateToken = 3;
public const uint NetworkBehaviourSerializationSafety = 4;

// The most current session version (!!!!set this when you increment!!!!!)
public static uint PackageSessionVersion => SessionStateToken;
public static uint PackageSessionVersion => NetworkBehaviourSerializationSafety;

internal uint SessionVersion;

Expand Down
91 changes: 31 additions & 60 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1232,55 +1232,41 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie
var networkManager = NetworkManager;
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;


// Exit early if there are no NetworkVariables
if (NetworkVariableFields.Count == 0)
{
return;
}

for (int j = 0; j < NetworkVariableFields.Count; j++)
foreach (var field in NetworkVariableFields)
{
// Client-Server: Try to write values only for clients that have read permissions.
// Distributed Authority: All clients have read permissions, always try to write the value.
if (NetworkVariableFields[j].CanClientRead(targetClientId))
if (field.CanClientRead(targetClientId))
{
// Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode
// Write additional NetworkVariable information when length safety is enabled
if (ensureLengthSafety)
{
var writePos = writer.Position;
// Note: This value can't be packed because we don't know how large it will be in advance
// we reserve space for it, then write the data, then come back and fill in the space
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
writer.WriteValueSafe((ushort)0);
// we reserve space for it, then write the data, then come back and write the final size value
writer.WriteValueSafe(0);
var startPos = writer.Position;

// Write the NetworkVariable field value
// WriteFieldSynchronization will write the current value only if there are no pending changes.
// Otherwise, it will write the previous value if there are pending changes since the pending
// changes will be sent shortly after the client's synchronization.
NetworkVariableFields[j].WriteFieldSynchronization(writer);
field.WriteFieldSynchronization(writer);

// Write the NetworkVariable field value size
var size = writer.Position - startPos;
writer.Seek(writePos);
// Write the NetworkVariable field value size
writer.WriteValueSafe((ushort)size);
writer.WriteValueSafe(size);
writer.Seek(startPos + size);
}
else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology
else
{
// Write the NetworkVariable field value
// WriteFieldSynchronization will write the current value only if there are no pending changes.
// Otherwise, it will write the previous value if there are pending changes since the pending
// changes will be sent shortly after the client's synchronization.
NetworkVariableFields[j].WriteFieldSynchronization(writer);
field.WriteFieldSynchronization(writer);
}
}
else if (ensureLengthSafety)
{
// Client-Server Only: If the client cannot read this field, then skip it but write a 0 for this NetworkVariable's position
{
writer.WriteValueSafe((ushort)0);
writer.WriteValueSafe(0);
}
}
}
Expand All @@ -1300,26 +1286,20 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
var networkManager = NetworkManager;
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;

// Exit early if nothing else to read
if (NetworkVariableFields.Count == 0)
foreach (var field in NetworkVariableFields)
{
return;
}

for (int j = 0; j < NetworkVariableFields.Count; j++)
{
var varSize = (ushort)0;
int expectedBytesToRead = 0;
var readStartPos = 0;
// Client-Server: Clients that only have read permissions will try to read the value
// Distributed Authority: All clients have read permissions, always try to read the value
if (NetworkVariableFields[j].CanClientRead(clientId))
if (field.CanClientRead(clientId))
{
if (ensureLengthSafety)
{
reader.ReadValueSafe(out varSize);
if (varSize == 0)
reader.ReadValueSafe(out expectedBytesToRead);
if (expectedBytesToRead == 0)
{
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected non-zero size readable NetworkVariable! (Skipping)");
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{field.Name}] Expected non-zero size readable NetworkVariable! (Skipping)");
continue;
}
readStartPos = reader.Position;
Expand All @@ -1330,38 +1310,29 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
// If skipping and length safety, then fill in a 0 size for this one spot
if (ensureLengthSafety)
{
reader.ReadValueSafe(out ushort size);
if (size != 0)
reader.ReadValueSafe(out expectedBytesToRead);
if (expectedBytesToRead != 0)
{
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected zero size for non-readable NetworkVariable when EnsureNetworkVariableLengthSafety is enabled! (Skipping)");
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{field.Name}] Expected zero size for non-readable NetworkVariable when EnsureNetworkVariableLengthSafety is enabled! (Skipping)");
}
}
continue;
}

// Read the NetworkVariable value
NetworkVariableFields[j].ReadField(reader);
field.ReadField(reader);

// When EnsureNetworkVariableLengthSafety always do a bounds check
if (ensureLengthSafety)
{
if (reader.Position > (readStartPos + varSize))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too big. {reader.Position - (readStartPos + varSize)} bytes.");
}

reader.Seek(readStartPos + varSize);
}
else if (reader.Position < (readStartPos + varSize))
var totalBytesRead = reader.Position - readStartPos;
if (totalBytesRead != expectedBytesToRead)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too small. {(readStartPos + varSize) - reader.Position} bytes.");
NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{field.Name}] NetworkVariable read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization!");
}

reader.Seek(readStartPos + varSize);
reader.Seek(readStartPos + expectedBytesToRead);
}
}
}
Expand Down Expand Up @@ -1443,7 +1414,7 @@ internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetCli
// Save our position where we will write the final size being written so we can skip over it in the
// event an exception occurs when deserializing.
var sizePosition = writer.Position;
writer.WriteValueSafe((ushort)0);
writer.WriteValueSafe(0);

// Save our position before synchronizing to determine how much was written
var positionBeforeSynchronize = writer.Position;
Expand Down Expand Up @@ -1481,7 +1452,7 @@ internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetCli
// Write the number of bytes serialized to handle exceptions on the deserialization side
var bytesWritten = finalPosition - positionBeforeSynchronize;
writer.Seek(sizePosition);
writer.WriteValueSafe((ushort)bytesWritten);
writer.WriteValueSafe(bytesWritten);
writer.Seek(finalPosition);
}
return true;
Expand All @@ -1490,7 +1461,7 @@ internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetCli
{
var reader = serializer.GetFastBufferReader();
// We will always read the expected byte count
reader.ReadValueSafe(out ushort expectedBytesToRead);
reader.ReadValueSafe(out int expectedBytesToRead);

// Save our position before we begin synchronization deserialization
var positionBeforeSynchronize = reader.Position;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,14 @@ internal void NetworkBehaviourUpdate(bool forceSend = false)
}
}
}

// Now, reset all the no-longer-dirty variables
foreach (var dirtyobj in m_DirtyNetworkObjects)
foreach (var dirtyObj in m_DirtyNetworkObjects)
{
dirtyobj.PostNetworkVariableWrite(forceSend);
foreach (var behaviour in dirtyObj.ChildNetworkBehaviours)
{
behaviour.PostNetworkVariableWrite(forceSend);
}
}
m_DirtyNetworkObjects.Clear();
}
Expand Down
Loading