Skip to content

Commit ea7ef0f

Browse files
authored
Version 0.12.0 release
Version 0.12.0 release
2 parents 10a8b03 + d26d24c commit ea7ef0f

17 files changed

+374
-35
lines changed

README.md

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,74 @@ var serializer = new Serializer(options);
4848

4949
This is essential for frameworks like Akka.NET where we need to be able to resolve live Actor References in the deserializing system.
5050

51+
## Whitelisting Types On Deserialization
52+
53+
Sometimes we need to limit the types that are allowed to be deserialized for security reasons. For this reason, you can either pass a class instance that implements the `ITypeFilter` interface into the `SerializerOptions` or use the `TypeFilterBuilder` class to build a `TypeFilter` that Hyperion can use to filter out any possibly harmful injection attack during deserialization.
54+
55+
using the `ITypeFilter` interface:
56+
57+
```c#
58+
public sealed class TypeFilter : ITypeFilter
59+
{
60+
public ImmutableHashSet<string> FilteredTypes { get; }
61+
62+
internal TypeFilter(IEnumerable<Type> types)
63+
{
64+
FilteredTypes = types.Select(t => t.GetShortAssemblyQualifiedName()).ToImmutableHashSet();
65+
}
66+
67+
public bool IsAllowed(string typeName)
68+
=> FilteredTypes.Any(t => t == typeName);
69+
}
70+
```
71+
72+
using the `TypeFilterBuilder` convenience builder:
73+
74+
```c#
75+
var typeFilter = TypeFilterBuilder.Create()
76+
.Include<AllowedClassA>()
77+
.Include<AllowedClassB>()
78+
.Build();
79+
80+
var options = SerializerOptions.Default
81+
.WithTypeFilter(typeFilter);
82+
83+
var serializer = new Serializer(options);
84+
```
85+
86+
### Convert Whitelist To Blacklist
87+
88+
To do blacklisting instead of whitelisting a list of types, you will need to do a slight modification to the TypeFilter class.
89+
90+
```c#
91+
public sealed class TypeFilter : ITypeFilter
92+
{
93+
public ImmutableHashSet<string> FilteredTypes { get; }
94+
95+
internal TypeFilter(IEnumerable<Type> types)
96+
{
97+
FilteredTypes = types.Select(t => t.GetShortAssemblyQualifiedName()).ToImmutableHashSet();
98+
}
99+
100+
public bool IsAllowed(string typeName)
101+
=> FilteredTypes.All(t => t != typeName);
102+
}
103+
```
104+
51105
## Version Tolerance
52106

53107
Hyperion has been designed to work in multiple modes in terms of version tolerance vs. performance.
54108

55109
1. Pre Register Types, when using "Pre registered types", Hyperion will only emit a type ID in the output stream.
56110
This results in the best performance, but is also fragile if different clients have different versions of the contract types.
57111
2. Non Versioned, this is largely the same as the above, but the serializer does not need to know about your types up front. it will embed the fully qualified typename
58-
in the outputstream. this results in a larger payload and some performance overhead.
112+
in the output stream. this results in a larger payload and some performance overhead.
59113
3. Versioned, in this mode, Hyperion will emit both type names and field information in the output stream.
60114
This allows systems to have slightly different versions of the contract types where some fields may have been added or removed.
61115

62116
Hyperion has been designed as a wire format, point to point for soft realtime scenarios.
63117
If you need a format that is durable for persistence over time.
64-
e.g. EventSourcing or for message queues, then Protobuf or MS Bond is probably a better choise as those formats have been designed for true version tolerance.
118+
e.g. EventSourcing or for message queues, then Protobuf or MS Bond is probably a better choice as those formats have been designed for true version tolerance.
65119

66120
## Performance
67121

