From: Levi Broderick Date: Thu, 16 Jul 2020 05:26:32 +0000 (-0700) Subject: Introduce BinaryFormatter enablement switch; disable on wasm (#38963) X-Git-Tag: submit/tizen/20210909.063632~6641 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0554117cd70e0ab594178a4faa761c119afe1a24;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Introduce BinaryFormatter enablement switch; disable on wasm (#38963) Applications are given the option of disabling BinaryFormatter.Serialize and BinaryFormatter.Deserialize, meaning they'll throw NotSupportedException if anybody tries calling them. WASM projects always disable these APIs via a hardcoded mechanism; there is no re-enablement mechanism for those projects. This commit only introduces the switch. It does not disable it in any project types by default. (Exception: wasm, as mentioned above.) The related PR https://github.com/dotnet/sdk/pull/12434 causes aspnet 5.0+ applications to disable BinaryFormatter functionality by default, but apps can re-enable if needed. --- diff --git a/docs/workflow/trimming/feature-switches.md b/docs/workflow/trimming/feature-switches.md index 2f05e5b..ad911e8 100644 --- a/docs/workflow/trimming/feature-switches.md +++ b/docs/workflow/trimming/feature-switches.md @@ -9,7 +9,8 @@ configurations but their defaults might vary as any SDK can set the defaults dif | MSBuild Property Name | AppContext Setting | Description | |-|-|-| | DebuggerSupport | System.Diagnostics.Debugger.IsSupported | Any dependency that enables better debugging experience to be trimmed when set to false | -| EnableUnsafeUTF7Encoding | System.Text.Encoding.EnableUnsafeUTF7Encoding | Insecure UTF-7 encoding is trimmed when set to false | +| EnableUnsafeUTF7Encoding | System.Text.Encoding.EnableUnsafeUTF7Encoding | Insecure UTF-7 encoding is trimmed when set to false | +| EnableUnsafeBinaryFormatterSerialization | System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization | BinaryFormatter serialization support is trimmed when set to false | | EventSourceSupport | System.Diagnostics.Tracing.EventSource.IsSupported | Any EventSource related code or logic is trimmed when set to false | | InvariantGlobalization | System.Globalization.Invariant | All globalization specific code and data is trimmed when set to true | | UseSystemResourceKeys | System.Resources.UseSystemResourceKeys | Any localizable resources for system assemblies is trimmed when set to true | diff --git a/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs b/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs index 4457c08..bcbc8d5 100644 --- a/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs +++ b/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs @@ -51,6 +51,11 @@ namespace System return true; } + if (switchName == "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization") + { + return true; + } + return false; } } diff --git a/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Serialization.Tests.cs b/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Serialization.Tests.cs index aa20a9a..ce3bca0 100644 --- a/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Serialization.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Serialization.Tests.cs @@ -9,7 +9,7 @@ namespace System.Collections.Tests { public abstract partial class IEnumerable_Generic_Tests : TestBase { - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] [MemberData(nameof(ValidCollectionSizes))] public void IGenericSharedAPI_SerializeDeserialize(int count) { diff --git a/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Serialization.Tests.cs b/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Serialization.Tests.cs index 8fbdfd9..8ad921c 100644 --- a/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Serialization.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Serialization.Tests.cs @@ -11,7 +11,7 @@ namespace System.Collections.Tests { public abstract partial class IEnumerable_NonGeneric_Tests : TestBase { - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] [MemberData(nameof(ValidCollectionSizes))] public void IGenericSharedAPI_SerializeDeserialize(int count) { diff --git a/src/libraries/System.Collections/tests/Generic/Comparers/EqualityComparer.Generic.Serialization.Tests.cs b/src/libraries/System.Collections/tests/Generic/Comparers/EqualityComparer.Generic.Serialization.Tests.cs index 344218f..d7b92ac 100644 --- a/src/libraries/System.Collections/tests/Generic/Comparers/EqualityComparer.Generic.Serialization.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Comparers/EqualityComparer.Generic.Serialization.Tests.cs @@ -11,7 +11,7 @@ namespace System.Collections.Generic.Tests { public abstract partial class ComparersGenericTests { - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void EqualityComparer_SerializationRoundtrip() { var bf = new BinaryFormatter(); diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs index 49fb3e2..ee9957b 100644 --- a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs @@ -399,7 +399,7 @@ namespace System.Collections.Tests return dict; } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void ComparerSerialization() { // Strings switch between randomized and non-randomized comparers, diff --git a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs index ea60d18..65261bf 100644 --- a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs @@ -658,7 +658,7 @@ namespace System.Collections.Tests #region Serialization - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void ComparerSerialization() { // Strings switch between randomized and non-randomized comparers, diff --git a/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidationExceptionTests.cs b/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidationExceptionTests.cs index 7e30a61..65831b5 100644 --- a/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidationExceptionTests.cs +++ b/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidationExceptionTests.cs @@ -80,7 +80,7 @@ namespace System.ComponentModel.DataAnnotations.Tests Assert.Same(value, ex.Value); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var stream = new MemoryStream()) diff --git a/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidAsynchronousStateExceptionTests.cs b/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidAsynchronousStateExceptionTests.cs index d90aa35..b94a27c 100644 --- a/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidAsynchronousStateExceptionTests.cs +++ b/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidAsynchronousStateExceptionTests.cs @@ -42,7 +42,7 @@ namespace System.ComponentModel.Tests Assert.Null(exception.ParamName); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var stream = new MemoryStream()) diff --git a/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidEnumArgumentExceptionTests.cs b/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidEnumArgumentExceptionTests.cs index d6f592f..4b6187e 100644 --- a/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidEnumArgumentExceptionTests.cs +++ b/src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidEnumArgumentExceptionTests.cs @@ -87,7 +87,7 @@ namespace System.ComponentModel.Tests AssertExtensions.Throws("enumClass", () => new InvalidEnumArgumentException("argumentName", 1, null)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var stream = new MemoryStream()) diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/Design/CheckoutExceptionTests.cs b/src/libraries/System.ComponentModel.TypeConverter/tests/Design/CheckoutExceptionTests.cs index a284948..635c7bb 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/tests/Design/CheckoutExceptionTests.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/tests/Design/CheckoutExceptionTests.cs @@ -60,7 +60,7 @@ namespace System.ComponentModel.Design.Tests Assert.Null(exception.InnerException); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var stream = new MemoryStream()) diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/LicenseExceptionTests.cs b/src/libraries/System.ComponentModel.TypeConverter/tests/LicenseExceptionTests.cs index 1a275c3..e41b3b2 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/tests/LicenseExceptionTests.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/tests/LicenseExceptionTests.cs @@ -73,7 +73,7 @@ namespace System.ComponentModel.Tests Assert.Equal(message, exception.Message); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var stream = new MemoryStream()) diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/WarningExceptionTests.cs b/src/libraries/System.ComponentModel.TypeConverter/tests/WarningExceptionTests.cs index 998ae25..0940a0b 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/tests/WarningExceptionTests.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/tests/WarningExceptionTests.cs @@ -74,7 +74,7 @@ namespace System.ComponentModel.Tests Assert.Equal(message, exception.Message); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var stream = new MemoryStream()) diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml index 1ebf944..81e98c3f 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml @@ -12,5 +12,8 @@ + + + diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 8dbea2e..e3e8692 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3757,4 +3757,7 @@ Object must be of type Half. + + BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information. + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 743c99e..99a2fb9 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -819,6 +819,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.Core.cs b/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.Core.cs index c76a8b6..880f21c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.Core.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.Core.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; +using System.Runtime.Serialization; using System.Text; using System.Threading; @@ -50,7 +51,14 @@ namespace System.Resources if (_binaryFormatter == null) { - InitializeBinaryFormatter(); + if (!InitializeBinaryFormatter()) + { + // The linker trimmed away the BinaryFormatter implementation and we can't call into it. + // We'll throw an exception with the same text that BinaryFormatter would have thrown + // had we been able to call into it. Keep this resource string in sync with the same + // resource from the Formatters assembly. + throw new NotSupportedException(SR.BinaryFormatter_SerializationDisallowed); + } } Type type = FindType(typeIndex); @@ -65,8 +73,11 @@ namespace System.Resources } // Issue https://github.com/dotnet/runtime/issues/39290 tracks finding an alternative to BinaryFormatter - private void InitializeBinaryFormatter() + private bool InitializeBinaryFormatter() { + // If BinaryFormatter support is disabled for the app, the linker will replace this entire + // method body with "return false;", skipping all reflection code below. + LazyInitializer.EnsureInitialized(ref s_binaryFormatterType, () => Type.GetType("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter, System.Runtime.Serialization.Formatters, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: true)!); @@ -83,6 +94,8 @@ namespace System.Resources }); _binaryFormatter = Activator.CreateInstance(s_binaryFormatterType!)!; + + return true; // initialization successful } // generic method that we specialize at runtime once we've loaded the BinaryFormatter type diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.SerializationGuard.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.SerializationGuard.cs new file mode 100644 index 0000000..cc66407 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.SerializationGuard.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security; +using System.Threading; + +namespace System.Runtime.Serialization +{ + /// The structure for holding all of the data needed for object serialization and deserialization. + public sealed partial class SerializationInfo + { + internal static AsyncLocal AsyncDeserializationInProgress { get; } = new AsyncLocal(); + +#if !CORECLR + // On AoT, assume private members are reflection blocked, so there's no further protection required + // for the thread's DeserializationTracker + [ThreadStatic] + private static DeserializationTracker? t_deserializationTracker; + + private static DeserializationTracker GetThreadDeserializationTracker() => + t_deserializationTracker ??= new DeserializationTracker(); +#endif // !CORECLR + + // Returns true if deserialization is currently in progress + public static bool DeserializationInProgress + { +#if CORECLR + [DynamicSecurityMethod] // Methods containing StackCrawlMark local var must be marked DynamicSecurityMethod +#endif + get + { + if (AsyncDeserializationInProgress.Value) + { + return true; + } + +#if CORECLR + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + DeserializationTracker tracker = Thread.GetThreadDeserializationTracker(ref stackMark); +#else + DeserializationTracker tracker = GetThreadDeserializationTracker(); +#endif + bool result = tracker.DeserializationInProgress; + return result; + } + } + + // Throws a SerializationException if dangerous deserialization is currently + // in progress + public static void ThrowIfDeserializationInProgress() + { + if (DeserializationInProgress) + { + throw new SerializationException(SR.Serialization_DangerousDeserialization); + } + } + + // Throws a DeserializationBlockedException if dangerous deserialization is currently + // in progress and the AppContext switch Switch.System.Runtime.Serialization.SerializationGuard.{switchSuffix} + // is not true. The value of the switch is cached in cachedValue to avoid repeated lookups: + // 0: No value cached + // 1: The switch is true + // -1: The switch is false + public static void ThrowIfDeserializationInProgress(string switchSuffix, ref int cachedValue) + { + const string SwitchPrefix = "Switch.System.Runtime.Serialization.SerializationGuard."; + if (switchSuffix == null) + { + throw new ArgumentNullException(nameof(switchSuffix)); + } + if (string.IsNullOrWhiteSpace(switchSuffix)) + { + throw new ArgumentException(SR.Argument_EmptyName, nameof(switchSuffix)); + } + + if (cachedValue == 0) + { + if (AppContext.TryGetSwitch(SwitchPrefix + switchSuffix, out bool isEnabled) && isEnabled) + { + cachedValue = 1; + } + else + { + cachedValue = -1; + } + } + + if (cachedValue == 1) + { + return; + } + else if (cachedValue == -1) + { + if (DeserializationInProgress) + { + throw new SerializationException(SR.Format(SR.Serialization_DangerousDeserialization_Switch, SwitchPrefix + switchSuffix)); + } + } + else + { + throw new ArgumentOutOfRangeException(nameof(cachedValue)); + } + } + + // Declares that the current thread and async context have begun deserialization. + // In this state, if the SerializationGuard or other related AppContext switches are set, + // actions likely to be dangerous during deserialization, such as starting a process will be blocked. + // Returns a DeserializationToken that must be disposed to remove the deserialization state. +#if CORECLR + [DynamicSecurityMethod] // Methods containing StackCrawlMark local var must be marked DynamicSecurityMethod +#endif + public static DeserializationToken StartDeserialization() + { + if (LocalAppContextSwitches.SerializationGuard) + { +#if CORECLR + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + DeserializationTracker tracker = Thread.GetThreadDeserializationTracker(ref stackMark); +#else + DeserializationTracker tracker = GetThreadDeserializationTracker(); +#endif + if (!tracker.DeserializationInProgress) + { + lock (tracker) + { + if (!tracker.DeserializationInProgress) + { + AsyncDeserializationInProgress.Value = true; + tracker.DeserializationInProgress = true; + return new DeserializationToken(tracker); + } + } + } + } + + return new DeserializationToken(null); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs index 0f7eb97..ccc7bd6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs @@ -3,13 +3,11 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Security; -using System.Threading; namespace System.Runtime.Serialization { /// The structure for holding all of the data needed for object serialization and deserialization. - public sealed class SerializationInfo + public sealed partial class SerializationInfo { private const int DefaultSize = 4; @@ -25,133 +23,6 @@ namespace System.Runtime.Serialization private string _rootTypeAssemblyName; private Type _rootType; - internal static AsyncLocal AsyncDeserializationInProgress { get; } = new AsyncLocal(); - -#if !CORECLR - // On AoT, assume private members are reflection blocked, so there's no further protection required - // for the thread's DeserializationTracker - [ThreadStatic] - private static DeserializationTracker? t_deserializationTracker; - - private static DeserializationTracker GetThreadDeserializationTracker() => - t_deserializationTracker ??= new DeserializationTracker(); -#endif // !CORECLR - - // Returns true if deserialization is currently in progress - public static bool DeserializationInProgress - { -#if CORECLR - [DynamicSecurityMethod] // Methods containing StackCrawlMark local var must be marked DynamicSecurityMethod -#endif - get - { - if (AsyncDeserializationInProgress.Value) - { - return true; - } - -#if CORECLR - StackCrawlMark stackMark = StackCrawlMark.LookForMe; - DeserializationTracker tracker = Thread.GetThreadDeserializationTracker(ref stackMark); -#else - DeserializationTracker tracker = GetThreadDeserializationTracker(); -#endif - bool result = tracker.DeserializationInProgress; - return result; - } - } - - // Throws a SerializationException if dangerous deserialization is currently - // in progress - public static void ThrowIfDeserializationInProgress() - { - if (DeserializationInProgress) - { - throw new SerializationException(SR.Serialization_DangerousDeserialization); - } - } - - // Throws a DeserializationBlockedException if dangerous deserialization is currently - // in progress and the AppContext switch Switch.System.Runtime.Serialization.SerializationGuard.{switchSuffix} - // is not true. The value of the switch is cached in cachedValue to avoid repeated lookups: - // 0: No value cached - // 1: The switch is true - // -1: The switch is false - public static void ThrowIfDeserializationInProgress(string switchSuffix, ref int cachedValue) - { - const string SwitchPrefix = "Switch.System.Runtime.Serialization.SerializationGuard."; - if (switchSuffix == null) - { - throw new ArgumentNullException(nameof(switchSuffix)); - } - if (string.IsNullOrWhiteSpace(switchSuffix)) - { - throw new ArgumentException(SR.Argument_EmptyName, nameof(switchSuffix)); - } - - if (cachedValue == 0) - { - if (AppContext.TryGetSwitch(SwitchPrefix + switchSuffix, out bool isEnabled) && isEnabled) - { - cachedValue = 1; - } - else - { - cachedValue = -1; - } - } - - if (cachedValue == 1) - { - return; - } - else if (cachedValue == -1) - { - if (DeserializationInProgress) - { - throw new SerializationException(SR.Format(SR.Serialization_DangerousDeserialization_Switch, SwitchPrefix + switchSuffix)); - } - } - else - { - throw new ArgumentOutOfRangeException(nameof(cachedValue)); - } - } - - // Declares that the current thread and async context have begun deserialization. - // In this state, if the SerializationGuard or other related AppContext switches are set, - // actions likely to be dangerous during deserialization, such as starting a process will be blocked. - // Returns a DeserializationToken that must be disposed to remove the deserialization state. -#if CORECLR - [DynamicSecurityMethod] // Methods containing StackCrawlMark local var must be marked DynamicSecurityMethod -#endif - public static DeserializationToken StartDeserialization() - { - if (LocalAppContextSwitches.SerializationGuard) - { -#if CORECLR - StackCrawlMark stackMark = StackCrawlMark.LookForMe; - DeserializationTracker tracker = Thread.GetThreadDeserializationTracker(ref stackMark); -#else - DeserializationTracker tracker = GetThreadDeserializationTracker(); -#endif - if (!tracker.DeserializationInProgress) - { - lock (tracker) - { - if (!tracker.DeserializationInProgress) - { - AsyncDeserializationInProgress.Value = true; - tracker.DeserializationInProgress = true; - return new DeserializationToken(tracker); - } - } - } - } - - return new DeserializationToken(null); - } - [CLSCompliant(false)] public SerializationInfo(Type type, IFormatterConverter converter) { diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/ILLink/ILLink.Substitutions.xml b/src/libraries/System.Runtime.Serialization.Formatters/src/ILLink/ILLink.Substitutions.xml new file mode 100644 index 0000000..891fc4c --- /dev/null +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/ILLink/ILLink.Substitutions.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx b/src/libraries/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx index 078b0d6..db744f8 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx @@ -249,4 +249,7 @@ Unable to read beyond the end of the stream. + + BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information. + diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj b/src/libraries/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj index e7f6a3f..9d66e01 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj @@ -2,9 +2,18 @@ System.Runtime.Serialization.Formatters System.Runtime.Serialization.Formatters - $(NetCoreAppCurrent) + $(NetCoreAppCurrent);$(NetCoreAppCurrent)-Browser enable + + $(NoWarn);CS0649 + + + $(MSBuildThisFileDirectory)ILLink\ + + + + @@ -14,6 +23,10 @@ + + + Common\System\LocalAppContextSwitches.Common.cs + @@ -49,6 +62,8 @@ + + diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.cs new file mode 100644 index 0000000..9183902 --- /dev/null +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + public sealed partial class BinaryFormatter : IFormatter + { + [Obsolete(Obsoletions.InsecureSerializationMessage, DiagnosticId = Obsoletions.InsecureSerializationDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + public object Deserialize(Stream serializationStream) + { + // don't refactor the 'throw' into a helper method; linker will have difficulty trimming + if (!LocalAppContextSwitches.BinaryFormatterEnabled) + { + throw new NotSupportedException(SR.BinaryFormatter_SerializationDisallowed); + } + + if (serializationStream == null) + { + throw new ArgumentNullException(nameof(serializationStream)); + } + if (serializationStream.CanSeek && (serializationStream.Length == 0)) + { + throw new SerializationException(SR.Serialization_Stream); + } + + var formatterEnums = new InternalFE() + { + _typeFormat = _typeFormat, + _serializerTypeEnum = InternalSerializerTypeE.Binary, + _assemblyFormat = _assemblyFormat, + _securityLevel = _securityLevel, + }; + + var reader = new ObjectReader(serializationStream, _surrogates, _context, formatterEnums, _binder) + { + _crossAppDomainArray = _crossAppDomainArray + }; + try + { + var parser = new BinaryParser(serializationStream, reader); + return reader.Deserialize(parser); + } + catch (SerializationException) + { + throw; + } + catch (Exception e) + { + throw new SerializationException(SR.Serialization_CorruptedStream, e); + } + } + + [Obsolete(Obsoletions.InsecureSerializationMessage, DiagnosticId = Obsoletions.InsecureSerializationDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + public void Serialize(Stream serializationStream, object graph) + { + // don't refactor the 'throw' into a helper method; linker will have difficulty trimming + if (!LocalAppContextSwitches.BinaryFormatterEnabled) + { + throw new NotSupportedException(SR.BinaryFormatter_SerializationDisallowed); + } + + if (serializationStream == null) + { + throw new ArgumentNullException(nameof(serializationStream)); + } + + var formatterEnums = new InternalFE() + { + _typeFormat = _typeFormat, + _serializerTypeEnum = InternalSerializerTypeE.Binary, + _assemblyFormat = _assemblyFormat, + }; + + var sow = new ObjectWriter(_surrogates, _context, formatterEnums, _binder); + BinaryFormatterWriter binaryWriter = new BinaryFormatterWriter(serializationStream, sow, _typeFormat); + sow.Serialize(graph, binaryWriter); + _crossAppDomainArray = sow._crossAppDomainArray; + } + } +} diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.cs new file mode 100644 index 0000000..0a8bdd5 --- /dev/null +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + public sealed partial class BinaryFormatter : IFormatter + { + [Obsolete(Obsoletions.InsecureSerializationMessage, DiagnosticId = Obsoletions.InsecureSerializationDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + public object Deserialize(Stream serializationStream) + => throw new PlatformNotSupportedException(SR.BinaryFormatter_SerializationDisallowed); + + [Obsolete(Obsoletions.InsecureSerializationMessage, DiagnosticId = Obsoletions.InsecureSerializationDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + public void Serialize(Stream serializationStream, object graph) + => throw new PlatformNotSupportedException(SR.BinaryFormatter_SerializationDisallowed); + } +} diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs index 5ca47d5..59d2d51 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs @@ -1,14 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.InteropServices; namespace System.Runtime.Serialization.Formatters.Binary { - public sealed class BinaryFormatter : IFormatter + public sealed partial class BinaryFormatter : IFormatter { private static readonly ConcurrentDictionary s_typeNameCache = new ConcurrentDictionary(); @@ -37,71 +34,6 @@ namespace System.Runtime.Serialization.Formatters.Binary _context = context; } - [Obsolete(Obsoletions.InsecureSerializationMessage, DiagnosticId = Obsoletions.InsecureSerializationDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] - public object Deserialize(Stream serializationStream) => Deserialize(serializationStream, true); - - internal object Deserialize(Stream serializationStream, bool check) - { - if (serializationStream == null) - { - throw new ArgumentNullException(nameof(serializationStream)); - } - if (serializationStream.CanSeek && (serializationStream.Length == 0)) - { - throw new SerializationException(SR.Serialization_Stream); - } - - var formatterEnums = new InternalFE() - { - _typeFormat = _typeFormat, - _serializerTypeEnum = InternalSerializerTypeE.Binary, - _assemblyFormat = _assemblyFormat, - _securityLevel = _securityLevel, - }; - - var reader = new ObjectReader(serializationStream, _surrogates, _context, formatterEnums, _binder) - { - _crossAppDomainArray = _crossAppDomainArray - }; - try - { - var parser = new BinaryParser(serializationStream, reader); - return reader.Deserialize(parser, check); - } - catch (SerializationException) - { - throw; - } - catch (Exception e) - { - throw new SerializationException(SR.Serialization_CorruptedStream, e); - } - } - - [Obsolete(Obsoletions.InsecureSerializationMessage, DiagnosticId = Obsoletions.InsecureSerializationDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] - public void Serialize(Stream serializationStream, object graph) => - Serialize(serializationStream, graph, true); - - internal void Serialize(Stream serializationStream, object graph, bool check) - { - if (serializationStream == null) - { - throw new ArgumentNullException(nameof(serializationStream)); - } - - var formatterEnums = new InternalFE() - { - _typeFormat = _typeFormat, - _serializerTypeEnum = InternalSerializerTypeE.Binary, - _assemblyFormat = _assemblyFormat, - }; - - var sow = new ObjectWriter(_surrogates, _context, formatterEnums, _binder); - BinaryFormatterWriter binaryWriter = new BinaryFormatterWriter(serializationStream, sow, _typeFormat); - sow.Serialize(graph, binaryWriter, check); - _crossAppDomainArray = sow._crossAppDomainArray; - } - internal static TypeInformation GetTypeInformation(Type type) => s_typeNameCache.GetOrAdd(type, t => { diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs index 332db03..cb1d8f4 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs @@ -74,7 +74,7 @@ namespace System.Runtime.Serialization.Formatters.Binary _binder = binder; _formatterEnums = formatterEnums; } - internal object Deserialize(BinaryParser serParser, bool fCheck) + internal object Deserialize(BinaryParser serParser) { if (serParser == null) { diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs index 2bde4e2..8ecc282 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs @@ -47,7 +47,7 @@ namespace System.Runtime.Serialization.Formatters.Binary _objectManager = new SerializationObjectManager(context); } - internal void Serialize(object graph, BinaryFormatterWriter serWriter, bool fCheck) + internal void Serialize(object graph, BinaryFormatterWriter serWriter) { if (graph == null) { diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/LocalAppContextSwitches.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/LocalAppContextSwitches.cs new file mode 100644 index 0000000..eead42d --- /dev/null +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/LocalAppContextSwitches.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System +{ + internal static partial class LocalAppContextSwitches + { + private static int s_binaryFormatterEnabled; + public static bool BinaryFormatterEnabled + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", ref s_binaryFormatterEnabled); + } + } +} diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs index bdcf0e4..8b5d1ab 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs @@ -18,6 +18,7 @@ using Xunit; namespace System.Runtime.Serialization.Formatters.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public partial class BinaryFormatterTests : FileCleanupTestBase { // On 32-bit we can't test these high inputs as they cause OutOfMemoryExceptions. diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/DisableBitTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/DisableBitTests.cs new file mode 100644 index 0000000..3311907 --- /dev/null +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/DisableBitTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public static class DisableBitTests + { + // these tests only make sense on platforms with both "feature switch" and RemoteExecutor support + public static bool ShouldRunFullFeatureSwitchEnablementChecks => !PlatformDetection.IsNetFramework && RemoteExecutor.IsSupported; + + // determines whether BinaryFormatter will always fail, regardless of config, on this platform + public static bool IsBinaryFormatterSuppressedOnThisPlatform => !PlatformDetection.IsBinaryFormatterSupported; + + private const string EnableBinaryFormatterSwitchName = "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization"; + private const string MoreInfoUrl = "https://aka.ms/binaryformatter"; + + [ConditionalFact(nameof(IsBinaryFormatterSuppressedOnThisPlatform))] + public static void DisabledAlwaysInBrowser() + { + // First, test serialization + + MemoryStream ms = new MemoryStream(); + BinaryFormatter bf = new BinaryFormatter(); + var ex = Assert.Throws(() => bf.Serialize(ms, "A string to serialize.")); + Assert.Contains(MoreInfoUrl, ex.Message, StringComparison.Ordinal); // error message should link to the more info URL + + // Then test deserialization + + ex = Assert.Throws(() => bf.Deserialize(ms)); + Assert.Contains(MoreInfoUrl, ex.Message, StringComparison.Ordinal); // error message should link to the more info URL + } + + [ConditionalFact(nameof(ShouldRunFullFeatureSwitchEnablementChecks))] + public static void DisabledThroughFeatureSwitch() + { + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions[EnableBinaryFormatterSwitchName] = bool.FalseString; + + RemoteExecutor.Invoke(() => + { + // First, test serialization + + MemoryStream ms = new MemoryStream(); + BinaryFormatter bf = new BinaryFormatter(); + var ex = Assert.Throws(() => bf.Serialize(ms, "A string to serialize.")); + Assert.Contains(MoreInfoUrl, ex.Message, StringComparison.Ordinal); // error message should link to the more info URL + + // Then test deserialization + + ex = Assert.Throws(() => bf.Deserialize(ms)); + Assert.Contains(MoreInfoUrl, ex.Message, StringComparison.Ordinal); // error message should link to the more info URL + }, options).Dispose(); + } + } +} diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs index 91201a4..daf11b1 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs @@ -21,7 +21,7 @@ namespace System.Runtime.Serialization.Formatters.Tests Assert.Null(typeName); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void BindToType_AllValuesTracked() { var s = new MemoryStream(); diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs index f0d3e9a..1d10621 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs @@ -12,6 +12,7 @@ using Xunit; namespace System.Runtime.Serialization.Formatters.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public static class SerializationGuardTests { [Fact] diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj b/src/libraries/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj index ff7628e..635709d 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/src/libraries/System.Runtime.Serialization.Primitives/tests/System/Runtime/Serialization/InvalidDataContractExceptionTests.cs b/src/libraries/System.Runtime.Serialization.Primitives/tests/System/Runtime/Serialization/InvalidDataContractExceptionTests.cs index 47f52e9..57a4d5a 100644 --- a/src/libraries/System.Runtime.Serialization.Primitives/tests/System/Runtime/Serialization/InvalidDataContractExceptionTests.cs +++ b/src/libraries/System.Runtime.Serialization.Primitives/tests/System/Runtime/Serialization/InvalidDataContractExceptionTests.cs @@ -36,7 +36,7 @@ namespace System.Runtime.Serialization.Tests Assert.Equal(innerException, exception.InnerException); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var memoryStream = new MemoryStream()) diff --git a/src/libraries/System.Runtime/tests/System/Runtime/Serialization/SerializationExceptionTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/Serialization/SerializationExceptionTests.cs index 6f92853..9f140c6 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/Serialization/SerializationExceptionTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/Serialization/SerializationExceptionTests.cs @@ -42,7 +42,7 @@ namespace System.Runtime.Serialization.Tests Assert.Equal(COR_E_SERIALIZATION, exception.HResult); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))] public void Ctor_SerializationInfo_StreamingContext() { using (var memoryStream = new MemoryStream()) diff --git a/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.xml b/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.xml index f0304dd..4ee6414 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.xml +++ b/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.xml @@ -9,5 +9,8 @@ + + +