From 288224cc73754b1f83f406696a7f92ecd269b4f2 Mon Sep 17 00:00:00 2001 From: yallie Date: Wed, 25 Apr 2018 15:03:12 +0300 Subject: [PATCH] Added DelegateValidator and unit tests for it. --- .gitignore | 6 ++ .../DelegateValidatorTests.cs | 58 ++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++++++++ .../SafeDeserializationHelpers.Tests.csproj | 58 ++++++++++++++++ SafeDeserializationHelpers.sln | 31 +++++++++ .../DelegateValidator.cs | 66 +++++++++++++++++++ .../GlobalSuppressions.cs | 9 +++ .../Properties/AssemblyInfo.cs | 36 ++++++++++ .../SafeDeserializationHelpers.csproj | 55 ++++++++++++++++ .../UnsafeDeserializationException.cs | 39 +++++++++++ SafeDeserializationHelpers/packages.config | 4 ++ 11 files changed, 398 insertions(+) create mode 100644 .gitignore create mode 100644 SafeDeserializationHelpers.Tests/DelegateValidatorTests.cs create mode 100644 SafeDeserializationHelpers.Tests/Properties/AssemblyInfo.cs create mode 100644 SafeDeserializationHelpers.Tests/SafeDeserializationHelpers.Tests.csproj create mode 100644 SafeDeserializationHelpers.sln create mode 100644 SafeDeserializationHelpers/DelegateValidator.cs create mode 100644 SafeDeserializationHelpers/GlobalSuppressions.cs create mode 100644 SafeDeserializationHelpers/Properties/AssemblyInfo.cs create mode 100644 SafeDeserializationHelpers/SafeDeserializationHelpers.csproj create mode 100644 SafeDeserializationHelpers/UnsafeDeserializationException.cs create mode 100644 SafeDeserializationHelpers/packages.config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2460b7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin +obj +packages +.vs +*.suo +*.user diff --git a/SafeDeserializationHelpers.Tests/DelegateValidatorTests.cs b/SafeDeserializationHelpers.Tests/DelegateValidatorTests.cs new file mode 100644 index 0000000..8265042 --- /dev/null +++ b/SafeDeserializationHelpers.Tests/DelegateValidatorTests.cs @@ -0,0 +1,58 @@ +namespace SafeDeserializationHelpers.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Text; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DelegateValidatorTests + { + [TestMethod] + public void NullDelegateIsValid() + { + // Assert.DoesNotThrow + new DelegateValidator().ValidateDelegate(null); + } + + [TestMethod] + public void DelegateIsValidUnlessBlacklisted() + { + new DelegateValidator().ValidateDelegate(new Action(x => { })); + } + + [TestMethod, ExpectedException(typeof(UnsafeDeserializationException))] + public void SystemDiagnosticsDelegatesAreNotValid() + { + var del = new Func(Process.Start); + new DelegateValidator().ValidateDelegate(del); + } + + [TestMethod, ExpectedException(typeof(UnsafeDeserializationException))] + public void SystemIODelegatesAreNotValid() + { + var del = new Action(File.Delete); + new DelegateValidator().ValidateDelegate(del); + } + + [TestMethod] + public void MulticastDelegatesAreValidated() + { + var del = new Func((a, b) => null); + del = Delegate.Combine(del, del, del) as Func; + new DelegateValidator().ValidateDelegate(del); + } + + [TestMethod, ExpectedException(typeof(UnsafeDeserializationException))] + public void MulticastDelegatesWithSystemDiagnosticsMethodsAreNotValid() + { + var del = new Func((a, b) => null); + var start = new Func(Process.Start); + del = Delegate.Combine(del, del, start, del, del) as Func; + new DelegateValidator().ValidateDelegate(del); + } + } +} diff --git a/SafeDeserializationHelpers.Tests/Properties/AssemblyInfo.cs b/SafeDeserializationHelpers.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3b64527 --- /dev/null +++ b/SafeDeserializationHelpers.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SafeDeserializationHelpers.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SafeDeserializationHelpers.Tests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c808ddee-be52-4384-8376-d4966fff9438")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SafeDeserializationHelpers.Tests/SafeDeserializationHelpers.Tests.csproj b/SafeDeserializationHelpers.Tests/SafeDeserializationHelpers.Tests.csproj new file mode 100644 index 0000000..2b53d44 --- /dev/null +++ b/SafeDeserializationHelpers.Tests/SafeDeserializationHelpers.Tests.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {C808DDEE-BE52-4384-8376-D4966FFF9438} + Library + Properties + SafeDeserializationHelpers.Tests + SafeDeserializationHelpers.Tests + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + + + + + + + + + + {06ede6b8-ff31-45e3-9efb-7c92292d2404} + SafeDeserializationHelpers + + + + + + + \ No newline at end of file diff --git a/SafeDeserializationHelpers.sln b/SafeDeserializationHelpers.sln new file mode 100644 index 0000000..bf565b7 --- /dev/null +++ b/SafeDeserializationHelpers.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeDeserializationHelpers", "SafeDeserializationHelpers\SafeDeserializationHelpers.csproj", "{06EDE6B8-FF31-45E3-9EFB-7C92292D2404}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeDeserializationHelpers.Tests", "SafeDeserializationHelpers.Tests\SafeDeserializationHelpers.Tests.csproj", "{C808DDEE-BE52-4384-8376-D4966FFF9438}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {06EDE6B8-FF31-45E3-9EFB-7C92292D2404}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06EDE6B8-FF31-45E3-9EFB-7C92292D2404}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06EDE6B8-FF31-45E3-9EFB-7C92292D2404}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06EDE6B8-FF31-45E3-9EFB-7C92292D2404}.Release|Any CPU.Build.0 = Release|Any CPU + {C808DDEE-BE52-4384-8376-D4966FFF9438}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C808DDEE-BE52-4384-8376-D4966FFF9438}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C808DDEE-BE52-4384-8376-D4966FFF9438}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C808DDEE-BE52-4384-8376-D4966FFF9438}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6815B225-79EF-41D2-B260-4279E8EA5E82} + EndGlobalSection +EndGlobal diff --git a/SafeDeserializationHelpers/DelegateValidator.cs b/SafeDeserializationHelpers/DelegateValidator.cs new file mode 100644 index 0000000..269f51d --- /dev/null +++ b/SafeDeserializationHelpers/DelegateValidator.cs @@ -0,0 +1,66 @@ +namespace SafeDeserializationHelpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// Blacklist-based delegate validator. + /// + public class DelegateValidator + { + /// + /// The default blacklist of the namespaces. + /// + private static readonly string[] DefaultBlacklistedNamespaces = new[] + { + "System.IO", + "System.Diagnostics", + }; + + /// + /// Initializes a new instance of the class. + /// + /// Namespace blacklist. + public DelegateValidator(params string[] blacklistedNamespaces) + { + if (blacklistedNamespaces == null || blacklistedNamespaces.Length == 0) + { + blacklistedNamespaces = DefaultBlacklistedNamespaces; + } + + BlacklistedNamespaces = new HashSet(blacklistedNamespaces, StringComparer.OrdinalIgnoreCase); + } + + private HashSet BlacklistedNamespaces { get; } + + /// + /// Validates the given delegates. + /// Throws exceptions for methods defined in the blacklisted namespaces. + /// + /// The delegate to validate. + public void ValidateDelegate(Delegate del) + { + if (del == null) + { + return; + } + + foreach (var d in del.GetInvocationList()) + { + if (d == null) + { + continue; + } + + var type = d.Method.DeclaringType; + if (BlacklistedNamespaces.Contains(type.Namespace)) + { + var msg = $"Deserializing delegates for {type.FullName} may be unsafe."; + throw new UnsafeDeserializationException(msg); + } + } + } + } +} diff --git a/SafeDeserializationHelpers/GlobalSuppressions.cs b/SafeDeserializationHelpers/GlobalSuppressions.cs new file mode 100644 index 0000000..ec8ab7d --- /dev/null +++ b/SafeDeserializationHelpers/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "This is a visual garbage", Scope = "member", Target = "~M:SafeDeserializationHelpers.DelegateValidator.#ctor(System.String[])")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Not necessary for this project")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "", Scope = "member", Target = "~M:SafeDeserializationHelpers.DelegateValidator.ValidateDelegate(System.Delegate)")] + diff --git a/SafeDeserializationHelpers/Properties/AssemblyInfo.cs b/SafeDeserializationHelpers/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8eb134c --- /dev/null +++ b/SafeDeserializationHelpers/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SafeDeserializationHelpers")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SafeDeserializationHelpers")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("06ede6b8-ff31-45e3-9efb-7c92292d2404")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SafeDeserializationHelpers/SafeDeserializationHelpers.csproj b/SafeDeserializationHelpers/SafeDeserializationHelpers.csproj new file mode 100644 index 0000000..b0d38a7 --- /dev/null +++ b/SafeDeserializationHelpers/SafeDeserializationHelpers.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {06EDE6B8-FF31-45E3-9EFB-7C92292D2404} + Library + Properties + SafeDeserializationHelpers + SafeDeserializationHelpers + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeDeserializationHelpers/UnsafeDeserializationException.cs b/SafeDeserializationHelpers/UnsafeDeserializationException.cs new file mode 100644 index 0000000..faab0b2 --- /dev/null +++ b/SafeDeserializationHelpers/UnsafeDeserializationException.cs @@ -0,0 +1,39 @@ +namespace SafeDeserializationHelpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.Serialization; + using System.Security; + using System.Text; + + /// + /// Exception to be thrown when possible deserialization vulnerability is detected. + /// + [Serializable] + public class UnsafeDeserializationException : SecurityException + { + /// + /// Initializes a new instance of the class. + /// + public UnsafeDeserializationException() + : base() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception message. + public UnsafeDeserializationException(string message) + : base(message) + { + } + + /// + protected UnsafeDeserializationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/SafeDeserializationHelpers/packages.config b/SafeDeserializationHelpers/packages.config new file mode 100644 index 0000000..a30cbc8 --- /dev/null +++ b/SafeDeserializationHelpers/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file