Skip to content

Commit db6f787

Browse files
Merge pull request #813 from AArnott/fix808_v1.9
Add secure hashing for enums with backing integers of 32-bit or less
2 parents 4da3ed6 + 95aac9c commit db6f787

File tree

2 files changed

+96
-31
lines changed

2 files changed

+96
-31
lines changed

src/MessagePack/MessagePackSecurity.cs

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -157,37 +157,54 @@ public IEqualityComparer GetEqualityComparer()
157157
/// <returns>A hash collision resistant equality comparer.</returns>
158158
protected virtual IEqualityComparer<T> GetHashCollisionResistantEqualityComparer<T>()
159159
{
160-
// For anything 32-bits and under, our fallback base secure hasher is usually adequate since it makes the hash unpredictable.
161-
// We should have special implementations for any value that is larger than 32-bits in order to make sure
162-
// that all the data gets hashed securely rather than trivially and predictably compressed into 32-bits before being hashed.
163-
// We also have to specially handle some 32-bit types (e.g. float) where multiple in-memory representations should hash to the same value.
164-
// Any type supported by the PrimitiveObjectFormatter should be added here if supporting it as a key in a collection makes sense.
165-
return
166-
// 32-bits or smaller:
167-
typeof(T) == typeof(bool) ? CollisionResistantHasher<T>.Instance :
168-
typeof(T) == typeof(char) ? CollisionResistantHasher<T>.Instance :
169-
typeof(T) == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
170-
typeof(T) == typeof(byte) ? CollisionResistantHasher<T>.Instance :
171-
typeof(T) == typeof(short) ? CollisionResistantHasher<T>.Instance :
172-
typeof(T) == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
173-
typeof(T) == typeof(int) ? CollisionResistantHasher<T>.Instance :
174-
typeof(T) == typeof(uint) ? CollisionResistantHasher<T>.Instance :
175-
176-
// Larger than 32-bits (or otherwise require special handling):
177-
typeof(T) == typeof(long) ? (IEqualityComparer<T>)Int64EqualityComparer.Instance :
178-
typeof(T) == typeof(ulong) ? (IEqualityComparer<T>)UInt64EqualityComparer.Instance :
179-
typeof(T) == typeof(float) ? (IEqualityComparer<T>)SingleEqualityComparer.Instance :
180-
typeof(T) == typeof(double) ? (IEqualityComparer<T>)DoubleEqualityComparer.Instance :
181-
typeof(T) == typeof(string) ? (IEqualityComparer<T>)StringEqualityComparer.Instance :
182-
typeof(T) == typeof(Guid) ? (IEqualityComparer<T>)GuidEqualityComparer.Instance :
183-
typeof(T) == typeof(DateTime) ? (IEqualityComparer<T>)DateTimeEqualityComparer.Instance :
184-
typeof(T) == typeof(DateTimeOffset) ? (IEqualityComparer<T>)DateTimeOffsetEqualityComparer.Instance :
185-
typeof(T) == typeof(object) ? (IEqualityComparer<T>)this.objectFallbackEqualityComparer :
186-
187-
// Any type we don't explicitly whitelist here shouldn't be allowed to use as the key in a hash-based collection since it isn't known to be hash resistant.
188-
// This method can of course be overridden to add more hash collision resistant type support, or the deserializing party can indicate that the data is Trusted
189-
// so that this method doesn't even get called.
190-
throw new TypeAccessException($"No hash-resistant equality comparer available for type: {typeof(T)}");
160+
IEqualityComparer<T> result = null;
161+
if (typeof(T).GetTypeInfo().IsEnum)
162+
{
163+
Type underlyingType = typeof(T).GetTypeInfo().GetEnumUnderlyingType();
164+
result =
165+
underlyingType == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
166+
underlyingType == typeof(byte) ? CollisionResistantHasher<T>.Instance :
167+
underlyingType == typeof(short) ? CollisionResistantHasher<T>.Instance :
168+
underlyingType == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
169+
underlyingType == typeof(int) ? CollisionResistantHasher<T>.Instance :
170+
underlyingType == typeof(uint) ? CollisionResistantHasher<T>.Instance :
171+
null;
172+
}
173+
else
174+
{
175+
// For anything 32-bits and under, our fallback base secure hasher is usually adequate since it makes the hash unpredictable.
176+
// We should have special implementations for any value that is larger than 32-bits in order to make sure
177+
// that all the data gets hashed securely rather than trivially and predictably compressed into 32-bits before being hashed.
178+
// We also have to specially handle some 32-bit types (e.g. float) where multiple in-memory representations should hash to the same value.
179+
// Any type supported by the PrimitiveObjectFormatter should be added here if supporting it as a key in a collection makes sense.
180+
result =
181+
// 32-bits or smaller:
182+
typeof(T) == typeof(bool) ? CollisionResistantHasher<T>.Instance :
183+
typeof(T) == typeof(char) ? CollisionResistantHasher<T>.Instance :
184+
typeof(T) == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
185+
typeof(T) == typeof(byte) ? CollisionResistantHasher<T>.Instance :
186+
typeof(T) == typeof(short) ? CollisionResistantHasher<T>.Instance :
187+
typeof(T) == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
188+
typeof(T) == typeof(int) ? CollisionResistantHasher<T>.Instance :
189+
typeof(T) == typeof(uint) ? CollisionResistantHasher<T>.Instance :
190+
191+
// Larger than 32-bits (or otherwise require special handling):
192+
typeof(T) == typeof(long) ? (IEqualityComparer<T>)Int64EqualityComparer.Instance :
193+
typeof(T) == typeof(ulong) ? (IEqualityComparer<T>)UInt64EqualityComparer.Instance :
194+
typeof(T) == typeof(float) ? (IEqualityComparer<T>)SingleEqualityComparer.Instance :
195+
typeof(T) == typeof(double) ? (IEqualityComparer<T>)DoubleEqualityComparer.Instance :
196+
typeof(T) == typeof(string) ? (IEqualityComparer<T>)StringEqualityComparer.Instance :
197+
typeof(T) == typeof(Guid) ? (IEqualityComparer<T>)GuidEqualityComparer.Instance :
198+
typeof(T) == typeof(DateTime) ? (IEqualityComparer<T>)DateTimeEqualityComparer.Instance :
199+
typeof(T) == typeof(DateTimeOffset) ? (IEqualityComparer<T>)DateTimeOffsetEqualityComparer.Instance :
200+
typeof(T) == typeof(object) ? (IEqualityComparer<T>)this.objectFallbackEqualityComparer :
201+
null;
202+
}
203+
204+
// Any type we don't explicitly whitelist here shouldn't be allowed to use as the key in a hash-based collection since it isn't known to be hash resistant.
205+
// This method can of course be overridden to add more hash collision resistant type support, or the deserializing party can indicate that the data is Trusted
206+
// so that this method doesn't even get called.
207+
return result ?? throw new TypeAccessException($"No hash-resistant equality comparer available for type: {typeof(T)}");
191208
}
192209

