diff --git a/.github/DISCUSSION_TEMPLATE/bug-reports.yml b/.github/DISCUSSION_TEMPLATE/bug-reports.yml index 45472fffd..7f8dfb6e5 100644 --- a/.github/DISCUSSION_TEMPLATE/bug-reports.yml +++ b/.github/DISCUSSION_TEMPLATE/bug-reports.yml @@ -73,7 +73,7 @@ body: label: Version description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu. options: - - v1.3.0.2 + - v1.3.0.3 - v1.4.0.0 (unstable) - Other validations: diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index fa1990bcb..8c47bb289 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -775,6 +775,7 @@ namespace Barotrauma AssignRelayToServer("simulatedduplicateschance", false); AssignRelayToServer("simulatedlongloadingtime", false); AssignRelayToServer("storeinfo", false); + AssignRelayToServer("sendrawpacket", false); #endif commands.Add(new Command("clientlist", "", (string[] args) => { })); @@ -3263,6 +3264,36 @@ namespace Barotrauma LocationType.Prefabs.Select(lt => lt.Identifier.Value).ToArray() }; })); + + commands.Add(new Command("sendrawpacket", "sendrawpacket [data]: Send a string of hex values as raw binary data to the server", (string[] args) => + { + if (GameMain.NetworkMember is null) + { + ThrowError("Not connected to a server"); + return; + } + + if (args.Length == 0) + { + ThrowError("No data provided"); + return; + } + + string dataString = string.Join(" ", args); + + try + { + byte[] bytes = ToolBox.HexStringToBytes(dataString); + IWriteMessage msg = new WriteOnlyMessage(); + foreach (byte b in bytes) { msg.WriteByte(b); } + GameMain.Client?.ClientPeer?.DebugSendRawMessage(msg); + NewMessage($"Sent {bytes.Length} byte(s)", Color.Green); + } + catch (Exception e) + { + ThrowError("Failed to parse the data", e); + } + })); #endif commands.Add(new Command("limbscale", "Define the limbscale for the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (string[] args) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs index ef0b76332..f357f1a2b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs @@ -231,6 +231,8 @@ namespace Barotrauma.Networking #if DEBUG public abstract void ForceTimeOut(); + + public abstract void DebugSendRawMessage(IWriteMessage msg); #endif } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs index a2a1b7120..563c5ecd1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs @@ -287,6 +287,9 @@ namespace Barotrauma.Networking { netClient?.ServerConnection?.ForceTimeOut(); } + + public override void DebugSendRawMessage(IWriteMessage msg) + => ForwardToLidgren(msg, DeliveryMethod.Reliable); #endif } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs index d6624b4cf..9e0e1e3c3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2PClientPeer.cs @@ -423,6 +423,9 @@ namespace Barotrauma.Networking { timeout = 0.0f; } + + public override void DebugSendRawMessage(IWriteMessage msg) + => ForwardToRemotePeer(msg, DeliveryMethod.Reliable); #endif } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs index 0b2a340da..537345b0c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/P2POwnerPeer.cs @@ -142,8 +142,12 @@ namespace Barotrauma.Networking if (remotePeer is null) { return; } if (remotePeer.PendingDisconnect.IsSome()) { return; } - var peerPacketHeaders = INetSerializableStruct.Read(inc); - + if (!INetSerializableStruct.TryRead(inc, remotePeer.AccountInfo, out PeerPacketHeaders peerPacketHeaders)) + { + CommunicateDisconnectToRemotePeer(remotePeer, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData)); + return; + } + PacketHeader packetHeader = peerPacketHeaders.PacketHeader; if (packetHeader.IsConnectionInitializationStep()) @@ -178,13 +182,11 @@ namespace Barotrauma.Networking { remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.AuthenticationPending; - var packet = INetSerializableStruct.Read(inc); - - void failAuth() + if (!INetSerializableStruct.TryRead(inc, remotePeer.AccountInfo, out ClientAuthTicketAndVersionPacket packet)) { - CommunicateDisconnectToRemotePeer(remotePeer, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed)); + failAuth(); + return; } - if (!packet.AuthTicket.TryUnwrap(out var authenticationTicket)) { failAuth(); @@ -221,6 +223,11 @@ namespace Barotrauma.Networking } remotePeer.UnauthedMessages.Clear(); }); + + void failAuth() + { + CommunicateDisconnectToRemotePeer(remotePeer, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed)); + } } public override void Update(float deltaTime) @@ -381,7 +388,7 @@ namespace Barotrauma.Networking { OnInitializationComplete(); - PeerPacketMessage packet = INetSerializableStruct.Read(inc); + var packet = INetSerializableStruct.Read(inc); IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection); callbacks.OnMessageReceived.Invoke(msg); } @@ -552,6 +559,9 @@ namespace Barotrauma.Networking { //TODO: reimplement? } + + public override void DebugSendRawMessage(IWriteMessage msg) + => ForwardToServerProcess(msg); #endif } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs index 626db0eb9..047dce5cd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs @@ -520,5 +520,32 @@ namespace Barotrauma static string ColorString(string text, Color color) => $"‖color:{color.ToStringHex()}‖{text}‖end‖"; } + + /// + /// Converts a string of hex values to a byte array. + /// + /// + /// 04 03 4b 50 -> { 4, 3, 75, 80 } + /// + /// + /// + public static byte[] HexStringToBytes(string raw) + { + string value = string.Join(string.Empty, raw.Split(" ")); + List bytes = new List(); + for (int i = 0; i < value.Length; i += 2) + { + string hex = value.Substring(i, 2); + byte b = Convert.ToByte(hex, 16); + bytes.Add(b); + + static bool IsHexChar(char c) => c is + >= '0' and <= '9' or + >= 'A' and <= 'F' or + >= 'a' and <= 'f'; + } + + return bytes.ToArray(); + } } } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 64c52d9e9..d49b2ab21 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.3.0.1 + 1.3.0.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 8effce6b9..3d3148698 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.3.0.1 + 1.3.0.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 3574cc843..f5f537757 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.3.0.1 + 1.3.0.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 20678b562..5ce39bac1 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.3.0.1 + 1.3.0.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 4d80f2da0..6fd38119a 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.3.0.1 + 1.3.0.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index 1f5c680b0..ceb81bd03 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -226,7 +226,7 @@ namespace Barotrauma.Networking } else if (!packetHeader.IsConnectionInitializationStep()) { - if (connectedClients.Find(c => c.Connection.NetConnection == lidgrenMsg.SenderConnection) is not { Connection: LidgrenConnection conn }) + if (FindConnection(lidgrenMsg.SenderConnection) is not { } conn) { if (pendingClient != null) { @@ -254,6 +254,15 @@ namespace Barotrauma.Networking var packet = INetSerializableStruct.Read(inc); callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn)); } + + LidgrenConnection? FindConnection(NetConnection ligdrenConn) + { + if (connectedClients.Find(c => c.Connection.NetConnection == ligdrenConn) is { Connection: LidgrenConnection conn }) + { + return conn; + } + return null; + } } private void HandleStatusChanged(NetIncomingMessage inc) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs index 69dc977a6..4781c5e0a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/P2PServerPeer.cs @@ -105,11 +105,7 @@ namespace Barotrauma.Networking if (!started) { return; } var senderInfo = INetSerializableStruct.Read(inc); - if (!senderInfo.Endpoint.TryUnwrap(out var senderEndpoint)) - { - return; - } - + if (!senderInfo.Endpoint.TryUnwrap(out var senderEndpoint)) { return; } var (_, packetHeader, initialization) = INetSerializableStruct.Read(inc); if (packetHeader.IsServerMessage()) @@ -179,7 +175,8 @@ namespace Barotrauma.Networking { if (packetHeader.IsDataFragment()) { - var completeMessageOption = connectedClient.Defragmenter.ProcessIncomingFragment(INetSerializableStruct.Read(inc)); + var fragment = INetSerializableStruct.Read(inc); + var completeMessageOption = connectedClient.Defragmenter.ProcessIncomingFragment(fragment); if (!completeMessageOption.TryUnwrap(out var completeMessage)) { return; } IReadMessage msg = new ReadOnlyMessage(completeMessage.ToArray(), false, 0, completeMessage.Length, connectedClient.Connection); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs index 1c5d06389..fd8d39273 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs @@ -97,7 +97,11 @@ namespace Barotrauma.Networking switch (initializationStep) { case ConnectionInitialization.AuthInfoAndVersion: - var authPacket = INetSerializableStruct.Read(inc); + if (!INetSerializableStruct.TryRead(inc, pendingClient.AccountInfo, out ClientAuthTicketAndVersionPacket authPacket)) + { + RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData)); + return; + } if (!Client.IsValidName(authPacket.Name, serverSettings)) { @@ -134,7 +138,11 @@ namespace Barotrauma.Networking break; case ConnectionInitialization.Password: - var passwordPacket = INetSerializableStruct.Read(inc); + if (!INetSerializableStruct.TryRead(inc, pendingClient.AccountInfo, out ClientPeerPasswordPacket passwordPacket)) + { + RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData)); + return; + } if (pendingClient.PasswordSalt is null) { @@ -335,5 +343,20 @@ namespace Barotrauma.Networking public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true); public abstract void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket); + + private void LogMalformedMessage(NetworkConnection conn) + { + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.Connection == conn) + { + DebugConsole.ThrowError($"Received malformed message from {c.Name}."); + return; + } + } + DebugConsole.ThrowError("Received malformed message from remote peer."); + } + protected static void LogMalformedMessage() + => DebugConsole.ThrowError("Received malformed message from remote peer."); } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index d7aaef3d6..2fe048e3d 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.3.0.1 + 1.3.0.3 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index a013fabf8..dd6b41281 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -2611,6 +2611,17 @@ namespace Barotrauma errorMsg); } + private static readonly HashSet loggedErrorIdentifiers = new HashSet(); + /// + /// Log the error message, but only if an error with the same identifier hasn't been thrown yet during this session. + /// + public static void ThrowErrorOnce(string identifier, string errorMsg, Exception e) + { + if (loggedErrorIdentifiers.Contains(identifier)) { return; } + ThrowError(errorMsg, e); + loggedErrorIdentifiers.Add(identifier); + } + public static void AddWarning(string warning, ContentPackage contentPackage = null) { warning = AddContentPackageInfoToMessage($"WARNING: {warning}", contentPackage); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs index cba0112e6..e2fab1f74 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Collections.Generic; @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Text; using Barotrauma.Networking; using Microsoft.Xna.Framework; @@ -792,5 +793,60 @@ namespace Barotrauma property.Behavior.WriteAction(value!, property.Attribute, msg, bitField); } } + + public static bool TryRead(IReadMessage inc, AccountInfo sender, [NotNullWhen(true)] out T? data) where T : INetSerializableStruct + { + try + { + data = Read(inc); + return true; + } + catch (Exception e) + { + LogError(e); + data = default; + return false; + } + + void LogError(Exception e) + { + int prevPos = inc.BitPosition; + + StringBuilder hexData = new(); + inc.BitPosition = 0; + while (inc.BitPosition < inc.LengthBits) + { + byte b = inc.ReadByte(); + hexData.Append($"{b:X2} "); + } + // trim the last space if there is one + if (hexData.Length > 0) { hexData.Length--; } + + inc.BitPosition = prevPos; + + //only log the error once per sender, so this can't be abused by spamming the server with malformed data to fill up the console with errors + //note that the name is "Unknown" if the client hasn't properly joined yet, so errors when first joining are only logged once + string accountInfoName = AccountInfoToName(sender); + DebugConsole.ThrowErrorOnce( + identifier: $"INetSerializableStruct.TryRead:{accountInfoName}", + errorMsg: $"Failed to read a message by {accountInfoName}. Data: \"{hexData}\"", e); + + static string AccountInfoToName(AccountInfo info) + { + var connectedClients = + GameMain.NetworkMember?.ConnectedClients ?? Array.Empty(); + + foreach (Client c in connectedClients) + { + if (c.AccountInfo == info) + { + return c.Name; + } + } + + return info.AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : "Unknown"; + } + } + } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs index 7432c97cf..ef48e3162 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs @@ -155,6 +155,7 @@ namespace Barotrauma.Networking NameTaken, InvalidVersion, SteamP2PError, + MalformedData, //attempt reconnecting with these reasons Timeout, diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 2768565a0..9b3a8ee2f 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,9 @@ +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.3.0.3 +------------------------------------------------------------------------------------------------------------------------------------------------- + +- Fixed an exploit that allowed crashing servers by sending them specifically crafted malformed data. + ------------------------------------------------------------------------------------------------------------------------------------------------- v1.3.0.2 -------------------------------------------------------------------------------------------------------------------------------------------------