Introduce BinaryFormatter enablement switch; disable on wasm (#38963)
authorLevi Broderick <GrabYourPitchforks@users.noreply.github.com>
Thu, 16 Jul 2020 05:26:32 +0000 (22:26 -0700)
committerGitHub <noreply@github.com>
Thu, 16 Jul 2020 05:26:32 +0000 (22:26 -0700)
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.

36 files changed:
docs/workflow/trimming/feature-switches.md
src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs
src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Serialization.Tests.cs
src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Serialization.Tests.cs
src/libraries/System.Collections/tests/Generic/Comparers/EqualityComparer.Generic.Serialization.Tests.cs
src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs
src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs
src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/ValidationExceptionTests.cs
src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidAsynchronousStateExceptionTests.cs
src/libraries/System.ComponentModel.Primitives/tests/System/ComponentModel/InvalidEnumArgumentExceptionTests.cs
src/libraries/System.ComponentModel.TypeConverter/tests/Design/CheckoutExceptionTests.cs
src/libraries/System.ComponentModel.TypeConverter/tests/LicenseExceptionTests.cs
src/libraries/System.ComponentModel.TypeConverter/tests/WarningExceptionTests.cs
src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Resources/ResourceReader.Core.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.SerializationGuard.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs
src/libraries/System.Runtime.Serialization.Formatters/src/ILLink/ILLink.Substitutions.xml [new file with mode: 0644]
src/libraries/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx
src/libraries/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj
src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.cs [new file with mode: 0644]
src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.cs [new file with mode: 0644]
src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs
src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs
src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs
src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/LocalAppContextSwitches.cs [new file with mode: 0644]
src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs
src/libraries/System.Runtime.Serialization.Formatters/tests/DisableBitTests.cs [new file with mode: 0644]
src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs
src/libraries/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs
src/libraries/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj
src/libraries/System.Runtime.Serialization.Primitives/tests/System/Runtime/Serialization/InvalidDataContractExceptionTests.cs
src/libraries/System.Runtime/tests/System/Runtime/Serialization/SerializationExceptionTests.cs
src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.xml

index 2f05e5b..ad911e8 100644 (file)
@@ -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 |
index 4457c08..bcbc8d5 100644 (file)
@@ -51,6 +51,11 @@ namespace System
                 return true;
             }
 
+            if (switchName == "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization")
+            {
+                return true;
+            }
+
             return false;
         }
     }
index aa20a9a..ce3bca0 100644 (file)
@@ -9,7 +9,7 @@ namespace System.Collections.Tests
 {
     public abstract partial class IEnumerable_Generic_Tests<T> : TestBase<T>
     {
-        [Theory]
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))]
         [MemberData(nameof(ValidCollectionSizes))]
         public void IGenericSharedAPI_SerializeDeserialize(int count)
         {
index 8fbdfd9..8ad921c 100644 (file)
@@ -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)
         {
index 344218f..d7b92ac 100644 (file)
@@ -11,7 +11,7 @@ namespace System.Collections.Generic.Tests
 {
     public abstract partial class ComparersGenericTests<T>
     {
-        [Fact]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))]
         public void EqualityComparer_SerializationRoundtrip()
         {
             var bf = new BinaryFormatter();
index 49fb3e2..ee9957b 100644 (file)
@@ -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,
index ea60d18..65261bf 100644 (file)
@@ -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,
index 7e30a61..65831b5 100644 (file)
@@ -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())
index d90aa35..b94a27c 100644 (file)
@@ -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())
index d6f592f..4b6187e 100644 (file)
@@ -87,7 +87,7 @@ namespace System.ComponentModel.Tests
             AssertExtensions.Throws<ArgumentNullException, NullReferenceException>("enumClass", () => new InvalidEnumArgumentException("argumentName", 1, null));
         }
 