RELEASE_NOTES.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
### 0.12.0 January 12 2022 ####
2+
3+
* Allow explicit control over which types can be deserialized [#281](https://github.com/akkadotnet/Hyperion/pull/281)
4+
5+
We've expanded our deserialization safety check to block dangerous types from being deserialized; we recommend this method as a best practice to prevent [deserialization of untrusted data](https://cwe.mitre.org/data/definitions/502.html). You can now create a custom deserialize layer type filter programmatically:
6+
7+
```c#
8+
var typeFilter = TypeFilterBuilder.Create()
9+
.Include<AllowedClassA>()
10+
.Include<AllowedClassB>()
11+
.Build();
12+
var options = SerializerOptions.Default
13+
.WithTypeFilter(typeFilter);
14+
var serializer = new Serializer(options);
15+
```
16+
17+
For complete documentation, please read the [readme on filtering types for secure deserialization.](https://github.com/akkadotnet/Hyperion#whitelisting-types-on-deserialization)
18+
119
### 0.11.2 October 7 2021 ####
220
* Fix exception thrown during deserialization when preserve object reference was turned on
321
and a surrogate instance was inserted into a collection multiple times. [#264](https://github.com/akkadotnet/Hyperion/pull/264)

src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ namespace Hyperion
4141
public void TrackDeserializedType([Hyperion.Internal.NotNull] System.Type type) { }
4242
public void TrackDeserializedTypeWithVersion([Hyperion.Internal.NotNull] System.Type type, [Hyperion.Internal.NotNull] Hyperion.TypeVersionInfo versionInfo) { }
4343
}
44+
public sealed class DisabledTypeFilter : Hyperion.ITypeFilter
45+
{
46+
public static readonly Hyperion.DisabledTypeFilter Instance;
47+
public bool IsAllowed(string typeName) { }
48+
}
4449
public delegate object FieldInfoReader(object obj);
4550
public delegate void FieldInfoWriter(object obj, object value);
4651
public delegate void FieldReader(System.IO.Stream stream, object obj, Hyperion.DeserializerSession session);
@@ -49,6 +54,10 @@ namespace Hyperion
4954
{
5055
void BuildSerializer(Hyperion.Serializer serializer, Hyperion.ValueSerializers.ObjectSerializer objectSerializer);
5156
}
57+
public interface ITypeFilter
58+
{
59+
bool IsAllowed(string typeName);
60+
}
5261
[System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.All, AllowMultiple=false, Inherited=true)]
5362
public sealed class IgnoreAttribute : System.Attribute
5463
{
@@ -90,18 +99,24 @@ namespace Hyperion
9099
public class SerializerOptions
91100
{
92101
public static readonly Hyperion.SerializerOptions Default;
93-
[System.Obsolete]
102+
[System.Obsolete("This constructor is deprecated and will be removed in the future, please use the " +
103+
"one with the packageNameOverrides argument")]
94104
public SerializerOptions(bool versionTolerance = false, bool preserveObjectReferences = false, System.Collections.Generic.IEnumerable<Hyperion.Surrogate> surrogates = null, System.Collections.Generic.IEnumerable<Hyperion.SerializerFactories.ValueSerializerFactory> serializerFactories = null, System.Collections.Generic.IEnumerable<System.Type> knownTypes = null, bool ignoreISerializable = false) { }
95-
[System.Obsolete]
105+
[System.Obsolete("This constructor is deprecated and will be removed in the future, please use the " +
106+
"one with the disallowUnsafeTypes argument")]
96107
public SerializerOptions(bool versionTolerance, bool preserveObjectReferences, System.Collections.Generic.IEnumerable<Hyperion.Surrogate> surrogates, System.Collections.Generic.IEnumerable<Hyperion.SerializerFactories.ValueSerializerFactory> serializerFactories, System.Collections.Generic.IEnumerable<System.Type> knownTypes, bool ignoreISerializable, System.Collections.Generic.IEnumerable<System.Func<string, string>> packageNameOverrides) { }
108+
[System.Obsolete("This constructor is deprecated and will be removed in the future, please use the " +
109+
"one with the typeFilter argument")]
97110
public SerializerOptions(bool versionTolerance, bool preserveObjectReferences, System.Collections.Generic.IEnumerable<Hyperion.Surrogate> surrogates, System.Collections.Generic.IEnumerable<Hyperion.SerializerFactories.ValueSerializerFactory> serializerFactories, System.Collections.Generic.IEnumerable<System.Type> knownTypes, bool ignoreISerializable, System.Collections.Generic.IEnumerable<System.Func<string, string>> packageNameOverrides, bool disallowUnsafeTypes) { }
111+
public SerializerOptions(bool versionTolerance, bool preserveObjectReferences, System.Collections.Generic.IEnumerable<Hyperion.Surrogate> surrogates, System.Collections.Generic.IEnumerable<Hyperion.SerializerFactories.ValueSerializerFactory> serializerFactories, System.Collections.Generic.IEnumerable<System.Type> knownTypes, bool ignoreISerializable, System.Collections.Generic.IEnumerable<System.Func<string, string>> packageNameOverrides, bool disallowUnsafeTypes, Hyperion.ITypeFilter typeFilter) { }
98112
public Hyperion.SerializerOptions WithDisallowUnsafeType(bool disallowUnsafeType) { }
99113
public Hyperion.SerializerOptions WithIgnoreSerializable(bool ignoreISerializable) { }
100114
public Hyperion.SerializerOptions WithKnownTypes(System.Collections.Generic.IEnumerable<System.Type> knownTypes) { }
101115
public Hyperion.SerializerOptions WithPackageNameOverrides(System.Collections.Generic.IEnumerable<System.Func<string, string>> packageNameOverrides) { }
102116
public Hyperion.SerializerOptions WithPreserveObjectReferences(bool preserveObjectReferences) { }
103117
public Hyperion.SerializerOptions WithSerializerFactory(System.Collections.Generic.IEnumerable<Hyperion.SerializerFactories.ValueSerializerFactory> serializerFactories) { }
104118
public Hyperion.SerializerOptions WithSurrogates(System.Collections.Generic.IEnumerable<Hyperion.Surrogate> surrogates) { }
119+
public Hyperion.SerializerOptions WithTypeFilter(Hyperion.ITypeFilter typeFilter) { }
105120
public Hyperion.SerializerOptions WithVersionTolerance(bool versionTolerance) { }
106121
}
107122
public class SerializerSession
@@ -130,6 +145,18 @@ namespace Hyperion
130145
{
131146
public Surrogate(System.Func<TSource, TSurrogate> toSurrogate, System.Func<TSurrogate, TSource> fromSurrogate) { }
132147
}
148+
public sealed class TypeFilter : Hyperion.ITypeFilter
149+
{
150+
public System.Collections.Immutable.ImmutableHashSet<string> FilteredTypes { get; }
151+
public bool IsAllowed(string typeName) { }
152+
}
153+
public class TypeFilterBuilder
154+
{
155+
public Hyperion.TypeFilter Build() { }
156+
public Hyperion.TypeFilterBuilder Include(System.Type type) { }
157+
public Hyperion.TypeFilterBuilder Include<T>() { }
158+
public static Hyperion.TypeFilterBuilder Create() { }
159+
}
133160
public class TypeVersionInfo
134161
{
135162
public TypeVersionInfo() { }
@@ -591,6 +618,10 @@ namespace Hyperion.Internal
591618
public Hyperion.Internal.ImplicitUseTargetFlags TargetFlags { get; }
592619
public Hyperion.Internal.ImplicitUseKindFlags UseKindFlags { get; }
593620
}
621+
public class UserEvilDeserializationException : Hyperion.Internal.EvilDeserializationException
622+
{
623+
public UserEvilDeserializationException(string message, string typeString) { }
624+
}
594625
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter | System.AttributeTargets.All)]
595626
public sealed class ValueProviderAttribute : System.Attribute
596627
{

src/Hyperion.Benchmarks/Program.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ class Program
88
{
99
static void Main(string[] args)
1010
{
11-
var benchmark = BenchmarkSwitcher.FromAssembly(Assembly.GetExecutingAssembly());
12-
benchmark.RunAll();
11+
var benchmark = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly);
12+
13+
if (args.Length == 0)
14+
{
15+
benchmark.RunAll();
16+
}
17+
else
18+
{
19+
benchmark.Run(args);
20+
}
1321
}
1422
}
1523
}

