<data name="InvalidOperation_CannotRegisterSecondResolver" xml:space="preserve">
<value>A resolver is already set for the assembly.</value>
</data>
- <data name="InvalidOperation_CannotRemoveLastFromEmptyCollection" xml:space="preserve">
+ <data name="InvalidOperation_CannotRemoveLastFromEmptyCollection" xml:space="preserve">
<value>Cannot remove the last element from an empty collection.</value>
</data>
<data name="InvalidOperation_CannotRestoreUnsupressedFlow" xml:space="preserve">
<data name="IndexOutOfRange_ArrayWithOffset" xml:space="preserve">
<value>ArrayWithOffset: offset exceeds array size.</value>
</data>
-</root>
+ <data name="Serialization_DangerousDeserialization" xml:space="preserve">
+ <value>An action was attempted during deserialization that could lead to a security vulnerability. The action has been aborted.</value>
+ </data>
+ <data name="Serialization_DangerousDeserialization_Switch" xml:space="preserve">
+ <value>An action was attempted during deserialization that could lead to a security vulnerability. The action has been aborted. To allow the action, set the '{0}' AppContext switch to true.</value>
+ </data>
+</root>
\ No newline at end of file
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\X86\Enums.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Loader\AssemblyLoadContext.cs" Condition="'$(TargetsCoreRT)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Remoting\ObjectHandle.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\DeserializationBlockedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\DeserializationToken.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\DeserializationTracker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\IDeserializationCallback.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\IFormatterConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\IObjectReference.cs" />
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
/// <summary>Whether the file stream's handle has been exposed.</summary>
private bool _exposedHandle;
+ /// <summary>Caches whether Serialization Guard has been disabled for file writes</summary>
+ private static int s_cachedSerializationSwitch = 0;
+
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access)
: this(handle, access, true, DefaultBufferSize, false)
if ((options & FileOptions.Asynchronous) != 0)
_useAsyncIO = true;
+ if ((access & FileAccess.Write) == FileAccess.Write)
+ {
+ SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch);
+ }
+
_fileHandle = OpenHandle(mode, share, options);
try
private static bool GetCachedSwitchValueInternal(string switchName, ref int cachedSwitchValue)
{
bool isSwitchEnabled;
- AppContext.TryGetSwitch(switchName, out isSwitchEnabled);
+
+ bool hasSwitch = AppContext.TryGetSwitch(switchName, out isSwitchEnabled);
+ if (!hasSwitch)
+ {
+ isSwitchEnabled = GetSwitchDefaultValue(switchName);
+ }
AppContext.TryGetSwitch(@"TestSwitch.LocalAppContext.DisableCaching", out bool disableCaching);
if (!disableCaching)
return isSwitchEnabled;
}
+
+ // Provides default values for switches if they're not always false by default
+ private static bool GetSwitchDefaultValue(string switchName)
+ {
+ if (switchName == "Switch.System.Runtime.Serialization.SerializationGuard")
+ {
+ return true;
+ }
+
+ return false;
+ }
}
}
return GetCachedSwitchValue("Switch.System.Diagnostics.EventSource.PreserveEventListnerObjectIdentity", ref s_preserveEventListnerObjectIdentity);
}
}
+
+ private static int s_serializationGuard;
+ public static bool SerializationGuard
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return GetCachedSwitchValue("Switch.System.Runtime.Serialization.SerializationGuard", ref s_serializationGuard);
+ }
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Runtime.Serialization
+{
+ // Thrown when a dangerous action would be performed during deserialization
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class DeserializationBlockedException : SerializationException
+ {
+ // Creates a new DeserializationBlockedException with its message
+ // string set to a default message.
+ public DeserializationBlockedException()
+ : base(SR.Serialization_DangerousDeserialization)
+ {
+ HResult = HResults.COR_E_SERIALIZATION;
+ }
+
+ // Creates a new DeserializationBlockedException with a message indicating an opt-out switch
+ // for a particular part of SerializationGuard
+ public DeserializationBlockedException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_SERIALIZATION;
+ }
+
+ public DeserializationBlockedException(Exception innerException)
+ : base(SR.Serialization_DangerousDeserialization, innerException)
+ {
+ HResult = HResults.COR_E_SERIALIZATION;
+ }
+
+ private DeserializationBlockedException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace System.Runtime.Serialization
+{
+ // Tracks whether deserialization is currently in progress
+ public readonly struct DeserializationToken : IDisposable
+ {
+ private readonly DeserializationTracker _tracker;
+
+ internal DeserializationToken(DeserializationTracker tracker)
+ {
+ _tracker = tracker;
+ }
+
+ // If this token owned the DeserializationTracker, turn off DeserializationInProgress tracking
+ public void Dispose()
+ {
+ if (_tracker != null && _tracker.DeserializationInProgress)
+ {
+ lock (_tracker)
+ {
+ if (_tracker.DeserializationInProgress)
+ {
+ _tracker.DeserializationInProgress = false;
+ SerializationInfo.AsyncDeserializationInProgress.Value = false;
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime.Serialization
+{
+ // Tracks whether deserialization is currently in progress
+ internal sealed class DeserializationTracker
+ {
+ // True if the thread this tracker applies to is currently deserializing
+ // potentially untrusted data
+ internal bool DeserializationInProgress { get; set; }
+ }
+}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Security;
+using System.Threading;
namespace System.Runtime.Serialization
{
private string _rootTypeName;
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()
+ {
+ if (t_deserializationTracker == null)
+ {
+ t_deserializationTracker = new DeserializationTracker();
+ }
+
+ return t_deserializationTracker;
+ }
+#endif // !CORECLR
+
+ // Returns true if deserialization is currently in progress
+ public static bool DeserializationInProgress
+ {
+ [DynamicSecurityMethod] // Methods containing StackCrawlMark local var must be marked DynamicSecurityMethod
+ 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 DeserializationBlockedException if dangerous deserialization is currently
+ // in progress
+ public static void ThrowIfDeserializationInProgress()
+ {
+ if (DeserializationInProgress)
+ {
+ throw new DeserializationBlockedException();
+ }
+ }
+
+ // 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)
+ {
+ bool isEnabled = false;
+ if (AppContext.TryGetSwitch(SwitchPrefix + switchSuffix, out isEnabled) && isEnabled)
+ {
+ cachedValue = 1;
+ }
+ else
+ {
+ cachedValue = -1;
+ }
+ }
+
+ if (cachedValue == 1)
+ {
+ return;
+ }
+ else if (cachedValue == -1)
+ {
+ if (DeserializationInProgress)
+ {
+ throw new DeserializationBlockedException(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.
+ [DynamicSecurityMethod] // Methods containing StackCrawlMark local var must be marked DynamicSecurityMethod
+ 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)
private static object s_syncRootLoadFrom = new object();
private static List<string> s_LoadFromAssemblyList = new List<string>();
private static object s_syncLoadFromAssemblyList = new object();
+ private static int s_cachedSerializationSwitch = 0;
private static Assembly LoadFromResolveHandler(object sender, ResolveEventArgs args)
{
throw new NotSupportedException(SR.Format(SR.NotSupported_AppX, "Assembly.Load(byte[], ...)"));
#endif
+ SerializationInfo.ThrowIfDeserializationInProgress("AllowAssembliesFromByteArrays",
+ ref s_cachedSerializationSwitch);
+
AssemblyLoadContext alc = new IndividualAssemblyLoadContext();
MemoryStream assemblyStream = new MemoryStream(rawAssembly);
MemoryStream symbolStream = (rawSymbolStore != null) ? new MemoryStream(rawSymbolStore) : null;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
namespace System.Threading
{
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern void InformThreadNameChange(ThreadHandle t, string name, int len);
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern DeserializationTracker GetThreadDeserializationTracker(ref StackCrawlMark stackMark);
+
/// <summary>Returns true if the thread has been started and is not dead.</summary>
public extern bool IsAlive
{
return ret;
}
+FCIMPL1(Object*, ThreadNative::GetThreadDeserializationTracker, StackCrawlMark* stackMark)
+{
+ FCALL_CONTRACT;
+ OBJECTREF refRetVal = NULL;
+ HELPER_METHOD_FRAME_BEGIN_RET_1(refRetVal)
+
+ // To avoid reflection trying to bypass deserialization tracking, check the caller
+ // and only allow SerializationInfo to call into this method.
+ MethodTable* pCallerMT = SystemDomain::GetCallersType(stackMark);
+ if (pCallerMT != MscorlibBinder::GetClass(CLASS__SERIALIZATION_INFO))
+ {
+ COMPlusThrowArgumentException(W("stackMark"), NULL);
+ }
+
+ Thread* pThread = GetThread();
+
+ refRetVal = ObjectFromHandle(pThread->GetOrCreateDeserializationTracker());
+
+ HELPER_METHOD_FRAME_END();
+
+ return OBJECTREFToObject(refRetVal);
+}
+FCIMPLEND
FCIMPL0(INT32, ThreadNative::GetCurrentProcessorNumber)
{
static FCDECL1(void, DisableComObjectEagerCleanup, ThreadBaseObject* pThis);
#endif //FEATURE_COMINTEROP
static FCDECL1(FC_BOOL_RET,IsThreadpoolThread, ThreadBaseObject* thread);
+ static FCDECL1(Object*, GetThreadDeserializationTracker, StackCrawlMark* stackMark);
static FCDECL0(INT32, GetCurrentProcessorNumber);
FCFuncElement("Join", ThreadNative::Join)
QCFuncElement("GetOptimalMaxSpinWaitsPerSpinIterationInternal", ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration)
FCFuncElement("GetCurrentProcessorNumber", ThreadNative::GetCurrentProcessorNumber)
+ FCFuncElement("GetThreadDeserializationTracker", ThreadNative::GetThreadDeserializationTracker)
FCFuncEnd()
FCFuncStart(gThreadPoolFuncs)
DEFINE_CLASS(IDESERIALIZATIONCB, Serialization, IDeserializationCallback)
DEFINE_CLASS(STREAMING_CONTEXT, Serialization, StreamingContext)
DEFINE_CLASS(SERIALIZATION_INFO, Serialization, SerializationInfo)
-
+DEFINE_CLASS(DESERIALIZATION_TRACKER, Serialization, DeserializationTracker)
DEFINE_CLASS(IENUMERATOR, Collections, IEnumerator)
memset(&m_activityId, 0, sizeof(m_activityId));
#endif // FEATURE_PERFTRACING
m_HijackReturnKind = RT_Illegal;
+ m_DeserializationTracker = NULL;
}
//--------------------------------------------------------------------
// Destroy any handles that we're using to hold onto exception objects
SafeSetThrowables(NULL);
+ if (m_DeserializationTracker != NULL)
+ {
+ DestroyGlobalStrongHandle(m_DeserializationTracker);
+ }
+
DestroyShortWeakHandle(m_ExposedObject);
DestroyStrongHandle(m_StrongHndToExposedObject);
}
return ullCurrentUsage - ullPreviousUsage;
}
#endif // FEATURE_APPDOMAIN_RESOURCE_MONITORING
+
+OBJECTHANDLE Thread::GetOrCreateDeserializationTracker()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+#if !defined (DACCESS_COMPILE)
+ if (m_DeserializationTracker != NULL)
+ {
+ return m_DeserializationTracker;
+ }
+
+ _ASSERTE(this == GetThread());
+
+ MethodTable* pMT = MscorlibBinder::GetClass(CLASS__DESERIALIZATION_TRACKER);
+ m_DeserializationTracker = CreateGlobalStrongHandle(AllocateObject(pMT));
+
+ _ASSERTE(m_DeserializationTracker != NULL);
+#endif // !defined (DACCESS_COMPILE)
+
+ return m_DeserializationTracker;
+}
m_HijackReturnKind = returnKind;
}
#endif // FEATURE_HIJACK
+
+public:
+ OBJECTHANDLE GetOrCreateDeserializationTracker();
+
+private:
+ OBJECTHANDLE m_DeserializationTracker;
};
// End of class Thread