-        [Fact]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))]
         public void Ctor_SerializationInfo_StreamingContext()
         {
             using (var stream = new MemoryStream())
index a284948..635c7bb 100644 (file)
@@ -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())
index 1a275c3..e41b3b2 100644 (file)
@@ -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())
index 998ae25..0940a0b 100644 (file)
@@ -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())
index 1ebf944..81e98c3 100644 (file)
@@ -12,5 +12,8 @@
     <type fullname="System.LocalAppContextSwitches">
       <method signature="System.Boolean get_EnableUnsafeUTF7Encoding()" body="stub" value="false" feature="System.Text.Encoding.EnableUnsafeUTF7Encoding" featurevalue="false" />
     </type>
+    <type fullname="System.Resources.ResourceReader">
+      <method signature="System.Boolean InitializeBinaryFormatter()" body="stub" value="false" feature="System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization" featurevalue="false" />
+    </type>
   </assembly>
 </linker>
index 8dbea2e..e3e8692 100644 (file)
   <data name="Arg_MustBeHalf" xml:space="preserve">
     <value>Object must be of type Half.</value>
   </data>
+  <data name="BinaryFormatter_SerializationDisallowed" xml:space="preserve">
+    <value>BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information.</value>
+  </data>
 </root>
index 743c99e..99a2fb9 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SafeSerializationEventArgs.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SerializationException.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SerializationInfo.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SerializationInfo.SerializationGuard.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SerializationInfoEnumerator.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\StreamingContext.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Versioning\ComponentGuaranteesAttribute.cs" />
index c76a8b6..880f21c 100644 (file)
@@ -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 (file)
index 0000000..cc66407
--- /dev/null
@@ -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
+{
+    /// <summary>The structure for holding all of the data needed for object serialization and deserialization.</summary>
+    public sealed partial class SerializationInfo
+    {
+        internal static AsyncLocal<bool> AsyncDeserializationInProgress { get; } = new AsyncLocal<bool>();
+
+#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);
+        }
+    }
+}
index 0f7eb97..ccc7bd6 100644 (file)
@@ -3,13 +3,11 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Security;
-using System.Threading;
 
 namespace System.Runtime.Serialization
 {
     /// <summary>The structure for holding all of the data needed for object serialization and deserialization.</summary>
-    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<bool> AsyncDeserializationInProgress { get; } = new AsyncLocal<bool>();
-
-#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 (file)
index 0000000..891fc4c
--- /dev/null
@@ -0,0 +1,7 @@
+<linker>
+  <assembly fullname="System.Runtime.Serialization.Formatters">
+    <type fullname="System.LocalAppContextSwitches">
+      <method signature="System.Boolean get_BinaryFormatterEnabled()" body="stub" value="false" feature="System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization" featurevalue="false" />
+    </type>
+  </assembly>
+</linker>
index 078b0d6..db744f8 100644 (file)
   <data name="IO_EOF_ReadBeyondEOF" xml:space="preserve">
     <value>Unable to read beyond the end of the stream.</value>
   </data>
+  <data name="BinaryFormatter_SerializationDisallowed" xml:space="preserve">
+    <value>BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information.</value>
+  </data>
 </root>
index e7f6a3f..9d66e01 100644 (file)
@@ -2,9 +2,18 @@
   <PropertyGroup>
     <AssemblyName>System.Runtime.Serialization.Formatters</AssemblyName>
     <RootNamespace>System.Runtime.Serialization.Formatters</RootNamespace>
-    <TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppCurrent)-Browser</TargetFrameworks>
     <Nullable>enable</Nullable>
+    <!-- When we replace implementations with PNSE, need to suppress some "field is never assigned to" warnings. -->
+    <NoWarn Condition="'$(TargetsBrowser)' == 'true'">$(NoWarn);CS0649</NoWarn>
   </PropertyGroup>
+  <!-- ILLinker settings -->
+  <PropertyGroup>
+    <ILLinkDirectory>$(MSBuildThisFileDirectory)ILLink\</ILLinkDirectory>
+  </PropertyGroup>
+  <ItemGroup>
+    <ILLinkSubstitutionsXmls Include="$(ILLinkDirectory)ILLink.Substitutions.xml" />
+  </ItemGroup>
   <ItemGroup>
     <Compile Include="System.Runtime.Serialization.Formatters.TypeForwards.cs" />
     <Compile Include="System\Runtime\Serialization\DeserializationEventHandler.cs" />
     <Compile Include="System\Runtime\Serialization\IFormatter.cs" />
     <Compile Include="System\Runtime\Serialization\ISerializationSurrogate.cs" />
     <Compile Include="System\Runtime\Serialization\ISurrogateSelector.cs" />