src/Hyperion.Benchmarks/SerializeClassesBenchmark.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#endregion
99

1010
using System;
11+
using System.Runtime.CompilerServices;
1112
using BenchmarkDotNet.Attributes;
1213

1314
namespace Hyperion.Benchmarks
@@ -20,9 +21,25 @@ public class SerializeClassesBenchmark : HyperionBenchmark
2021
private LargeSealedClass sealedObject;
2122
private GenericClass<int, string, bool, DateTime, Guid> genericObject;
2223

24+
private Serializer _filteredSerializer;
25+
2326
protected override void Init()
2427
{
25-
Serializer = new Serializer(new SerializerOptions(preserveObjectReferences:true));
28+
var baseOptions = new SerializerOptions(preserveObjectReferences: true);
29+
Serializer = new Serializer(baseOptions);
30+
31+
var filteredOptions = baseOptions
32+
.WithTypeFilter(
33+
TypeFilterBuilder.Create()
34+
.Include<CyclicClassA>()
35+
.Include<CyclicClassB>()
36+
.Include<VirtualTestClass>()
37+
.Include<LargeSealedClass>()
38+
.Include<GenericClass<int, string, bool, DateTime, Guid>>()
39+
.Include<TestEnum>()
40+
.Build());
41+
_filteredSerializer = new Serializer(filteredOptions);
42+
2643
var a = new CyclicClassA();
2744
var b = new CyclicClassB();
2845
a.B = b;
@@ -45,9 +62,23 @@ protected override void Init()
4562
#endregion
4663

4764
[Benchmark] public void Cyclic_References() => SerializeAndDeserialize(cyclic);
65+
[Benchmark] public void Filtered_Cyclic_References() => SerializeAndFilteredDeserialize(cyclic);
4866
[Benchmark] public void Virtual_Classes() => SerializeAndDeserialize(virtualObject);
67+
[Benchmark] public void Filtered_Virtual_Classes() => SerializeAndFilteredDeserialize(virtualObject);
4968
[Benchmark] public void Large_Sealed_Classes() => SerializeAndDeserialize(sealedObject);
69+
[Benchmark] public void Filtered_Large_Sealed_Classes() => SerializeAndFilteredDeserialize(sealedObject);
5070
[Benchmark] public void Generic_Classes() => SerializeAndDeserialize(genericObject);
71+
[Benchmark] public void Filtered_Generic_Classes() => SerializeAndFilteredDeserialize(genericObject);
72+
73+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
74+
private void SerializeAndFilteredDeserialize<T>(T elem)
75+
{
76+
Serializer.Serialize(elem, Stream);
77+
Stream.Position = 0;
78+
79+
_filteredSerializer.Deserialize<T>(Stream);
80+
Stream.Position = 0;
81+
}
5182
}
5283

