From 52ec7921a4d35b6361fe6d98701b8c075fa0e1ad Mon Sep 17 00:00:00 2001 From: "Jarle B. Hjortand" <007536@student.usn.no> Date: Thu, 21 Oct 2021 20:15:10 +0200 Subject: [PATCH 01/13] Use IOException instead of FileLoadException to handle #269 where an FileNotFoundExeption is thrown. (#270) Co-authored-by: Jarle Hjortland --- src/Hyperion/Extensions/TypeEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hyperion/Extensions/TypeEx.cs b/src/Hyperion/Extensions/TypeEx.cs index 491b4851..59a7105e 100644 --- a/src/Hyperion/Extensions/TypeEx.cs +++ b/src/Hyperion/Extensions/TypeEx.cs @@ -210,7 +210,7 @@ public static Type LoadTypeByName(string name, bool disallowUnsafeTypes) "Unsafe Type Deserialization Detected!", name); return type; } - catch (FileLoadException) + catch (IOException) { var typename = ToQualifiedAssemblyName(name, ignoreAssemblyVersion: true); var type = Type.GetType(typename, true); From d8124f0bc8f4056213f2360d20d7395e086aa651 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 18:21:04 +0000 Subject: [PATCH 02/13] Bump AkkaVersion from 1.4.26 to 1.4.27 (#271) Bumps `AkkaVersion` from 1.4.26 to 1.4.27. Updates `Akka` from 1.4.26 to 1.4.27 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.26...1.4.27) Updates `Akka.Serialization.Hyperion` from 1.4.26 to 1.4.27 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.26...1.4.27) Updates `Akka.TestKit.Xunit2` from 1.4.26 to 1.4.27 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.26...1.4.27) --- updated-dependencies: - dependency-name: Akka dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Akka.Serialization.Hyperion dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Akka.TestKit.Xunit2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index 1395c256..87367ccb 100644 --- a/src/common.props +++ b/src/common.props @@ -21,7 +21,7 @@ Please report any serialization problem that occurs after an upgrade to this ver net5.0 net471 netstandard2.0 - 1.4.26 + 1.4.27 6.1.0 2.4.1 2.4.3 From 6bd4be8e303f75c6c7c40f041dd64d6aa0089f05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 10:14:37 -0500 Subject: [PATCH 03/13] Bump FluentAssertions from 6.1.0 to 6.2.0 (#272) Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.1.0 to 6.2.0. - [Release notes](https://github.com/fluentassertions/fluentassertions/releases) - [Changelog](https://github.com/fluentassertions/fluentassertions/blob/master/AcceptApiChanges.ps1) - [Commits](https://github.com/fluentassertions/fluentassertions/compare/6.1.0...6.2.0) --- updated-dependencies: - dependency-name: FluentAssertions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index 87367ccb..c8742453 100644 --- a/src/common.props +++ b/src/common.props @@ -22,7 +22,7 @@ Please report any serialization problem that occurs after an upgrade to this ver net471 netstandard2.0 1.4.27 - 6.1.0 + 6.2.0 2.4.1 2.4.3 16.11.0 From 2ad62a2e037816a019937e1f59004b3eb6809a8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 02:51:26 -0500 Subject: [PATCH 04/13] Bump Microsoft.NET.Test.Sdk from 16.11.0 to 17.0.0 (#273) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.11.0 to 17.0.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.11.0...v17.0.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index c8742453..906bc89c 100644 --- a/src/common.props +++ b/src/common.props @@ -25,7 +25,7 @@ Please report any serialization problem that occurs after an upgrade to this ver 6.2.0 2.4.1 2.4.3 - 16.11.0 + 17.0.0 1.2.2 \ No newline at end of file From 35c3b9e357ffa6057efcddafdccc4ee2c182da10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Nov 2021 17:52:15 -0500 Subject: [PATCH 05/13] Bump FSharp.Core from 6.0.0 to 6.0.1 (#274) Bumps [FSharp.Core](https://github.com/dotnet/fsharp) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/dotnet/fsharp/releases) - [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md) - [Commits](https://github.com/dotnet/fsharp/commits) --- updated-dependencies: - dependency-name: FSharp.Core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/Hyperion.Tests.FSharpData/Hyperion.Tests.FSharpData.fsproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hyperion.Tests.FSharpData/Hyperion.Tests.FSharpData.fsproj b/src/Hyperion.Tests.FSharpData/Hyperion.Tests.FSharpData.fsproj index 14373005..d8fa6533 100644 --- a/src/Hyperion.Tests.FSharpData/Hyperion.Tests.FSharpData.fsproj +++ b/src/Hyperion.Tests.FSharpData/Hyperion.Tests.FSharpData.fsproj @@ -9,7 +9,7 @@ - + From 1296c6db79badba4d7f113126a927d553da01423 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Dec 2021 09:03:59 -0600 Subject: [PATCH 06/13] Bump AkkaVersion from 1.4.27 to 1.4.29 (#278) Bumps `AkkaVersion` from 1.4.27 to 1.4.29. Updates `Akka` from 1.4.27 to 1.4.29 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.27...1.4.29) Updates `Akka.Serialization.Hyperion` from 1.4.27 to 1.4.29 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.27...1.4.29) Updates `Akka.TestKit.Xunit2` from 1.4.27 to 1.4.29 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.27...1.4.29) --- updated-dependencies: - dependency-name: Akka dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Akka.Serialization.Hyperion dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Akka.TestKit.Xunit2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index 906bc89c..08b7c9ad 100644 --- a/src/common.props +++ b/src/common.props @@ -21,7 +21,7 @@ Please report any serialization problem that occurs after an upgrade to this ver net5.0 net471 netstandard2.0 - 1.4.27 + 1.4.29 6.2.0 2.4.1 2.4.3 From 4ecbb0563e13507fecebec7796d526357b0ee0ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Dec 2021 08:46:46 -0600 Subject: [PATCH 07/13] Bump AkkaVersion from 1.4.29 to 1.4.31 (#279) Bumps `AkkaVersion` from 1.4.29 to 1.4.31. Updates `Akka` from 1.4.29 to 1.4.31 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.29...1.4.31) Updates `Akka.Serialization.Hyperion` from 1.4.29 to 1.4.31 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.29...1.4.31) Updates `Akka.TestKit.Xunit2` from 1.4.29 to 1.4.31 - [Release notes](https://github.com/akkadotnet/akka.net/releases) - [Changelog](https://github.com/akkadotnet/akka.net/blob/dev/RELEASE_NOTES.md) - [Commits](https://github.com/akkadotnet/akka.net/compare/1.4.29...1.4.31) --- updated-dependencies: - dependency-name: Akka dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Akka.Serialization.Hyperion dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Akka.TestKit.Xunit2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index 08b7c9ad..35631658 100644 --- a/src/common.props +++ b/src/common.props @@ -21,7 +21,7 @@ Please report any serialization problem that occurs after an upgrade to this ver net5.0 net471 netstandard2.0 - 1.4.29 + 1.4.31 6.2.0 2.4.1 2.4.3 From 7a781559c914ad0f946ae0e8a13cec29fa42682a Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 12 Jan 2022 00:45:11 +0700 Subject: [PATCH 08/13] Fix SerializeStructBenchmark, Serializer not initialized (#282) --- src/Hyperion.Benchmarks/Program.cs | 12 ++++++++++-- src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Hyperion.Benchmarks/Program.cs b/src/Hyperion.Benchmarks/Program.cs index 41a15b23..acec8c5d 100644 --- a/src/Hyperion.Benchmarks/Program.cs +++ b/src/Hyperion.Benchmarks/Program.cs @@ -8,8 +8,16 @@ class Program { static void Main(string[] args) { - var benchmark = BenchmarkSwitcher.FromAssembly(Assembly.GetExecutingAssembly()); - benchmark.RunAll(); + var benchmark = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly); + + if (args.Length == 0) + { + benchmark.RunAll(); + } + else + { + benchmark.Run(args); + } } } } diff --git a/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs b/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs index 63fbd47d..2f625bdb 100644 --- a/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs +++ b/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs @@ -21,6 +21,7 @@ public class SerializeStructsBenchmark : HyperionBenchmark protected override void Init() { + base.Init(); standardValue = new StandardStruct(1, "John", "Doe", isLoggedIn: false); blittableValue = new BlittableStruct(59, 92); testEnum = TestEnum.HatesAll; From 122f5af36d96693c3ada3d1594ea500ff3316fb0 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 12 Jan 2022 03:48:50 +0700 Subject: [PATCH 09/13] Add type filtering feature (#281) * Add type filtering feature * fix implicit typed variable and lambda expression build error * Add documentation * Deserializer type filter should work on downcasted type * add XML-DOC * Update API Approval list --- README.md | 58 ++++++++++++++++- .../CoreApiSpec.ApproveApi.approved.txt | 35 ++++++++++- .../UnsafeDeserializationExclusionTests.cs | 63 ++++++++++++++++++- src/Hyperion/Extensions/TypeEx.cs | 13 ++-- src/Hyperion/Hyperion.csproj | 1 + src/Hyperion/ITypeFilter.cs | 15 +++++ src/Hyperion/Internal/Annotations.cs | 7 +++ src/Hyperion/SerializerOptions.cs | 42 ++++++++----- src/Hyperion/TypeFilter.cs | 34 ++++++++++ src/Hyperion/TypeFilterBuilder.cs | 36 +++++++++++ .../ValueSerializers/TypeSerializer.cs | 3 +- 11 files changed, 281 insertions(+), 26 deletions(-) create mode 100644 src/Hyperion/ITypeFilter.cs create mode 100644 src/Hyperion/TypeFilter.cs create mode 100644 src/Hyperion/TypeFilterBuilder.cs diff --git a/README.md b/README.md index 78b53d59..168de438 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,60 @@ var serializer = new Serializer(options); This is essential for frameworks like Akka.NET where we need to be able to resolve live Actor References in the deserializing system. +## Whitelisting Types On Deserialization + +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. + +using the `ITypeFilter` interface: + +```c# +public sealed class TypeFilter : ITypeFilter +{ + public ImmutableHashSet FilteredTypes { get; } + + internal TypeFilter(IEnumerable types) + { + FilteredTypes = types.Select(t => t.GetShortAssemblyQualifiedName()).ToImmutableHashSet(); + } + + public bool IsAllowed(string typeName) + => FilteredTypes.Any(t => t == typeName); +} +``` + +using the `TypeFilterBuilder` convenience builder: + +```c# +var typeFilter = TypeFilterBuilder.Create() + .Include() + .Include() + .Build(); + +var options = SerializerOptions.Default + .WithTypeFilter(typeFilter); + +var serializer = new Serializer(options); +``` + +### Convert Whitelist To Blacklist + +To do blacklisting instead of whitelisting a list of types, you will need to do a slight modification to the TypeFilter class. + +```c# +public sealed class TypeFilter : ITypeFilter +{ + public ImmutableHashSet FilteredTypes { get; } + + internal TypeFilter(IEnumerable types) + { + FilteredTypes = types.Select(t => t.GetShortAssemblyQualifiedName()).ToImmutableHashSet(); + } + + public bool IsAllowed(string typeName) + => FilteredTypes.All(t => t != typeName); +} +``` + ## Version Tolerance Hyperion has been designed to work in multiple modes in terms of version tolerance vs. performance. @@ -55,13 +109,13 @@ Hyperion has been designed to work in multiple modes in terms of version toleran 1. Pre Register Types, when using "Pre registered types", Hyperion will only emit a type ID in the output stream. This results in the best performance, but is also fragile if different clients have different versions of the contract types. 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 -in the outputstream. this results in a larger payload and some performance overhead. +in the output stream. this results in a larger payload and some performance overhead. 3. Versioned, in this mode, Hyperion will emit both type names and field information in the output stream. This allows systems to have slightly different versions of the contract types where some fields may have been added or removed. Hyperion has been designed as a wire format, point to point for soft realtime scenarios. If you need a format that is durable for persistence over time. -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. +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. ## Performance diff --git a/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt b/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt index d5fe90b4..bca91930 100644 --- a/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt +++ b/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt @@ -41,6 +41,11 @@ namespace Hyperion public void TrackDeserializedType([Hyperion.Internal.NotNull] System.Type type) { } public void TrackDeserializedTypeWithVersion([Hyperion.Internal.NotNull] System.Type type, [Hyperion.Internal.NotNull] Hyperion.TypeVersionInfo versionInfo) { } } + public sealed class DisabledTypeFilter : Hyperion.ITypeFilter + { + public static readonly Hyperion.DisabledTypeFilter Instance; + public bool IsAllowed(string typeName) { } + } public delegate object FieldInfoReader(object obj); public delegate void FieldInfoWriter(object obj, object value); public delegate void FieldReader(System.IO.Stream stream, object obj, Hyperion.DeserializerSession session); @@ -49,6 +54,10 @@ namespace Hyperion { void BuildSerializer(Hyperion.Serializer serializer, Hyperion.ValueSerializers.ObjectSerializer objectSerializer); } + public interface ITypeFilter + { + bool IsAllowed(string typeName); + } [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.All, AllowMultiple=false, Inherited=true)] public sealed class IgnoreAttribute : System.Attribute { @@ -90,11 +99,16 @@ namespace Hyperion public class SerializerOptions { public static readonly Hyperion.SerializerOptions Default; - [System.Obsolete] + [System.Obsolete("This constructor is deprecated and will be removed in the future, please use the " + + "one with the packageNameOverrides argument")] public SerializerOptions(bool versionTolerance = false, bool preserveObjectReferences = false, System.Collections.Generic.IEnumerable surrogates = null, System.Collections.Generic.IEnumerable serializerFactories = null, System.Collections.Generic.IEnumerable knownTypes = null, bool ignoreISerializable = false) { } - [System.Obsolete] + [System.Obsolete("This constructor is deprecated and will be removed in the future, please use the " + + "one with the disallowUnsafeTypes argument")] public SerializerOptions(bool versionTolerance, bool preserveObjectReferences, System.Collections.Generic.IEnumerable surrogates, System.Collections.Generic.IEnumerable serializerFactories, System.Collections.Generic.IEnumerable knownTypes, bool ignoreISerializable, System.Collections.Generic.IEnumerable> packageNameOverrides) { } + [System.Obsolete("This constructor is deprecated and will be removed in the future, please use the " + + "one with the typeFilter argument")] public SerializerOptions(bool versionTolerance, bool preserveObjectReferences, System.Collections.Generic.IEnumerable surrogates, System.Collections.Generic.IEnumerable serializerFactories, System.Collections.Generic.IEnumerable knownTypes, bool ignoreISerializable, System.Collections.Generic.IEnumerable> packageNameOverrides, bool disallowUnsafeTypes) { } + public SerializerOptions(bool versionTolerance, bool preserveObjectReferences, System.Collections.Generic.IEnumerable surrogates, System.Collections.Generic.IEnumerable serializerFactories, System.Collections.Generic.IEnumerable knownTypes, bool ignoreISerializable, System.Collections.Generic.IEnumerable> packageNameOverrides, bool disallowUnsafeTypes, Hyperion.ITypeFilter typeFilter) { } public Hyperion.SerializerOptions WithDisallowUnsafeType(bool disallowUnsafeType) { } public Hyperion.SerializerOptions WithIgnoreSerializable(bool ignoreISerializable) { } public Hyperion.SerializerOptions WithKnownTypes(System.Collections.Generic.IEnumerable knownTypes) { } @@ -102,6 +116,7 @@ namespace Hyperion public Hyperion.SerializerOptions WithPreserveObjectReferences(bool preserveObjectReferences) { } public Hyperion.SerializerOptions WithSerializerFactory(System.Collections.Generic.IEnumerable serializerFactories) { } public Hyperion.SerializerOptions WithSurrogates(System.Collections.Generic.IEnumerable surrogates) { } + public Hyperion.SerializerOptions WithTypeFilter(Hyperion.ITypeFilter typeFilter) { } public Hyperion.SerializerOptions WithVersionTolerance(bool versionTolerance) { } } public class SerializerSession @@ -130,6 +145,18 @@ namespace Hyperion { public Surrogate(System.Func toSurrogate, System.Func fromSurrogate) { } } + public sealed class TypeFilter : Hyperion.ITypeFilter + { + public System.Collections.Immutable.ImmutableHashSet FilteredTypes { get; } + public bool IsAllowed(string typeName) { } + } + public class TypeFilterBuilder + { + public Hyperion.TypeFilter Build() { } + public Hyperion.TypeFilterBuilder Include(System.Type type) { } + public Hyperion.TypeFilterBuilder Include() { } + public static Hyperion.TypeFilterBuilder Create() { } + } public class TypeVersionInfo { public TypeVersionInfo() { } @@ -591,6 +618,10 @@ namespace Hyperion.Internal public Hyperion.Internal.ImplicitUseTargetFlags TargetFlags { get; } public Hyperion.Internal.ImplicitUseKindFlags UseKindFlags { get; } } + public class UserEvilDeserializationException : Hyperion.Internal.EvilDeserializationException + { + public UserEvilDeserializationException(string message, string typeString) { } + } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter | System.AttributeTargets.All)] public sealed class ValueProviderAttribute : System.Attribute { diff --git a/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs b/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs index 0444f377..e69a58bd 100644 --- a/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs +++ b/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs @@ -1,7 +1,9 @@ -using System.IO; +using System; +using System.IO; using Hyperion.Extensions; using Hyperion.Internal; using Xunit; +using FluentAssertions; namespace Hyperion.Tests { @@ -22,5 +24,64 @@ public void CantDeserializeANaughtyType() serializer.Deserialize(stream)); } } + + internal class ClassA + { } + + internal class ClassB + { } + + internal class ClassC + { } + + [Fact] + public void TypeFilterShouldThrowOnNaughtyType() + { + var typeFilter = TypeFilterBuilder.Create() + .Include() + .Include() + .Build(); + + var options = SerializerOptions.Default + .WithTypeFilter(typeFilter); + + var serializer = new Serializer(options); + + using (var stream = new MemoryStream()) + { + serializer.Serialize(new ClassA(), stream); + stream.Position = 0; + Action act = () => serializer.Deserialize(stream); + act.Should().NotThrow(); + + stream.Position = 0; + Action actObj = () => serializer.Deserialize(stream); + actObj.Should().NotThrow(); + } + + using (var stream = new MemoryStream()) + { + serializer.Serialize(new ClassB(), stream); + stream.Position = 0; + Action act = () => serializer.Deserialize(stream); + act.Should().NotThrow(); + + stream.Position = 0; + Action actObj = () => serializer.Deserialize(stream); + actObj.Should().NotThrow(); + } + + using (var stream = new MemoryStream()) + { + serializer.Serialize(new ClassC(), stream); + stream.Position = 0; + Action act = () => serializer.Deserialize(stream); + act.Should().Throw(); + + stream.Position = 0; + Action actObj = () => serializer.Deserialize(stream); + actObj.Should().Throw(); + } + } } } \ No newline at end of file diff --git a/src/Hyperion/Extensions/TypeEx.cs b/src/Hyperion/Extensions/TypeEx.cs index 59a7105e..f323b133 100644 --- a/src/Hyperion/Extensions/TypeEx.cs +++ b/src/Hyperion/Extensions/TypeEx.cs @@ -162,7 +162,8 @@ private static Type GetTypeFromManifestName(Stream stream, DeserializerSession s break; } - return LoadTypeByName(shortName, session.Serializer.Options.DisallowUnsafeTypes); + var options = session.Serializer.Options; + return LoadTypeByName(shortName, options.DisallowUnsafeTypes, options.TypeFilter); }); } @@ -192,12 +193,14 @@ private static bool UnsafeInheritanceCheck(Type type) return false; } - public static Type LoadTypeByName(string name, bool disallowUnsafeTypes) + public static Type LoadTypeByName(string name, bool disallowUnsafeTypes, ITypeFilter typeFilter) { - if (disallowUnsafeTypes && UnsafeTypesDenySet.Any(name.Contains)) + if (disallowUnsafeTypes) { - throw new EvilDeserializationException( - "Unsafe Type Deserialization Detected!", name); + if(UnsafeTypesDenySet.Any(name.Contains)) + throw new EvilDeserializationException("Unsafe Type Deserialization Detected!", name); + if(!typeFilter.IsAllowed(name)) + throw new UserEvilDeserializationException("Unsafe Type Deserialization Detected!", name); } try { diff --git a/src/Hyperion/Hyperion.csproj b/src/Hyperion/Hyperion.csproj index 803258d0..214e2837 100644 --- a/src/Hyperion/Hyperion.csproj +++ b/src/Hyperion/Hyperion.csproj @@ -33,6 +33,7 @@ + diff --git a/src/Hyperion/ITypeFilter.cs b/src/Hyperion/ITypeFilter.cs new file mode 100644 index 00000000..038a3e1a --- /dev/null +++ b/src/Hyperion/ITypeFilter.cs @@ -0,0 +1,15 @@ +namespace Hyperion +{ + /// + /// Provide a callback to allow a user defined Type filter during certain operations + /// + public interface ITypeFilter + { + /// + /// Determines if a fully qualified class name is allowed to be processed or not + /// + /// The fully qualified class name of the type to be filtered + /// true if a type is allowed + bool IsAllowed(string typeName); + } +} \ No newline at end of file diff --git a/src/Hyperion/Internal/Annotations.cs b/src/Hyperion/Internal/Annotations.cs index b1c5c0f6..dd4a8525 100644 --- a/src/Hyperion/Internal/Annotations.cs +++ b/src/Hyperion/Internal/Annotations.cs @@ -30,6 +30,13 @@ public EvilDeserializationException(string message, public string BadTypeString { get; } } + + public class UserEvilDeserializationException : EvilDeserializationException + { + public UserEvilDeserializationException(string message, string typeString) : base(message, typeString) + { } + } + /// /// Indicates that the value of the marked element could be null sometimes, /// so the check for null is necessary before its usage. diff --git a/src/Hyperion/SerializerOptions.cs b/src/Hyperion/SerializerOptions.cs index 6b655097..0c8694dd 100644 --- a/src/Hyperion/SerializerOptions.cs +++ b/src/Hyperion/SerializerOptions.cs @@ -16,15 +16,7 @@ namespace Hyperion { public class SerializerOptions { - public static readonly SerializerOptions Default = new SerializerOptions( - versionTolerance: false, - preserveObjectReferences: false, - surrogates: null, - serializerFactories: null, - knownTypes: null, - ignoreISerializable: false, - packageNameOverrides: null, - disallowUnsafeTypes: true); + public static readonly SerializerOptions Default = new SerializerOptions(); internal static List> DefaultPackageNameOverrides() { @@ -81,8 +73,9 @@ internal static List> DefaultPackageNameOverrides() internal readonly Dictionary KnownTypesDict = new Dictionary(); internal readonly List> CrossFrameworkPackageNameOverrides = DefaultPackageNameOverrides(); internal readonly bool DisallowUnsafeTypes; - - [Obsolete] + internal readonly ITypeFilter TypeFilter; + + [Obsolete(message:"This constructor is deprecated and will be removed in the future, please use the one with the packageNameOverrides argument")] public SerializerOptions( bool versionTolerance = false, bool preserveObjectReferences = false, @@ -93,7 +86,7 @@ public SerializerOptions( : this(versionTolerance, preserveObjectReferences, surrogates, serializerFactories, knownTypes, ignoreISerializable, null) { } - [Obsolete] + [Obsolete(message:"This constructor is deprecated and will be removed in the future, please use the one with the disallowUnsafeTypes argument")] public SerializerOptions( bool versionTolerance, bool preserveObjectReferences, @@ -102,9 +95,10 @@ public SerializerOptions( IEnumerable knownTypes, bool ignoreISerializable, IEnumerable> packageNameOverrides) - : this(versionTolerance, preserveObjectReferences, surrogates, serializerFactories, knownTypes, ignoreISerializable, null, true) + : this(versionTolerance, preserveObjectReferences, surrogates, serializerFactories, knownTypes, ignoreISerializable, packageNameOverrides, true) { } + [Obsolete(message:"This constructor is deprecated and will be removed in the future, please use the one with the typeFilter argument")] public SerializerOptions( bool versionTolerance, bool preserveObjectReferences, @@ -114,6 +108,19 @@ public SerializerOptions( bool ignoreISerializable, IEnumerable> packageNameOverrides, bool disallowUnsafeTypes) + : this(versionTolerance, preserveObjectReferences, surrogates, serializerFactories, knownTypes, ignoreISerializable, packageNameOverrides, disallowUnsafeTypes, DisabledTypeFilter.Instance) + { } + + public SerializerOptions( + bool versionTolerance, + bool preserveObjectReferences, + IEnumerable surrogates, + IEnumerable serializerFactories, + IEnumerable knownTypes, + bool ignoreISerializable, + IEnumerable> packageNameOverrides, + bool disallowUnsafeTypes, + ITypeFilter typeFilter) { VersionTolerance = versionTolerance; Surrogates = surrogates?.ToArray() ?? EmptySurrogates; @@ -136,6 +143,7 @@ public SerializerOptions( CrossFrameworkPackageNameOverrides.AddRange(packageNameOverrides); DisallowUnsafeTypes = disallowUnsafeTypes; + TypeFilter = typeFilter ?? DisabledTypeFilter.Instance; } public SerializerOptions WithVersionTolerance(bool versionTolerance) @@ -154,6 +162,8 @@ public SerializerOptions WithPackageNameOverrides(IEnumerable Copy(packageNameOverrides: packageNameOverrides); public SerializerOptions WithDisallowUnsafeType(bool disallowUnsafeType) => Copy(disallowUnsafeType: disallowUnsafeType); + public SerializerOptions WithTypeFilter(ITypeFilter typeFilter) + => Copy(typeFilter: typeFilter); private SerializerOptions Copy( bool? versionTolerance = null, @@ -163,7 +173,8 @@ private SerializerOptions Copy( IEnumerable knownTypes = null, bool? ignoreISerializable = null, IEnumerable> packageNameOverrides = null, - bool? disallowUnsafeType = null) + bool? disallowUnsafeType = null, + ITypeFilter typeFilter = null) => new SerializerOptions( versionTolerance ?? VersionTolerance, preserveObjectReferences ?? PreserveObjectReferences, @@ -172,7 +183,8 @@ private SerializerOptions Copy( knownTypes ?? KnownTypes, ignoreISerializable ?? IgnoreISerializable, packageNameOverrides ?? CrossFrameworkPackageNameOverrides, - disallowUnsafeType ?? DisallowUnsafeTypes + disallowUnsafeType ?? DisallowUnsafeTypes, + typeFilter ?? TypeFilter ); } } \ No newline at end of file diff --git a/src/Hyperion/TypeFilter.cs b/src/Hyperion/TypeFilter.cs new file mode 100644 index 00000000..d337bf04 --- /dev/null +++ b/src/Hyperion/TypeFilter.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Hyperion.Extensions; + +namespace Hyperion +{ + /// + public sealed class TypeFilter : ITypeFilter + { + public ImmutableHashSet FilteredTypes { get; } + + internal TypeFilter(IEnumerable types) + { + FilteredTypes = types.Select(t => t.GetShortAssemblyQualifiedName()).ToImmutableHashSet(); + } + + public bool IsAllowed(string typeName) + => FilteredTypes.Any(t => t == typeName); + } + + /// + /// A disabled type filter that always returns true + /// + public sealed class DisabledTypeFilter : ITypeFilter + { + public static readonly DisabledTypeFilter Instance = new DisabledTypeFilter(); + + private DisabledTypeFilter() { } + + public bool IsAllowed(string typeName) => true; + } +} \ No newline at end of file diff --git a/src/Hyperion/TypeFilterBuilder.cs b/src/Hyperion/TypeFilterBuilder.cs new file mode 100644 index 00000000..569625d4 --- /dev/null +++ b/src/Hyperion/TypeFilterBuilder.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Hyperion +{ + /// + /// Helper class to programatically create a using fluent builder pattern. + /// + public class TypeFilterBuilder + { + /// + /// Create a new instance of + /// + /// a new instance of + public static TypeFilterBuilder Create() => new TypeFilterBuilder(); + + private readonly List _types = new List(); + + private TypeFilterBuilder() + { } + + public TypeFilterBuilder Include() + { + return Include(typeof(T)); + } + + public TypeFilterBuilder Include(Type type) + { + _types.Add(type); + return this; + } + + public TypeFilter Build() + => new TypeFilter(_types); + } +} \ No newline at end of file diff --git a/src/Hyperion/ValueSerializers/TypeSerializer.cs b/src/Hyperion/ValueSerializers/TypeSerializer.cs index 633d2cc7..cbf24857 100644 --- a/src/Hyperion/ValueSerializers/TypeSerializer.cs +++ b/src/Hyperion/ValueSerializers/TypeSerializer.cs @@ -69,8 +69,9 @@ public override object ReadValue(Stream stream, DeserializerSession session) if (shortname == null) return null; + var options = session.Serializer.Options; var type = TypeNameLookup.GetOrAdd(shortname, - name => TypeEx.LoadTypeByName(shortname, session.Serializer.Options.DisallowUnsafeTypes)); + name => TypeEx.LoadTypeByName(shortname, options.DisallowUnsafeTypes, options.TypeFilter)); //add the deserialized type to lookup if (session.Serializer.Options.PreserveObjectReferences) From 0feacb19163d7abf5b154774a696b22b0d82a5c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jan 2022 20:55:19 +0000 Subject: [PATCH 10/13] Bump FluentAssertions from 6.2.0 to 6.3.0 (#280) Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.2.0 to 6.3.0. - [Release notes](https://github.com/fluentassertions/fluentassertions/releases) - [Changelog](https://github.com/fluentassertions/fluentassertions/blob/master/AcceptApiChanges.ps1) - [Commits](https://github.com/fluentassertions/fluentassertions/compare/6.2.0...6.3.0) --- updated-dependencies: - dependency-name: FluentAssertions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index 35631658..1896206b 100644 --- a/src/common.props +++ b/src/common.props @@ -22,7 +22,7 @@ Please report any serialization problem that occurs after an upgrade to this ver net471 netstandard2.0 1.4.31 - 6.2.0 + 6.3.0 2.4.1 2.4.3 17.0.0 From 3c7a701312936d3bd24ea3a10b4efe360ddd5394 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 12 Jan 2022 20:29:30 +0700 Subject: [PATCH 11/13] Update RELEASE_NOTES.md for 0.11.3 release (#285) * Update RELEASE_NOTES.md for 0.11.3 release * Chage wording and version number to 0.12.0 --- RELEASE_NOTES.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f7414388..986e18d2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,21 @@ +### 0.12.0 January 12 2022 #### + +* Allow explicit control over which types can be deserialized [#281](https://github.com/akkadotnet/Hyperion/pull/281) + +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: + +```c# +var typeFilter = TypeFilterBuilder.Create() + .Include() + .Include() + .Build(); +var options = SerializerOptions.Default + .WithTypeFilter(typeFilter); +var serializer = new Serializer(options); +``` + +For complete documentation, please read the [readme on filtering types for secure deserialization.](https://github.com/akkadotnet/Hyperion#whitelisting-types-on-deserialization) + ### 0.11.2 October 7 2021 #### * Fix exception thrown during deserialization when preserve object reference was turned on and a surrogate instance was inserted into a collection multiple times. [#264](https://github.com/akkadotnet/Hyperion/pull/264) From d8c7ea14fff9e6a776248bc61e51a0ef3b138f90 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 12 Jan 2022 20:36:12 +0700 Subject: [PATCH 12/13] Improve EvilDeserializationException message (#284) --- src/Hyperion/Internal/Annotations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hyperion/Internal/Annotations.cs b/src/Hyperion/Internal/Annotations.cs index dd4a8525..9021e014 100644 --- a/src/Hyperion/Internal/Annotations.cs +++ b/src/Hyperion/Internal/Annotations.cs @@ -23,7 +23,7 @@ namespace Hyperion.Internal public class EvilDeserializationException : SecurityException { public EvilDeserializationException(string message, - string typeString) : base(message) + string typeString) : base($"{message} [type: {typeString}]") { BadTypeString = typeString; } From 87ad62d9fb024257e1935f1ee509bae2d4e7de48 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 12 Jan 2022 22:15:38 +0700 Subject: [PATCH 13/13] Add TypeFilter enabled benchmark (#283) --- .../SerializeClassesBenchmark.cs | 33 ++++++++++++++++++- .../SerializeStructsBenchmark.cs | 26 +++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/Hyperion.Benchmarks/SerializeClassesBenchmark.cs b/src/Hyperion.Benchmarks/SerializeClassesBenchmark.cs index 58357588..35203e22 100644 --- a/src/Hyperion.Benchmarks/SerializeClassesBenchmark.cs +++ b/src/Hyperion.Benchmarks/SerializeClassesBenchmark.cs @@ -8,6 +8,7 @@ #endregion using System; +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; namespace Hyperion.Benchmarks @@ -20,9 +21,25 @@ public class SerializeClassesBenchmark : HyperionBenchmark private LargeSealedClass sealedObject; private GenericClass genericObject; + private Serializer _filteredSerializer; + protected override void Init() { - Serializer = new Serializer(new SerializerOptions(preserveObjectReferences:true)); + var baseOptions = new SerializerOptions(preserveObjectReferences: true); + Serializer = new Serializer(baseOptions); + + var filteredOptions = baseOptions + .WithTypeFilter( + TypeFilterBuilder.Create() + .Include() + .Include() + .Include() + .Include() + .Include>() + .Include() + .Build()); + _filteredSerializer = new Serializer(filteredOptions); + var a = new CyclicClassA(); var b = new CyclicClassB(); a.B = b; @@ -45,9 +62,23 @@ protected override void Init() #endregion [Benchmark] public void Cyclic_References() => SerializeAndDeserialize(cyclic); + [Benchmark] public void Filtered_Cyclic_References() => SerializeAndFilteredDeserialize(cyclic); [Benchmark] public void Virtual_Classes() => SerializeAndDeserialize(virtualObject); + [Benchmark] public void Filtered_Virtual_Classes() => SerializeAndFilteredDeserialize(virtualObject); [Benchmark] public void Large_Sealed_Classes() => SerializeAndDeserialize(sealedObject); + [Benchmark] public void Filtered_Large_Sealed_Classes() => SerializeAndFilteredDeserialize(sealedObject); [Benchmark] public void Generic_Classes() => SerializeAndDeserialize(genericObject); + [Benchmark] public void Filtered_Generic_Classes() => SerializeAndFilteredDeserialize(genericObject); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SerializeAndFilteredDeserialize(T elem) + { + Serializer.Serialize(elem, Stream); + Stream.Position = 0; + + _filteredSerializer.Deserialize(Stream); + Stream.Position = 0; + } } #region test data types diff --git a/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs b/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs index 2f625bdb..a8ea197b 100644 --- a/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs +++ b/src/Hyperion.Benchmarks/SerializeStructsBenchmark.cs @@ -7,6 +7,7 @@ // ----------------------------------------------------------------------- #endregion +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; @@ -15,6 +16,8 @@ namespace Hyperion.Benchmarks public class SerializeStructsBenchmark : HyperionBenchmark { #region init + private Serializer _filteredSerializer; + private StandardStruct standardValue; private BlittableStruct blittableValue; private TestEnum testEnum; @@ -22,6 +25,16 @@ public class SerializeStructsBenchmark : HyperionBenchmark protected override void Init() { base.Init(); + + var filteredOptions = SerializerOptions.Default + .WithTypeFilter( + TypeFilterBuilder.Create() + .Include() + .Include() + .Include() + .Build()); + _filteredSerializer = new Serializer(filteredOptions); + standardValue = new StandardStruct(1, "John", "Doe", isLoggedIn: false); blittableValue = new BlittableStruct(59, 92); testEnum = TestEnum.HatesAll; @@ -30,8 +43,21 @@ protected override void Init() #endregion [Benchmark] public void Enums() => SerializeAndDeserialize(testEnum); + [Benchmark] public void Filtered_Enums() => SerializeAndFilteredDeserialize(testEnum); [Benchmark] public void Standard_Value_Types() => SerializeAndDeserialize(standardValue); + [Benchmark] public void Filtered_Standard_Value_Types() => SerializeAndFilteredDeserialize(standardValue); [Benchmark] public void Blittable_Value_Types() => SerializeAndDeserialize(blittableValue); + [Benchmark] public void Filtered_Blittable_Value_Types() => SerializeAndFilteredDeserialize(blittableValue); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SerializeAndFilteredDeserialize(T elem) + { + Serializer.Serialize(elem, Stream); + Stream.Position = 0; + + _filteredSerializer.Deserialize(Stream); + Stream.Position = 0; + } } #region test data types