193210
/// <summary>

tests/MessagePack.Tests/MessagePackSecurityTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ public unsafe void EqualityComparer_Double()
106106
Assert.NotEqual(eq.GetHashCode(1.0), eq.GetHashCode(2.0));
107107
}
108108

109+
[Fact]
110+
public void EqualityComparer_Enums()
111+
{
112+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt8Enum>());
113+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt8Enum>());
114+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt16Enum>());
115+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt16Enum>());
116+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt32Enum>());
117+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt32Enum>());
118+
119+
// Supporting enums with backing integers that exceed 32-bits would likely require Ref.Emit of new types
120+
// since C# doesn't let us cast T to the underlying int type.
121+
Assert.Throws<TypeAccessException>(() => MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt64Enum>());
122+
Assert.Throws<TypeAccessException>(() => MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt64Enum>());
123+
}
124+
109125
[Fact]
110126
public void EqualityComparer_ObjectFallback()
111127
{
@@ -197,4 +213,36 @@ public void TypelessFormatterWithUntrustedData_UnsafeKeys()
197213
public class ArbitraryType
198214
{
199215
}
216+
217+
public enum SomeInt8Enum : sbyte
218+
{
219+
}
220+
221+
public enum SomeUInt8Enum : byte
222+
{
223+
}
224+
225+
public enum SomeInt16Enum : short
226+
{
227+
}
228+
229+
public enum SomeUInt16Enum : ushort
230+
{
231+
}
232+
233+
public enum SomeInt32Enum : int
234+
{
235+
}
236+
237+
public enum SomeUInt32Enum : uint
238+
{
239+
}
240+
241+
public enum SomeInt64Enum : long
242+
{
243+
}
244+
245+
public enum SomeUInt64Enum : ulong
246+
{
247+
}
200248
}

0 commit comments

Comments
 (0)