diff --git a/build-system/pr-validation.yaml b/build-system/pr-validation.yaml index 6a62781c..71133c5e 100644 --- a/build-system/pr-validation.yaml +++ b/build-system/pr-validation.yaml @@ -21,10 +21,19 @@ jobs: vmImage: 'windows-2019' scriptFileName: build.cmd scriptArgs: all + - template: azure-pipeline.template.yaml parameters: - name: 'linux_pr' - displayName: 'Linux PR Validation' + name: 'linux_pr_net_core' + displayName: 'Linux PR Validation (netcoreapp3.1)' vmImage: 'ubuntu-16.04' scriptFileName: ./build.sh - scriptArgs: all \ No newline at end of file + scriptArgs: runTestsNetCore + + - template: azure-pipeline.template.yaml + parameters: + name: 'linux_pr_net_5' + displayName: 'Linux PR Validation (net5.0)' + vmImage: 'ubuntu-16.04' + scriptFileName: ./build.sh + scriptArgs: runTestsNet diff --git a/build-system/windows-pr-validation.yaml b/build-system/windows-pr-validation.yaml index 5f14abd4..18a7e762 100644 --- a/build-system/windows-pr-validation.yaml +++ b/build-system/windows-pr-validation.yaml @@ -20,10 +20,19 @@ jobs: vmImage: 'windows-2019' scriptFileName: build.cmd scriptArgs: all + - template: azure-pipeline.template.yaml parameters: - name: 'linux_pr' - displayName: 'Linux PR Validation' + name: 'linux_pr_net_core' + displayName: 'Linux PR Validation (netcoreapp3.1)' vmImage: 'ubuntu-18.04' scriptFileName: ./build.sh - scriptArgs: all + scriptArgs: runTestsNetCore + +- template: azure-pipeline.template.yaml + parameters: + name: 'linux_pr_net_5' + displayName: 'Linux PR Validation (net5.0)' + vmImage: 'ubuntu-18.04' + scriptFileName: ./build.sh + scriptArgs: runTestsNet diff --git a/build.fsx b/build.fsx index 2b65f763..b1919251 100644 --- a/build.fsx +++ b/build.fsx @@ -71,6 +71,8 @@ Target "Clean" (fun _ -> CleanDirs !! "./**/bin" CleanDirs !! "./**/obj" + + CreateDir "bin/nuget" ) Target "AssemblyInfo" (fun _ -> diff --git a/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs b/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs index aaf7ece9..f90e0247 100644 --- a/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs +++ b/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs @@ -1,5 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; using Akka.Actor; using Akka.Configuration; using Akka.Serialization; @@ -7,6 +14,7 @@ using Akka.TestKit; using Akka.TestKit.Xunit2; using FluentAssertions; +using Hyperion.Internal; using Xunit.Abstractions; using AkkaSerializer = Akka.Serialization.Serializer; @@ -72,6 +80,71 @@ public void Bugfix263_Akka_HyperionSerializer_should_serialize_ActorPath_list() deserialized.Destinations[0].Should().Be(deserialized.Destinations[1]); } + [Fact] + public async Task CanDeserializeANaughtyTypeWhenAllowed() + { + var config = ConfigurationFactory.ParseString(@" +akka { + serialize-messages = on + actor { + serializers { + hyperion = ""Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"" + } + serialization-bindings { + ""System.Object"" = hyperion + } + serialization-settings.hyperion.disallow-unsafe-type = false + } +}"); + var system = ActorSystem.Create("unsafeSystem", config); + + try + { + var serializer = system.Serialization.FindSerializerForType(typeof(DirectoryInfo)); + var di = new DirectoryInfo(@"c:\"); + + var serialized = serializer.ToBinary(di); + var deserialized = serializer.FromBinary(serialized); + } + finally + { + await system.Terminate(); + } + } + + [Fact] + public async Task CantDeserializeANaughtyTypeByDefault() + { + var config = ConfigurationFactory.ParseString(@" +akka { + serialize-messages = on + actor { + serializers { + hyperion = ""Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"" + } + serialization-bindings { + ""System.Object"" = hyperion + } + serialization-settings.hyperion.disallow-unsafe-type = true # this is the default value + } +}"); + var system = ActorSystem.Create("unsafeSystem", config); + + try + { + var serializer = system.Serialization.FindSerializerForType(typeof(DirectoryInfo)); + var di = new DirectoryInfo(@"c:\"); + + var serialized = serializer.ToBinary(di); + var ex = Assert.Throws(() => serializer.FromBinary(serialized)); + ex.InnerException.Should().BeOfType(); + } + finally + { + await system.Terminate(); + } + } + private class MyActor: ReceiveActor { diff --git a/src/Hyperion.Tests/Hyperion.Tests.csproj b/src/Hyperion.Tests/Hyperion.Tests.csproj index 6983f5f3..1e934f67 100644 --- a/src/Hyperion.Tests/Hyperion.Tests.csproj +++ b/src/Hyperion.Tests/Hyperion.Tests.csproj @@ -18,17 +18,54 @@ $(DefineConstants);NETFX - - - true - + + ./lib + true + + + ./lib + true + + + ./lib + true + + + ./lib + true + + + ./lib + true + + + ./lib + true + + + ./lib + true + + + ./lib + true + + + ./lib + true + + + + + + diff --git a/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs b/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs index e69a58bd..5da32de6 100644 --- a/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs +++ b/src/Hyperion.Tests/UnsafeDeserializationExclusionTests.cs @@ -1,18 +1,27 @@ using System; +using System.Collections.Generic; using System.IO; -using Hyperion.Extensions; +using System.Runtime.InteropServices; using Hyperion.Internal; using Xunit; using FluentAssertions; +using Hyperion.Extensions; +using Xunit.Abstractions; namespace Hyperion.Tests { public class UnsafeDeserializationExclusionTests { + private readonly ITestOutputHelper _output; + + public UnsafeDeserializationExclusionTests(ITestOutputHelper output) + { + _output = output; + } + [Fact] public void CantDeserializeANaughtyType() { - //System.Diagnostics.Process p = new Process(); var serializer = new Hyperion.Serializer(); var di =new System.IO.DirectoryInfo(@"c:\"); @@ -25,6 +34,73 @@ public void CantDeserializeANaughtyType() } } + [Theory] + [MemberData(nameof(DangerousObjectFactory))] + public void DetectNaughtyTypesByDefault(Type dangerousType) + { + _output.WriteLine($"Testing for dangerous type [{dangerousType.AssemblyQualifiedName}]"); + TypeEx.IsDisallowedType(dangerousType).Should().BeTrue(); + } +/* + X "System.Security.Principal.WindowsIdentity", + X "System.Security.Principal.WindowsPrincipal", + X "System.Security.Claims.ClaimsIdentity", + X "System.Web.Security.RolePrincipal", + X "System.Windows.Forms.AxHost.State", + X "System.Windows.Data.ObjectDataProvider", + X "System.Management.Automation.PSObject", + X "System.IO.FileSystemInfo", + X "System.IO.FileInfo", + X "System.IdentityModel.Tokens.SessionSecurityToken", + X "SessionViewStateHistoryItem", + X "TextFormattingRunProperties", + X "ToolboxItemContainer", + X "System.CodeDom.Compiler.TempFileCollection", + X "System.Activities.Presentation.WorkflowDesigner", + X "System.Windows.ResourceDictionary", + X "System.Windows.Forms.BindingSource", + X "System.Diagnostics.Process", + "System.Management.IWbemClassObjectFreeThreaded" // Need to have sharepoint installed, simulated + "Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider", // Need to have ?Exchange? installed, simulated + ??? "System.Security.Principal.WindowsClaimsIdentity", // This FQCN seemed to not exist in the past + */ + public static IEnumerable DangerousObjectFactory() + { + var isWindow = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + yield return new object[]{ typeof(System.IO.FileInfo) }; + yield return new object[]{ typeof(System.IO.FileSystemInfo) }; + yield return new object[]{ typeof(System.Security.Claims.ClaimsIdentity)}; + yield return new object[]{ typeof(System.Diagnostics.Process)}; + yield return new object[]{ typeof(System.CodeDom.Compiler.TempFileCollection)}; + yield return new object[]{ typeof(System.Management.IWbemClassObjectFreeThreaded)}; // SIMULATED + yield return new object[]{ typeof(Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider)}; // SIMULATED +#if !NETFX + yield return new object[]{ typeof(System.Management.Automation.PSObject)}; +#endif + + if (isWindow) + { + yield return new object[]{ typeof(System.Security.Principal.WindowsIdentity) }; + yield return new object[]{ typeof(System.Security.Principal.WindowsPrincipal)}; +#if NETFX + var ass = typeof(System.Web.Mobile.MobileCapabilities).Assembly; + var type = ass.GetType("System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem"); + yield return new object[]{ type }; + + yield return new object[]{ typeof(System.Drawing.Design.ToolboxItemContainer)}; + yield return new object[]{ typeof(System.Activities.Presentation.WorkflowDesigner)}; + yield return new object[]{ typeof(Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties)}; + yield return new object[]{ typeof(System.IdentityModel.Tokens.SessionSecurityToken)}; + yield return new object[]{ typeof(System.Web.Security.RolePrincipal) }; + yield return new object[]{ typeof(System.Windows.Forms.AxHost.State)}; + yield return new object[]{ typeof(System.Windows.Data.ObjectDataProvider)}; + yield return new object[]{ typeof(System.Windows.ResourceDictionary)}; + yield return new object[]{ typeof(System.Windows.Forms.BindingSource)}; +#endif + } + } + internal class ClassA { } @@ -84,4 +160,14 @@ public void TypeFilterShouldThrowOnNaughtyType() } } } +} + +namespace System.Management +{ + public interface IWbemClassObjectFreeThreaded{ } +} + +namespace Microsoft.Exchange.Management.SystemManager.WinForms +{ + public class ExchangeSettingsProvider { } } \ No newline at end of file diff --git a/src/Hyperion.Tests/lib/Microsoft.VisualStudio.Text.UI.Wpf.dll b/src/Hyperion.Tests/lib/Microsoft.VisualStudio.Text.UI.Wpf.dll new file mode 100644 index 00000000..15e7b226 Binary files /dev/null and b/src/Hyperion.Tests/lib/Microsoft.VisualStudio.Text.UI.Wpf.dll differ diff --git a/src/Hyperion.Tests/lib/PresentationFramework.dll b/src/Hyperion.Tests/lib/PresentationFramework.dll new file mode 100644 index 00000000..12a583e1 Binary files /dev/null and b/src/Hyperion.Tests/lib/PresentationFramework.dll differ diff --git a/src/Hyperion.Tests/lib/System.Activities.Presentation.dll b/src/Hyperion.Tests/lib/System.Activities.Presentation.dll new file mode 100644 index 00000000..7b3b6d47 Binary files /dev/null and b/src/Hyperion.Tests/lib/System.Activities.Presentation.dll differ diff --git a/src/Hyperion.Tests/lib/System.Drawing.Design.dll b/src/Hyperion.Tests/lib/System.Drawing.Design.dll new file mode 100644 index 00000000..b75f1776 Binary files /dev/null and b/src/Hyperion.Tests/lib/System.Drawing.Design.dll differ diff --git a/src/Hyperion.Tests/lib/System.Drawing.dll b/src/Hyperion.Tests/lib/System.Drawing.dll new file mode 100644 index 00000000..af29772c Binary files /dev/null and b/src/Hyperion.Tests/lib/System.Drawing.dll differ diff --git a/src/Hyperion.Tests/lib/System.IdentityModel.dll b/src/Hyperion.Tests/lib/System.IdentityModel.dll new file mode 100644 index 00000000..1d4f60ff Binary files /dev/null and b/src/Hyperion.Tests/lib/System.IdentityModel.dll differ diff --git a/src/Hyperion.Tests/lib/System.Web.Mobile.dll b/src/Hyperion.Tests/lib/System.Web.Mobile.dll new file mode 100644 index 00000000..6091823e Binary files /dev/null and b/src/Hyperion.Tests/lib/System.Web.Mobile.dll differ diff --git a/src/Hyperion.Tests/lib/System.Web.dll b/src/Hyperion.Tests/lib/System.Web.dll new file mode 100644 index 00000000..bb6d2e03 Binary files /dev/null and b/src/Hyperion.Tests/lib/System.Web.dll differ diff --git a/src/Hyperion.Tests/lib/System.Windows.Forms.dll b/src/Hyperion.Tests/lib/System.Windows.Forms.dll new file mode 100644 index 00000000..8df519cc Binary files /dev/null and b/src/Hyperion.Tests/lib/System.Windows.Forms.dll differ diff --git a/src/Hyperion/Extensions/TypeEx.cs b/src/Hyperion/Extensions/TypeEx.cs index f323b133..e9c42949 100644 --- a/src/Hyperion/Extensions/TypeEx.cs +++ b/src/Hyperion/Extensions/TypeEx.cs @@ -14,6 +14,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Hyperion.Internal; @@ -51,11 +52,12 @@ internal static class TypeEx { "System.Security.Claims.ClaimsIdentity", "System.Windows.Forms.AxHost.State", + "System.Windows.Forms.AxHost+State", "System.Windows.Data.ObjectDataProvider", "System.Management.Automation.PSObject", "System.Web.Security.RolePrincipal", "System.IdentityModel.Tokens.SessionSecurityToken", - "SessionViewStateHistoryItem", + "System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem", "TextFormattingRunProperties", "ToolboxItemContainer", "System.Security.Principal.WindowsClaimsIdentity", @@ -63,12 +65,14 @@ internal static class TypeEx "System.Security.Principal.WindowsPrincipal", "System.CodeDom.Compiler.TempFileCollection", "System.IO.FileSystemInfo", + "System.IO.FileInfo", "System.Activities.Presentation.WorkflowDesigner", "System.Windows.ResourceDictionary", "System.Windows.Forms.BindingSource", "Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider", "System.Diagnostics.Process", - "System.Management.IWbemClassObjectFreeThreaded" + "System.Management.IWbemClassObjectFreeThreaded", + "System.Configuration.Install.AssemblyInstaller" }); public static bool IsHyperionPrimitive(this Type type) @@ -167,6 +171,7 @@ private static Type GetTypeFromManifestName(Stream stream, DeserializerSession s }); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool UnsafeInheritanceCheck(Type type) { #if NETSTANDARD1_6 @@ -181,7 +186,7 @@ private static bool UnsafeInheritanceCheck(Type type) while (currentBase != null) { - if (UnsafeTypesDenySet.Any(r => currentBase.FullName?.Contains(r) ?? false)) + if (IsDisallowedType(currentBase)) return true; #if NETSTANDARD1_6 currentBase = currentBase.DeclaringType; @@ -192,12 +197,25 @@ private static bool UnsafeInheritanceCheck(Type type) return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDisallowedType() + => IsDisallowedType(typeof(TType)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDisallowedType(Type type) + => IsDisallowedType(type.FullName); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDisallowedType(string name) + => UnsafeTypesDenySet.Any(name.Contains); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Type LoadTypeByName(string name, bool disallowUnsafeTypes, ITypeFilter typeFilter) { if (disallowUnsafeTypes) { - if(UnsafeTypesDenySet.Any(name.Contains)) + if(IsDisallowedType(name)) throw new EvilDeserializationException("Unsafe Type Deserialization Detected!", name); if(!typeFilter.IsAllowed(name)) throw new UserEvilDeserializationException("Unsafe Type Deserialization Detected!", name); @@ -208,7 +226,7 @@ public static Type LoadTypeByName(string name, bool disallowUnsafeTypes, ITypeFi // i.e. if there are different version available in GAC and locally var typename = ToQualifiedAssemblyName(name, ignoreAssemblyVersion: false); var type = Type.GetType(typename, true); - if (UnsafeInheritanceCheck(type)) + if (disallowUnsafeTypes && UnsafeInheritanceCheck(type)) throw new EvilDeserializationException( "Unsafe Type Deserialization Detected!", name); return type; @@ -216,8 +234,8 @@ public static Type LoadTypeByName(string name, bool disallowUnsafeTypes, ITypeFi catch (IOException) { var typename = ToQualifiedAssemblyName(name, ignoreAssemblyVersion: true); - var type = Type.GetType(typename, true); - if (UnsafeInheritanceCheck(type)) + var type = Type.GetType(typename, true); + if (disallowUnsafeTypes && UnsafeInheritanceCheck(type)) throw new EvilDeserializationException( "Unsafe Type Deserialization Detected!", name); return type;