5384
#region test data types

src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// -----------------------------------------------------------------------
88
#endregion
99

10+
using System.Runtime.CompilerServices;
1011
using System.Runtime.InteropServices;
1112
using BenchmarkDotNet.Attributes;
1213

@@ -15,12 +16,25 @@ namespace Hyperion.Benchmarks
1516
public class SerializeStructsBenchmark : HyperionBenchmark
1617
{
1718
#region init
19+
private Serializer _filteredSerializer;
20+
1821
private StandardStruct standardValue;
1922
private BlittableStruct blittableValue;
2023
private TestEnum testEnum;
2124

2225
protected override void Init()
2326
{
27+
base.Init();
28+
29+
var filteredOptions = SerializerOptions.Default
30+
.WithTypeFilter(
31+
TypeFilterBuilder.Create()
32+
.Include<StandardStruct>()
33+
.Include<BlittableStruct>()
34+
.Include<TestEnum>()
35+
.Build());
36+
_filteredSerializer = new Serializer(filteredOptions);
37+
2438
standardValue = new StandardStruct(1, "John", "Doe", isLoggedIn: false);
2539
blittableValue = new BlittableStruct(59, 92);
2640
testEnum = TestEnum.HatesAll;
@@ -29,8 +43,21 @@ protected override void Init()
2943
#endregion
3044

3145
[Benchmark] public void Enums() => SerializeAndDeserialize(testEnum);
46+
[Benchmark] public void Filtered_Enums() => SerializeAndFilteredDeserialize(testEnum);
3247
[Benchmark] public void Standard_Value_Types() => SerializeAndDeserialize(standardValue);
48+
[Benchmark] public void Filtered_Standard_Value_Types() => SerializeAndFilteredDeserialize(standardValue);
3349
[Benchmark] public void Blittable_Value_Types() => SerializeAndDeserialize(blittableValue);
50+
[Benchmark] public void Filtered_Blittable_Value_Types() => SerializeAndFilteredDeserialize(blittableValue);
51+
52+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
53+
private void SerializeAndFilteredDeserialize<T>(T elem)
54+
{
55+
Serializer.Serialize(elem, Stream);
56+
Stream.Position = 0;
57+
58+
_filteredSerializer.Deserialize<T>(Stream);
59+
Stream.Position = 0;
60+
}
3461
}
3562

3663
#region test data types

src/Hyperion.Tests.FSharpData/Hyperion.Tests.FSharpData.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</ItemGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Update="FSharp.Core" Version="6.0.0" />
12+
<PackageReference Update="FSharp.Core" Version="6.0.1" />
1313
</ItemGroup>
1414

1515
</Project>

src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23
using Hyperion.Extensions;
34
using Hyperion.Internal;
45
using Xunit;
6+
using FluentAssertions;
57

68
namespace Hyperion.Tests
79
{
@@ -22,5 +24,64 @@ public void CantDeserializeANaughtyType()
2224
serializer.Deserialize<DirectoryInfo>(stream));
2325
}
2426
}
27+
28+
internal class ClassA
29+
{ }
30+
31+
internal class ClassB
32+
{ }
33+
34+
internal class ClassC
35+
{ }
36+
37+
[Fact]
38+
public void TypeFilterShouldThrowOnNaughtyType()
39+
{
40+
var typeFilter = TypeFilterBuilder.Create()
41+
.Include<ClassA>()
42+
.Include<ClassB>()
43+
.Build();
44+
45+
var options = SerializerOptions.Default
46+
.WithTypeFilter(typeFilter);
47+
48+
var serializer = new Serializer(options);
49+
50+
using (var stream = new MemoryStream())
51+
{
52+
serializer.Serialize(new ClassA(), stream);
53+
stream.Position = 0;
54+
Action act = () => serializer.Deserialize<ClassA>(stream);
55+
act.Should().NotThrow();
56+
57+
stream.Position = 0;
58+
Action actObj = () => serializer.Deserialize<object>(stream);
59+
actObj.Should().NotThrow();
60+
}
61+
62+
using (var stream = new MemoryStream())
63+
{
64+
serializer.Serialize(new ClassB(), stream);
65+
stream.Position = 0;
66+
Action act = () => serializer.Deserialize<ClassB>(stream);
67+
act.Should().NotThrow();
68+
69+
stream.Position = 0;
70+
Action actObj = () => serializer.Deserialize<object>(stream);
71+
actObj.Should().NotThrow();
72+
}
73+
74+
using (var stream = new MemoryStream())
75+
{
76+
serializer.Serialize(new ClassC(), stream);
77+
stream.Position = 0;
78+
Action act = () => serializer.Deserialize<ClassC>(stream);
79+
act.Should().Throw<UserEvilDeserializationException>();
80+
81+
stream.Position = 0;
82+
Action actObj = () => serializer.Deserialize<object>(stream);
83+
actObj.Should().Throw<UserEvilDeserializationException>();
84+
}
85+
}
2586
}
2687
}

0 commit comments

Comments
 (0)