Skip to content

Commit c968026

Browse files
Merge pull request #809 from AArnott/fix808
Add secure hashing for enums with backing integers of 32-bit or less
2 parents cff22de + 4ac0f3d commit c968026

File tree

2 files changed

+96
-31
lines changed

2 files changed

+96
-31
lines changed

src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSecurity.cs

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -145,37 +145,54 @@ public IEqualityComparer GetEqualityComparer()
145145
/// <returns>A hash collision resistant equality comparer.</returns>
146146
protected virtual IEqualityComparer<T> GetHashCollisionResistantEqualityComparer<T>()
147147
{
148-
// For anything 32-bits and under, our fallback base secure hasher is usually adequate since it makes the hash unpredictable.
149-
// We should have special implementations for any value that is larger than 32-bits in order to make sure
150-
// that all the data gets hashed securely rather than trivially and predictably compressed into 32-bits before being hashed.
151-
// We also have to specially handle some 32-bit types (e.g. float) where multiple in-memory representations should hash to the same value.
152-
// Any type supported by the PrimitiveObjectFormatter should be added here if supporting it as a key in a collection makes sense.
153-
return
154-
// 32-bits or smaller:
155-
typeof(T) == typeof(bool) ? CollisionResistantHasher<T>.Instance :
156-
typeof(T) == typeof(char) ? CollisionResistantHasher<T>.Instance :
157-
typeof(T) == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
158-
typeof(T) == typeof(byte) ? CollisionResistantHasher<T>.Instance :
159-
typeof(T) == typeof(short) ? CollisionResistantHasher<T>.Instance :
160-
typeof(T) == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
161-
typeof(T) == typeof(int) ? CollisionResistantHasher<T>.Instance :
162-
typeof(T) == typeof(uint) ? CollisionResistantHasher<T>.Instance :
163-
164-
// Larger than 32-bits (or otherwise require special handling):
165-
typeof(T) == typeof(long) ? (IEqualityComparer<T>)Int64EqualityComparer.Instance :
166-
typeof(T) == typeof(ulong) ? (IEqualityComparer<T>)UInt64EqualityComparer.Instance :
167-
typeof(T) == typeof(float) ? (IEqualityComparer<T>)SingleEqualityComparer.Instance :
168-
typeof(T) == typeof(double) ? (IEqualityComparer<T>)DoubleEqualityComparer.Instance :
169-
typeof(T) == typeof(string) ? (IEqualityComparer<T>)StringEqualityComparer.Instance :
170-
typeof(T) == typeof(Guid) ? (IEqualityComparer<T>)GuidEqualityComparer.Instance :
171-
typeof(T) == typeof(DateTime) ? (IEqualityComparer<T>)DateTimeEqualityComparer.Instance :
172-
typeof(T) == typeof(DateTimeOffset) ? (IEqualityComparer<T>)DateTimeOffsetEqualityComparer.Instance :
173-
typeof(T) == typeof(object) ? (IEqualityComparer<T>)this.objectFallbackEqualityComparer :
174-
175-
// 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.
176-
// 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
177-
// so that this method doesn't even get called.
178-
throw new TypeAccessException($"No hash-resistant equality comparer available for type: {typeof(T)}");
148+
IEqualityComparer<T> result = null;
149+
if (typeof(T).IsEnum)
150+
{
151+
Type underlyingType = typeof(T).GetEnumUnderlyingType();
152+
result =
153+
underlyingType == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
154+
underlyingType == typeof(byte) ? CollisionResistantHasher<T>.Instance :
155+
underlyingType == typeof(short) ? CollisionResistantHasher<T>.Instance :
156+
underlyingType == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
157+
underlyingType == typeof(int) ? CollisionResistantHasher<T>.Instance :
158+
underlyingType == typeof(uint) ? CollisionResistantHasher<T>.Instance :
159+
null;
160+
}
161+
else
162+
{
163+
// For anything 32-bits and under, our fallback base secure hasher is usually adequate since it makes the hash unpredictable.
164+
// We should have special implementations for any value that is larger than 32-bits in order to make sure
165+
// that all the data gets hashed securely rather than trivially and predictably compressed into 32-bits before being hashed.
166+
// We also have to specially handle some 32-bit types (e.g. float) where multiple in-memory representations should hash to the same value.
167+
// Any type supported by the PrimitiveObjectFormatter should be added here if supporting it as a key in a collection makes sense.
168+
result =
169+
// 32-bits or smaller:
170+
typeof(T) == typeof(bool) ? CollisionResistantHasher<T>.Instance :
171+
typeof(T) == typeof(char) ? CollisionResistantHasher<T>.Instance :
172+
typeof(T) == typeof(sbyte) ? CollisionResistantHasher<T>.Instance :
173+
typeof(T) == typeof(byte) ? CollisionResistantHasher<T>.Instance :
174+
typeof(T) == typeof(short) ? CollisionResistantHasher<T>.Instance :
175+
typeof(T) == typeof(ushort) ? CollisionResistantHasher<T>.Instance :
176+
typeof(T) == typeof(int) ? CollisionResistantHasher<T>.Instance :
177+
typeof(T) == typeof(uint) ? CollisionResistantHasher<T>.Instance :
178+
179+
// Larger than 32-bits (or otherwise require special handling):
180+
typeof(T) == typeof(long) ? (IEqualityComparer<T>)Int64EqualityComparer.Instance :
181+
typeof(T) == typeof(ulong) ? (IEqualityComparer<T>)UInt64EqualityComparer.Instance :
182+
typeof(T) == typeof(float) ? (IEqualityComparer<T>)SingleEqualityComparer.Instance :
183+
typeof(T) == typeof(double) ? (IEqualityComparer<T>)DoubleEqualityComparer.Instance :
184+
typeof(T) == typeof(string) ? (IEqualityComparer<T>)StringEqualityComparer.Instance :
185+
typeof(T) == typeof(Guid) ? (IEqualityComparer<T>)GuidEqualityComparer.Instance :
186+
typeof(T) == typeof(DateTime) ? (IEqualityComparer<T>)DateTimeEqualityComparer.Instance :
187+
typeof(T) == typeof(DateTimeOffset) ? (IEqualityComparer<T>)DateTimeOffsetEqualityComparer.Instance :
188+
typeof(T) == typeof(object) ? (IEqualityComparer<T>)this.objectFallbackEqualityComparer :
189+
null;
190+
}
191+
192+
// 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.
193+
// 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
194+
// so that this method doesn't even get called.
195+
return result ?? throw new TypeAccessException($"No hash-resistant equality comparer available for type: {typeof(T)}");
179196
}
180197

181198
/// <summary>

src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/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
{
@@ -182,4 +198,36 @@ public void TypelessFormatterWithUntrustedData_UnsafeKeys()
182198
public class ArbitraryType
183199
{
184200
}
201+
202+
public enum SomeInt8Enum : sbyte
203+
{
204+
}
205+
206+
public enum SomeUInt8Enum : byte
207+
{
208+
}
209+
210+
public enum SomeInt16Enum : short
211+
{
212+
}
213+
214+
public enum SomeUInt16Enum : ushort
215+
{
216+
}
217+
218+
public enum SomeInt32Enum : int
219+
{
220+
}
221+
222+
public enum SomeUInt32Enum : uint
223+
{
224+
}
225+
226+
public enum SomeInt64Enum : long
227+
{
228+
}
229+
230+
public enum SomeUInt64Enum : ulong
231+
{
232+
}
185233
}

0 commit comments

Comments
 (0)