msg.Write("HO");
msg.EndMessage();
msg.StartMessage(2);
- msg.Write("NO");
msg.EndMessage();
msg.EndMessage();
Assert.AreEqual("HO", sub.ReadString());
sub = reader.ReadMessage();
- Assert.AreEqual(3, sub.Length);
+ Assert.AreEqual(0, sub.Length);
Assert.AreEqual(2, sub.Tag);
- Assert.AreEqual("NO", sub.ReadString());
}
[TestMethod]
catch (InvalidDataException) { }
}
+ [TestMethod]
+ public void ReadMessageProtectsAgainstOverrun()
+ {
+ const string TestDataFromAPreviousPacket = "You shouldn't be able to see this data";
+
+ // An extra byte from the length of TestData when written via MessageWriter
+ // Extra 3 bytes for the length + tag header for ReadMessage.
+ int DataLength = TestDataFromAPreviousPacket.Length + 1 + 3;
+
+ // THE BUG
+ //
+ // No bound checks. When the server wants to read a message, it
+ // reads the uint16 at that offset, treats it as a length without any bound checks.
+ // This can be allow a later ReadString or ReadBytes to create an infoleak.
+
+ MessageWriter writer = MessageWriter.Get(SendOption.None);
+
+ // This is the malicious length. No data in this message, so it should be zero.
+ writer.Write((ushort)1);
+ writer.Write((byte)0); // Tag
+
+ // This is data from a "previous packet"
+ writer.Write(TestDataFromAPreviousPacket);
+
+ byte[] testData = writer.ToByteArray(includeHeader: false);
+
+ Assert.AreEqual(DataLength, testData.Length);
+
+ var outer = MessageReader.Get(testData);
+
+ // Length is just the malicious message header.
+ outer.Length = 3;
+
+ try
+ {
+ outer.ReadMessage();
+ Assert.Fail("ReadMessage is expected to throw");
+ }
+ catch (InvalidDataException) { }
+ }
+
[TestMethod]
public void GetLittleEndian()
{
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
-VisualStudioVersion = 16.0.30621.155
+VisualStudioVersion = 16.0.30523.141
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hazel", "Hazel\Hazel.csproj", "{02CFBD30-D77D-400F-94B2-700F60EFDD7F}"
EndProject
public MessageReader ReadMessage()
{
// Ensure there is at least a header
- if (this.readHead + 3 > this.Buffer.Length) return null;
+ if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}");
var output = new MessageReader();
output.Offset += 3;
output.Position = 0;
+ if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message length is longer than message length: {output.Length + 3} of {this.BytesRemaining}");
+
this.Position += output.Length + 3;
return output;
}
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Hazel")]
-[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Build Number
// Revision
//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
-// NuGet version information
-[assembly: AssemblyInformationalVersion("0.1.2-beta")]
-
// Show internals to unit testing assembly so it can test
[assembly:InternalsVisibleTo("Hazel.UnitTests")]
\ No newline at end of file