diff options
Diffstat (limited to 'src/mscorlib/src/System/Security/SecurityContext.cs')
-rw-r--r-- | src/mscorlib/src/System/Security/SecurityContext.cs | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Security/SecurityContext.cs b/src/mscorlib/src/System/Security/SecurityContext.cs new file mode 100644 index 0000000000..e422a312df --- /dev/null +++ b/src/mscorlib/src/System/Security/SecurityContext.cs @@ -0,0 +1,702 @@ +// 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. +/*============================================================ +** +** +** +** +** +** Purpose: Capture security context for a thread +** +** +===========================================================*/ +namespace System.Security +{ + using Microsoft.Win32; + using Microsoft.Win32.SafeHandles; + using System.Threading; + using System.Runtime.Remoting; +#if FEATURE_IMPERSONATION + using System.Security.Principal; +#endif + using System.Collections; + using System.Runtime.Serialization; + using System.Security.Permissions; + using System.Runtime.InteropServices; + using System.Runtime.CompilerServices; +#if FEATURE_CORRUPTING_EXCEPTIONS + using System.Runtime.ExceptionServices; +#endif // FEATURE_CORRUPTING_EXCEPTIONS + using System.Runtime.ConstrainedExecution; + using System.Runtime.Versioning; + using System.Diagnostics.Contracts; + + // This enum must be kept in sync with the SecurityContextSource enum in the VM + public enum SecurityContextSource + { + CurrentAppDomain = 0, + CurrentAssembly + } + + internal enum SecurityContextDisableFlow + { + Nothing = 0, + WI = 0x1, + All = 0x3FFF + } + +#if FEATURE_IMPERSONATION + internal enum WindowsImpersonationFlowMode { + IMP_FASTFLOW = 0, + IMP_NOFLOW = 1, + IMP_ALWAYSFLOW = 2, + IMP_DEFAULT = IMP_FASTFLOW + } +#endif + +#if FEATURE_COMPRESSEDSTACK + internal struct SecurityContextSwitcher: IDisposable + { + internal SecurityContext.Reader prevSC; // prev SC that we restore on an Undo + internal SecurityContext currSC; //current SC - SetSecurityContext that created the switcher set this on the Thread + internal ExecutionContext currEC; // current ExecutionContext on Thread + internal CompressedStackSwitcher cssw; +#if FEATURE_IMPERSONATION + internal WindowsImpersonationContext wic; +#endif + + [System.Security.SecuritySafeCritical] // overrides public transparent member + public void Dispose() + { + Undo(); + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] +#if FEATURE_CORRUPTING_EXCEPTIONS + [HandleProcessCorruptedStateExceptions] +#endif // FEATURE_CORRUPTING_EXCEPTIONS + internal bool UndoNoThrow() + { + try + { + Undo(); + } + catch + { + return false; + } + return true; + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] +#if FEATURE_CORRUPTING_EXCEPTIONS + [HandleProcessCorruptedStateExceptions] +#endif // FEATURE_CORRUPTING_EXCEPTIONS + public void Undo() + { + if (currSC == null) + { + return; // mutiple Undo()s called on this switcher object + } + + if (currEC != null) + { + Contract.Assert(currEC == Thread.CurrentThread.GetMutableExecutionContext(), "SecurityContextSwitcher used from another thread"); + Contract.Assert(currSC == currEC.SecurityContext, "SecurityContextSwitcher context mismatch"); + + // restore the saved security context + currEC.SecurityContext = prevSC.DangerousGetRawSecurityContext(); + } + else + { + // caller must have already restored the ExecutionContext + Contract.Assert(Thread.CurrentThread.GetExecutionContextReader().SecurityContext.IsSame(prevSC)); + } + + currSC = null; // this will prevent the switcher object being used again + + bool bNoException = true; +#if FEATURE_IMPERSONATION + try + { + if (wic != null) + bNoException &= wic.UndoNoThrow(); + } + catch + { + // Failfast since we can't continue safely... + bNoException &= cssw.UndoNoThrow(); + System.Environment.FailFast(Environment.GetResourceString("ExecutionContext_UndoFailed")); + + } +#endif + bNoException &= cssw.UndoNoThrow(); + + + if (!bNoException) + { + // Failfast since we can't continue safely... + System.Environment.FailFast(Environment.GetResourceString("ExecutionContext_UndoFailed")); + } + + } + } + + + public sealed class SecurityContext : IDisposable + { +#if FEATURE_IMPERSONATION + // Note that only one of the following variables will be true. The way we set up the flow mode in the g_pConfig guarantees this. + static bool _LegacyImpersonationPolicy = (GetImpersonationFlowMode() == WindowsImpersonationFlowMode.IMP_NOFLOW); + static bool _alwaysFlowImpersonationPolicy = (GetImpersonationFlowMode() == WindowsImpersonationFlowMode.IMP_ALWAYSFLOW); +#endif + /*========================================================================= + ** Data accessed from managed code that needs to be defined in + ** SecurityContextObject to maintain alignment between the two classes. + ** DON'T CHANGE THESE UNLESS YOU MODIFY SecurityContextObject in vm\object.h + =========================================================================*/ + + private ExecutionContext _executionContext; +#if FEATURE_IMPERSONATION + private volatile WindowsIdentity _windowsIdentity; +#endif + private volatile CompressedStack _compressedStack; + static private volatile SecurityContext _fullTrustSC; + + internal volatile bool isNewCapture = false; + internal volatile SecurityContextDisableFlow _disableFlow = SecurityContextDisableFlow.Nothing; + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal SecurityContext() + { + } + + internal struct Reader + { + SecurityContext m_sc; + + public Reader(SecurityContext sc) { m_sc = sc; } + + public SecurityContext DangerousGetRawSecurityContext() { return m_sc; } + + public bool IsNull { get { return m_sc == null; } } + public bool IsSame(SecurityContext sc) { return m_sc == sc; } + public bool IsSame(SecurityContext.Reader sc) { return m_sc == sc.m_sc; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsFlowSuppressed(SecurityContextDisableFlow flags) + { + return (m_sc == null) ? false : ((m_sc._disableFlow & flags) == flags); + } + + public CompressedStack CompressedStack { get { return IsNull ? null : m_sc.CompressedStack; } } + + public WindowsIdentity WindowsIdentity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return IsNull ? null : m_sc.WindowsIdentity; } + } + } + + + static internal SecurityContext FullTrustSecurityContext + { + [System.Security.SecurityCritical] // auto-generated + get + { + if (_fullTrustSC == null) + _fullTrustSC = CreateFullTrustSecurityContext(); + return _fullTrustSC; + } + } + + // link the security context to an ExecutionContext + internal ExecutionContext ExecutionContext + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + set + { + _executionContext = value; + } + } + +#if FEATURE_IMPERSONATION + + + internal WindowsIdentity WindowsIdentity + { + get + { + return _windowsIdentity; + } + set + { + // Note, we do not dispose of the existing windows identity, since some code such as remoting + // relies on reusing that identity. If you are not going to reuse the existing identity, then + // you should dispose of the existing identity before resetting it. + _windowsIdentity = value; + } + } +#endif // FEATURE_IMPERSONATION + + + internal CompressedStack CompressedStack + { + get + { + return _compressedStack; + } + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + set + { + _compressedStack = value; + } + } + + public void Dispose() + { +#if FEATURE_IMPERSONATION + if (_windowsIdentity != null) + _windowsIdentity.Dispose(); +#endif // FEATURE_IMPERSONATION + } + + [System.Security.SecurityCritical] // auto-generated_required + public static AsyncFlowControl SuppressFlow() + { + return SuppressFlow(SecurityContextDisableFlow.All); + } + + [System.Security.SecurityCritical] // auto-generated_required + public static AsyncFlowControl SuppressFlowWindowsIdentity() + { + return SuppressFlow(SecurityContextDisableFlow.WI); + } + + [SecurityCritical] + internal static AsyncFlowControl SuppressFlow(SecurityContextDisableFlow flags) + { + if (IsFlowSuppressed(flags)) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotSupressFlowMultipleTimes")); + } + + ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); + if (ec.SecurityContext == null) + ec.SecurityContext = new SecurityContext(); + AsyncFlowControl afc = new AsyncFlowControl(); + afc.Setup(flags); + return afc; + } + + [SecuritySafeCritical] + public static void RestoreFlow() + { + SecurityContext sc = Thread.CurrentThread.GetMutableExecutionContext().SecurityContext; + if (sc == null || sc._disableFlow == SecurityContextDisableFlow.Nothing) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotRestoreUnsupressedFlow")); + } + sc._disableFlow = SecurityContextDisableFlow.Nothing; + } + + public static bool IsFlowSuppressed() + { + return SecurityContext.IsFlowSuppressed(SecurityContextDisableFlow.All); + } +#if FEATURE_IMPERSONATION + public static bool IsWindowsIdentityFlowSuppressed() + { + return (_LegacyImpersonationPolicy|| SecurityContext.IsFlowSuppressed(SecurityContextDisableFlow.WI)); + } +#endif + [SecuritySafeCritical] + internal static bool IsFlowSuppressed(SecurityContextDisableFlow flags) + { + return Thread.CurrentThread.GetExecutionContextReader().SecurityContext.IsFlowSuppressed(flags); + } + + // This method is special from a security perspective - the VM will not allow a stack walk to + // continue past the call to SecurityContext.Run. If you change the signature to this method, or + // provide an alternate way to do a SecurityContext.Run make sure to update + // SecurityStackWalk::IsSpecialRunFrame in the VM to search for the new method. + [System.Security.SecurityCritical] // auto-generated_required + [DynamicSecurityMethodAttribute()] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static void Run(SecurityContext securityContext, ContextCallback callback, Object state) + { + if (securityContext == null ) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullContext")); + } + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMe; + + if (!securityContext.isNewCapture) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NotNewCaptureContext")); + } + + securityContext.isNewCapture = false; + + ExecutionContext.Reader ec = Thread.CurrentThread.GetExecutionContextReader(); + + // Optimization: do the callback directly if both the current and target contexts are equal to the + // default full-trust security context + if ( SecurityContext.CurrentlyInDefaultFTSecurityContext(ec) + && securityContext.IsDefaultFTSecurityContext()) + { + callback(state); + + if (GetCurrentWI(Thread.CurrentThread.GetExecutionContextReader()) != null) + { + // If we enter here it means the callback did an impersonation + // that we need to revert. + // We don't need to revert any other security state since it is stack-based + // and automatically goes away when the callback returns. + WindowsIdentity.SafeRevertToSelf(ref stackMark); + // Ensure we have reverted to the state we entered in. + Contract.Assert(GetCurrentWI(Thread.CurrentThread.GetExecutionContextReader()) == null); + } + } + else + { + RunInternal(securityContext, callback, state); + } + + } + [System.Security.SecurityCritical] // auto-generated + internal static void RunInternal(SecurityContext securityContext, ContextCallback callBack, Object state) + { + if (cleanupCode == null) + { + tryCode = new RuntimeHelpers.TryCode(runTryCode); + cleanupCode = new RuntimeHelpers.CleanupCode(runFinallyCode); + } + SecurityContextRunData runData = new SecurityContextRunData(securityContext, callBack, state); + RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(tryCode, cleanupCode, runData); + + } + + internal class SecurityContextRunData + { + internal SecurityContext sc; + internal ContextCallback callBack; + internal Object state; + internal SecurityContextSwitcher scsw; + internal SecurityContextRunData(SecurityContext securityContext, ContextCallback cb, Object state) + { + this.sc = securityContext; + this.callBack = cb; + this.state = state; + this.scsw = new SecurityContextSwitcher(); + } + } + + [System.Security.SecurityCritical] // auto-generated + static internal void runTryCode(Object userData) + { + SecurityContextRunData rData = (SecurityContextRunData) userData; + rData.scsw = SetSecurityContext(rData.sc, Thread.CurrentThread.GetExecutionContextReader().SecurityContext, modifyCurrentExecutionContext: true); + rData.callBack(rData.state); + + } + + [System.Security.SecurityCritical] // auto-generated + [PrePrepareMethod] + static internal void runFinallyCode(Object userData, bool exceptionThrown) + { + SecurityContextRunData rData = (SecurityContextRunData) userData; + rData.scsw.Undo(); + } + + static volatile internal RuntimeHelpers.TryCode tryCode; + static volatile internal RuntimeHelpers.CleanupCode cleanupCode; + + + + // Internal API that gets called from public SetSecurityContext and from SetExecutionContext + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + [System.Security.SecurityCritical] // auto-generated + [DynamicSecurityMethodAttribute()] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + internal static SecurityContextSwitcher SetSecurityContext(SecurityContext sc, SecurityContext.Reader prevSecurityContext, bool modifyCurrentExecutionContext) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return SetSecurityContext(sc, prevSecurityContext, modifyCurrentExecutionContext, ref stackMark); + } + + [System.Security.SecurityCritical] // auto-generated +#if FEATURE_CORRUPTING_EXCEPTIONS + [HandleProcessCorruptedStateExceptions] +#endif // FEATURE_CORRUPTING_EXCEPTIONS + internal static SecurityContextSwitcher SetSecurityContext(SecurityContext sc, SecurityContext.Reader prevSecurityContext, bool modifyCurrentExecutionContext, ref StackCrawlMark stackMark) + { + // Save the flow state at capture and reset it in the SC. + SecurityContextDisableFlow _capturedFlowState = sc._disableFlow; + sc._disableFlow = SecurityContextDisableFlow.Nothing; + + //Set up the switcher object + SecurityContextSwitcher scsw = new SecurityContextSwitcher(); + scsw.currSC = sc; + scsw.prevSC = prevSecurityContext; + + if (modifyCurrentExecutionContext) + { + // save the current Execution Context + ExecutionContext currEC = Thread.CurrentThread.GetMutableExecutionContext(); + scsw.currEC = currEC; + currEC.SecurityContext = sc; + } + + if (sc != null) + { + RuntimeHelpers.PrepareConstrainedRegions(); + try + { +#if FEATURE_IMPERSONATION + scsw.wic = null; + if (!_LegacyImpersonationPolicy) + { + if (sc.WindowsIdentity != null) + { + scsw.wic = sc.WindowsIdentity.Impersonate(ref stackMark); + } + else if ( ((_capturedFlowState & SecurityContextDisableFlow.WI) == 0) + && prevSecurityContext.WindowsIdentity != null) + { + // revert impersonation if there was no WI flow supression at capture and we're currently impersonating + scsw.wic = WindowsIdentity.SafeRevertToSelf(ref stackMark); + } + } +#endif + scsw.cssw = CompressedStack.SetCompressedStack(sc.CompressedStack, prevSecurityContext.CompressedStack); + } + catch + { + scsw.UndoNoThrow(); + throw; + } + } + return scsw; + } + + /// <internalonly/> + [System.Security.SecuritySafeCritical] // auto-generated + public SecurityContext CreateCopy() + { + if (!isNewCapture) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NotNewCaptureContext")); + } + + SecurityContext sc = new SecurityContext(); + sc.isNewCapture = true; + sc._disableFlow = _disableFlow; + +#if FEATURE_IMPERSONATION + if (WindowsIdentity != null) + sc._windowsIdentity = new WindowsIdentity(WindowsIdentity.AccessToken); +#endif //FEATURE_IMPERSONATION + + if (_compressedStack != null) + sc._compressedStack = _compressedStack.CreateCopy(); + + return sc; + } + + /// <internalonly/> + [System.Security.SecuritySafeCritical] // auto-generated + internal SecurityContext CreateMutableCopy() + { + Contract.Assert(!this.isNewCapture); + + SecurityContext sc = new SecurityContext(); + sc._disableFlow = this._disableFlow; + +#if FEATURE_IMPERSONATION + if (this.WindowsIdentity != null) + sc._windowsIdentity = new WindowsIdentity(this.WindowsIdentity.AccessToken); +#endif //FEATURE_IMPERSONATION + + if (this._compressedStack != null) + sc._compressedStack = this._compressedStack.CreateCopy(); + + return sc; + } + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static SecurityContext Capture( ) + { + // check to see if Flow is suppressed + if (IsFlowSuppressed()) + return null; + + StackCrawlMark stackMark= StackCrawlMark.LookForMyCaller; + SecurityContext sc = SecurityContext.Capture(Thread.CurrentThread.GetExecutionContextReader(), ref stackMark); + if (sc == null) + sc = CreateFullTrustSecurityContext(); + return sc; + } + + // create a clone from a non-existing SecurityContext + [System.Security.SecurityCritical] // auto-generated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static internal SecurityContext Capture(ExecutionContext.Reader currThreadEC, ref StackCrawlMark stackMark) + { + // check to see if Flow is suppressed + if (currThreadEC.SecurityContext.IsFlowSuppressed(SecurityContextDisableFlow.All)) + return null; + + // If we're in FT right now, return null + if (CurrentlyInDefaultFTSecurityContext(currThreadEC)) + return null; + + return CaptureCore(currThreadEC, ref stackMark); + } + + [System.Security.SecurityCritical] // auto-generated + static private SecurityContext CaptureCore(ExecutionContext.Reader currThreadEC, ref StackCrawlMark stackMark) + { + SecurityContext sc = new SecurityContext(); + sc.isNewCapture = true; + +#if FEATURE_IMPERSONATION + // Force create WindowsIdentity + if (!IsWindowsIdentityFlowSuppressed()) + { + WindowsIdentity currentIdentity = GetCurrentWI(currThreadEC); + if (currentIdentity != null) + sc._windowsIdentity = new WindowsIdentity(currentIdentity.AccessToken); + } + else + { + sc._disableFlow = SecurityContextDisableFlow.WI; + } +#endif // FEATURE_IMPERSONATION + + // Force create CompressedStack + sc.CompressedStack = CompressedStack.GetCompressedStack(ref stackMark); + return sc; + } + [System.Security.SecurityCritical] // auto-generated + static internal SecurityContext CreateFullTrustSecurityContext() + { + SecurityContext sc = new SecurityContext(); + sc.isNewCapture = true; + +#if FEATURE_IMPERSONATION + if (IsWindowsIdentityFlowSuppressed()) + { + sc._disableFlow = SecurityContextDisableFlow.WI; + } +#endif // FEATURE_IMPERSONATION + + + // Force create CompressedStack + sc.CompressedStack = new CompressedStack(null); + return sc; + } + +#if FEATURE_IMPERSONATION + + static internal bool AlwaysFlowImpersonationPolicy { get { return _alwaysFlowImpersonationPolicy; } } + + // Check to see if we have a WI on the thread and return if we do + [System.Security.SecurityCritical] // auto-generated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static internal WindowsIdentity GetCurrentWI(ExecutionContext.Reader threadEC) + { + return GetCurrentWI(threadEC, _alwaysFlowImpersonationPolicy); + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static internal WindowsIdentity GetCurrentWI(ExecutionContext.Reader threadEC, bool cachedAlwaysFlowImpersonationPolicy) + { + Contract.Assert(cachedAlwaysFlowImpersonationPolicy == _alwaysFlowImpersonationPolicy); + if (cachedAlwaysFlowImpersonationPolicy) + { + // Examine the threadtoken at the cost of a kernel call if the user has set the IMP_ALWAYSFLOW mode + return WindowsIdentity.GetCurrentInternal(TokenAccessLevels.MaximumAllowed, true); + } + + return threadEC.SecurityContext.WindowsIdentity; + } + + [System.Security.SecurityCritical] + static internal void RestoreCurrentWI(ExecutionContext.Reader currentEC, ExecutionContext.Reader prevEC, WindowsIdentity targetWI, bool cachedAlwaysFlowImpersonationPolicy) + { + Contract.Assert(currentEC.IsSame(Thread.CurrentThread.GetExecutionContextReader())); + Contract.Assert(cachedAlwaysFlowImpersonationPolicy == _alwaysFlowImpersonationPolicy); + + // NOTE: cachedAlwaysFlowImpersonationPolicy is a perf optimization to avoid always having to access a static variable here. + if (cachedAlwaysFlowImpersonationPolicy || prevEC.SecurityContext.WindowsIdentity != targetWI) + { + // + // Either we're always flowing, or the target WI was obtained from the current EC in the first place. + // + Contract.Assert(_alwaysFlowImpersonationPolicy || currentEC.SecurityContext.WindowsIdentity == targetWI); + + RestoreCurrentWIInternal(targetWI); + } + } + + [System.Security.SecurityCritical] + static private void RestoreCurrentWIInternal(WindowsIdentity targetWI) + { + int hr = Win32.RevertToSelf(); + if (hr < 0) + Environment.FailFast(Win32Native.GetMessage(hr)); + + if (targetWI != null) + { + SafeAccessTokenHandle tokenHandle = targetWI.AccessToken; + if (tokenHandle != null && !tokenHandle.IsInvalid) + { + hr = Win32.ImpersonateLoggedOnUser(tokenHandle); + if (hr < 0) + Environment.FailFast(Win32Native.GetMessage(hr)); + } + } + } + + [System.Security.SecurityCritical] // auto-generated + internal bool IsDefaultFTSecurityContext() + { + return (WindowsIdentity == null && (CompressedStack == null || CompressedStack.CompressedStackHandle == null)); + } + [System.Security.SecurityCritical] // auto-generated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static internal bool CurrentlyInDefaultFTSecurityContext(ExecutionContext.Reader threadEC) + { + return (IsDefaultThreadSecurityInfo() && GetCurrentWI(threadEC) == null); + } +#else + + internal bool IsDefaultFTSecurityContext() + { + return (CompressedStack == null || CompressedStack.CompressedStackHandle == null); + } + static internal bool CurrentlyInDefaultFTSecurityContext(ExecutionContext threadEC) + { + return (IsDefaultThreadSecurityInfo()); + } +#endif +#if FEATURE_IMPERSONATION + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal extern static WindowsImpersonationFlowMode GetImpersonationFlowMode(); +#endif + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal extern static bool IsDefaultThreadSecurityInfo(); + + } +#endif // FEATURE_COMPRESSEDSTACK +} |