From 6e6aa78f0e60b4946dbfec48b36972ab7fbf57a6 Mon Sep 17 00:00:00 2001 From: Forest Date: Mon, 7 Jun 2021 18:42:29 -0700 Subject: [PATCH] Add test for one of the bounds checks fixed --- Hazel.UnitTests/Dtls/ConnectionTests.cs | 90 +++++++++++++++++++ Hazel/Dtls/DtlsUnityConnection.cs | 62 ++++++++++++- .../ThreadLimitedUdpConnectionListener.cs | 2 + 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/Hazel.UnitTests/Dtls/ConnectionTests.cs b/Hazel.UnitTests/Dtls/ConnectionTests.cs index 0b9073e..5a3c37d 100644 --- a/Hazel.UnitTests/Dtls/ConnectionTests.cs +++ b/Hazel.UnitTests/Dtls/ConnectionTests.cs @@ -172,6 +172,55 @@ IsdbLCwHYD3GVgk/D7NVxyU= } } + class MalformedDTLSClient : DtlsUnityConnection + { + public MalformedDTLSClient(ILogger logger, IPEndPoint remoteEndPoint, IPMode ipMode = IPMode.IPv4) : base(logger, remoteEndPoint, ipMode) + { + + } + + protected override void SendClientHello() + { + Test_SendClientHello((clientHello, writer) => + { + ByteSpanBigEndianExtensions.WriteBigEndian16(writer, (ushort)ProtocolVersion.DTLS1_2); + writer = writer.Slice(2); + + clientHello.Random.CopyTo(writer); + writer = writer.Slice(Hazel.Dtls.Random.Size); + + // Do not encode session ids + writer[0] = (byte)0; + writer = writer.Slice(1); + + writer[0] = (byte)clientHello.Cookie.Length; + clientHello.Cookie.CopyTo(writer.Slice(1)); + writer = writer.Slice(1 + clientHello.Cookie.Length); + + ByteSpanBigEndianExtensions.WriteBigEndian16(writer, (ushort)clientHello.CipherSuites.Length); + clientHello.CipherSuites.CopyTo(writer.Slice(2)); + writer = writer.Slice(2 + clientHello.CipherSuites.Length); + + // ============ Here is the corruption. writer[0] should be 1. ============ + writer[0] = 255; + writer[1] = (byte)CompressionMethod.Null; + writer = writer.Slice(2); + + // Extensions size + ByteSpanBigEndianExtensions.WriteBigEndian16(writer, (ushort)(6 + clientHello.SupportedCurves.Length)); + writer = writer.Slice(2); + + // Supported curves extension + ByteSpanBigEndianExtensions.WriteBigEndian16(writer, (ushort)ExtensionType.EllipticCurves); + ByteSpanBigEndianExtensions.WriteBigEndian16(writer, (ushort)(2 + clientHello.SupportedCurves.Length), 2); + ByteSpanBigEndianExtensions.WriteBigEndian16(writer, (ushort)clientHello.SupportedCurves.Length, 4); + clientHello.SupportedCurves.CopyTo(writer.Slice(6)); + + return writer; + }); + } + } + [TestMethod] public void TestMalformedApplicationData() { @@ -227,6 +276,47 @@ IsdbLCwHYD3GVgk/D7NVxyU= } } + [TestMethod] + public void TestMalformedConnectionData() + { + IPEndPoint ep = new IPEndPoint(IPAddress.Loopback, 27510); + + IPEndPoint connectionEndPoint = ep; + DtlsConnectionListener.ConnectionId connectionId = new ThreadLimitedUdpConnectionListener.ConnectionId(); + + Semaphore signal = new Semaphore(0, int.MaxValue); + + using (DtlsConnectionListener listener = new DtlsConnectionListener(2, new IPEndPoint(IPAddress.Any, ep.Port), new TestLogger())) + using (MalformedDTLSClient connection = new MalformedDTLSClient(new TestLogger(), ep)) + { + listener.SetCertificate(GetCertificateForServer()); + connection.SetValidServerCertificates(GetCertificateForClient()); + + listener.NewConnection += (evt) => + { + connectionEndPoint = evt.Connection.EndPoint; + connectionId = ((ThreadLimitedUdpServerConnection)evt.Connection).ConnectionId; + + signal.Release(); + evt.Connection.Disconnected += (o, et) => { + }; + }; + connection.Disconnected += (o, evt) => { + signal.Release(); + }; + + listener.Start(); + connection.Connect(); + + Assert.IsTrue(listener.ReceiveThreadRunning, "Listener should be able to handle a malformed hello packet"); + Assert.AreEqual(ConnectionState.NotConnected, connection.State); + + // wait for the client to disconnect + listener.Dispose(); + signal.WaitOne(100); + } + } + [TestMethod] public void TestResentHandshakeConnects() { diff --git a/Hazel/Dtls/DtlsUnityConnection.cs b/Hazel/Dtls/DtlsUnityConnection.cs index a11bd0d..516cf69 100644 --- a/Hazel/Dtls/DtlsUnityConnection.cs +++ b/Hazel/Dtls/DtlsUnityConnection.cs @@ -867,7 +867,7 @@ namespace Hazel.Dtls /// /// Send (resend) a ClientHello message to the server /// - private void SendClientHello() + protected virtual void SendClientHello() { // Reset our verification stream this.nextEpoch.VerificationStream.Reset(); @@ -926,6 +926,66 @@ namespace Hazel.Dtls base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length); } + protected void Test_SendClientHello(Func encodeCallback) + { + // Reset our verification stream + this.nextEpoch.VerificationStream.Reset(); + + // Describe our ClientHello flight + ClientHello clientHello = new ClientHello(); + clientHello.Random = this.nextEpoch.ClientRandom; + clientHello.Cookie = this.nextEpoch.Cookie; + clientHello.CipherSuites = new byte[2]; + clientHello.CipherSuites.WriteBigEndian16((ushort)CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256); + clientHello.SupportedCurves = new byte[2]; + clientHello.SupportedCurves.WriteBigEndian16((ushort)NamedCurve.x25519); + + Handshake handshake = new Handshake(); + handshake.MessageType = HandshakeType.ClientHello; + handshake.Length = (uint)clientHello.CalculateSize(); + handshake.MessageSequence = 0; + handshake.FragmentOffset = 0; + handshake.FragmentLength = handshake.Length; + + // Describe the record + int plaintextLength = (int)(Handshake.Size + handshake.Length); + Record outgoingRecord = new Record(); + outgoingRecord.ContentType = ContentType.Handshake; + outgoingRecord.Epoch = this.epoch; + outgoingRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence; + outgoingRecord.Length = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(plaintextLength); + ++this.currentEpoch.NextOutgoingSequence; + + // Convert the record to wire format + ByteSpan packet = new byte[Record.Size + outgoingRecord.Length]; + ByteSpan writer = packet; + outgoingRecord.Encode(packet); + writer = writer.Slice(Record.Size); + handshake.Encode(writer); + writer = writer.Slice(Handshake.Size); + + writer = encodeCallback(clientHello, writer); + + // Write ClientHello to the verification stream + this.nextEpoch.VerificationStream.AddData( + packet.Slice( + Record.Size + , Handshake.Size + (int)handshake.Length + ) + ); + + // Protect the record + this.currentEpoch.RecordProtection.EncryptClientPlaintext( + packet.Slice(Record.Size, outgoingRecord.Length) + , packet.Slice(Record.Size, plaintextLength) + , ref outgoingRecord + ); + + this.nextEpoch.State = HandshakeState.ExpectingServerHello; + this.nextEpoch.NextPacketResendTime = DateTime.UtcNow + this.handshakeResendTimeout; + base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length); + } + /// /// Send (resend) the ClientKeyExchange flight /// diff --git a/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs b/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs index deda91d..e604765 100644 --- a/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs +++ b/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs @@ -50,6 +50,8 @@ namespace Hazel.Udp.FewerThreads private Thread sendThread; private HazelThreadPool processThreads; + public bool ReceiveThreadRunning => this.receiveThread.ThreadState == ThreadState.Running; + public struct ConnectionId : IEquatable { public IPEndPoint EndPoint; -- 2.39.5