+    <Compile Include="System\Runtime\Serialization\LocalAppContextSwitches.cs" />
+    <Compile Include="$(CommonPath)System\LocalAppContextSwitches.Common.cs">
+      <Link>Common\System\LocalAppContextSwitches.Common.cs</Link>
+    </Compile>
     <Compile Include="System\Runtime\Serialization\MemberHolder.cs" />
     <Compile Include="System\Runtime\Serialization\ObjectIDGenerator.cs" />
     <Compile Include="System\Runtime\Serialization\ObjectManager.cs" />
@@ -49,6 +62,8 @@
     <Compile Include="System\Runtime\Serialization\Formatters\Binary\Converter.cs" />
     <Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryEnums.cs" />
     <Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatter.cs" />
+    <Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatter.Core.cs" Condition="'$(TargetsBrowser)' != 'true'" />
+    <Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatter.PlatformNotSupported.cs" Condition="'$(TargetsBrowser)' == 'true'" />
     <Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryFormatterWriter.cs" />
     <Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryObjectInfo.cs" />
     <Compile Include="System\Runtime\Serialization\Formatters\Binary\BinaryObjectReader.cs" />
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 (file)
index 0000000..9183902
--- /dev/null
@@ -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 (file)
index 0000000..0a8bdd5
--- /dev/null
@@ -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);
+    }
+}
index 5ca47d5..59d2d51 100644 (file)
@@ -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<Type, TypeInformation> s_typeNameCache = new ConcurrentDictionary<Type, TypeInformation>();
 
@@ -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 =>
             {
index 332db03..cb1d8f4 100644 (file)
@@ -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)
             {
index 2bde4e2..8ecc282 100644 (file)
@@ -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 (file)
index 0000000..eead42d
--- /dev/null
@@ -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);
+        }
+    }
+}
index bdcf0e4..8b5d1ab 100644 (file)
@@ -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 (file)
index 0000000..3311907
--- /dev/null
@@ -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<PlatformNotSupportedException>(() => 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<PlatformNotSupportedException>(() => 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<NotSupportedException>(() => 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<NotSupportedException>(() => bf.Deserialize(ms));
+                Assert.Contains(MoreInfoUrl, ex.Message, StringComparison.Ordinal); // error message should link to the more info URL
+            }, options).Dispose();
+        }
+    }
+}
index 91201a4..daf11b1 100644 (file)
@@ -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();
index f0d3e9a..1d10621 100644 (file)
@@ -12,6 +12,7 @@ using Xunit;
 
 namespace System.Runtime.Serialization.Formatters.Tests
 {
+    [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsBinaryFormatterSupported))]
     public static class SerializationGuardTests
     {
         [Fact]
index ff7628e..635709d 100644 (file)
@@ -7,6 +7,7 @@
   <ItemGroup>
     <Compile Include="BinaryFormatterTestData.cs" />
     <Compile Include="BinaryFormatterTests.cs" />
+    <Compile Include="DisableBitTests.cs" />
     <Compile Include="EqualityExtensions.cs" />
     <Compile Include="OptionalFieldAttributeTests.cs" />
     <Compile Include="FormatterConverterTests.cs" />
index 47f52e9..57a4d5a 100644 (file)
@@ -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())
index 6f92853..9f140c6 100644 (file)
@@ -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())
index f0304dd..4ee6414 100644 (file)
@@ -9,5 +9,8 @@
     <type fullname="System.Runtime.CompilerServices.RuntimeFeature">
       <method signature="System.Boolean get_IsDynamicCodeCompiled()" body="stub" value="false" />
     </type>
+    <type fullname="System.Resources.ResourceReader">
+      <method signature="System.Boolean InitializeBinaryFormatter()" body="stub" value="false" />
+    </type>
   </assembly>
 </linker>