-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sets and maps with hash collision resistance #33
Comments
Do you have any suggestions? The shapes APIs abstract away concerns such as whether a collection is a hash table or a sorted collection or non-generic so it couldn't accept
|
They don't though. That's partly why MessagePack-CSharp has had 2 security advisories on hash collision non-resistance. These collections rely on
I don't see how that would help. If an intermediate collection has collision resistance, it'll be fine. But once you copy its contents into a non-collision resistant collection, the attack will have the same impact as if the intermediate collection were not used.
I believe all (built-in) collections that use hash tables (e.g. Along these lines too, some collections take a 'hint' as to their eventual size. For msgpack, we always know the final size up front, so it would be nice if a |
Even given the API support, one challenge here is that not all key types will have a collision resistant implementation of |
Actually, your We'd need to special case a few types though, and allow the user to add to these, because the above risks violating the contract that equality implies a hash code match. There are data types (including a few primitives like |
@eiriktsarpalis An easier goal to reach in the short-term is to allow a program to defend itself against hash collision attacks. Consider this data type: [GenerateShape]
public partial class HashCollisionResistance : IEquatable<HashCollisionResistance>
{
public HashCollisionResistance()
{
this.Dictionary = new Dictionary<string, string>(SipHashEqualityComparer<string>.Default);
}
public Dictionary<string, string> Dictionary { get; }
} This class creates its own dictionary instance, pre-initialized with a hash-collision resistant equality comparer. The expectation then is that the deserializer will merely populate the collection rather than try to create the collection itself. In fact the lack of a property setter on the dictionary property enforces this. In my first test of this technique with TypeShape, it failed to deserialize anything into that collection. I'm guessing this isn't TypeShape's fault, but rather the fault of my deserialization layer, so I'll look into that. |
I was able to get it to work. AArnott/Nerdbank.MessagePack#29 adds support for read-only collection properties, thus enabling hash collision attack mitigation within user code. This isn't a great end story, since users are very likely to not be aware of this requirement on them, or miss one place in their data types where they forget to apply the mitigation. So we should keep this issue active and look for a way to do this by default like MessagePack-CSharp does. |
From #52 (comment)
That could work. I'd likely use reflection to search for a constructor that takes an |
The source generator could possibly help in that regard, e.g. by emitting In terms of what built-in support would look like, my expectation is that we would have to augment all collection construction delegates with an optional We'd also need to adapt to whatever design C# uses for dictionary expressions. Support for custom comparers is still an open question over there. |
I don't understand why this isn't a feasible strategy: private global::PolyType.Abstractions.ITypeShape<global::System.Collections.Generic.Dictionary<string, int>> Create_Dictionary_String_Int32()
{
return new global::PolyType.SourceGenModel.SourceGenDictionaryTypeShape<global::System.Collections.Generic.Dictionary<string, int>, string, int>
{
KeyType = String,
ValueType = Int32,
GetDictionaryFunc = static obj => obj,
ConstructionStrategy = global::PolyType.Abstractions.CollectionConstructionStrategy.Mutable,
DefaultConstructorFunc = static () => new global::System.Collections.Generic.Dictionary<string, int>(),
+ KeyedConstructorFunc = static (IEqualityComparer<string> eq) => new global::System.Collections.Generic.Dictionary<string, int>(eq),
AddKeyValuePairFunc = static (ref global::System.Collections.Generic.Dictionary<string, int> dict, global::System.Collections.Generic.KeyValuePair<string, int> kvp) => dict[kvp.Key] = kvp.Value,
EnumerableConstructorFunc = null,
SpanConstructorFunc = null,
Provider = this,
};
} It wouldn't require reflection at runtime, or limiting what the linker can do. It could be left as |
What about ImmutableDictionary/Set or FrozenDictionary/Set? These accept an equality comparer from factory methods so they need a separate type of constructor delegate. |
Today we have three construction strategies for collections: mutable collections, collections with an IEnumerable ctor/factory, or collections with a span ctor/factory. All three would need to support IEqualityComparer separately. |
The means that these type shapes offer to construct a map or a set do not offer a way to provide a collision resistant hash function (in the form of a custom
IEqualityComparer<TKey>
. This is important when deserializing untrusted data.The text was updated successfully, but these errors were encountered: