// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved using System; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.ConstrainedExecution; using System.Security; using System.Threading; using Microsoft.Win32.SafeHandles; namespace Microsoft.Samples.DynamicAccessControl.Utility { using HANDLE = System.IntPtr; using Win32Exception = System.ComponentModel.Win32Exception; using PrivilegeNotHeldException = System.Security.AccessControl.PrivilegeNotHeldException; using TokenAccessLevels = System.Security.Principal.TokenAccessLevels; using TokenImpersonationLevel = System.Security.Principal.TokenImpersonationLevel; public delegate void PrivilegedCallback(object state); // // Adopted from http://msdn.microsoft.com/en-us/magazine/cc163823.aspx // internal sealed class TokenPrivilege : IDisposable { #region Private static members private static LocalDataStoreSlot tlsSlot = Thread.AllocateDataSlot(); private static HybridDictionary privileges = new HybridDictionary(); private static HybridDictionary luids = new HybridDictionary(); private static ReaderWriterLock privilegeLock = new ReaderWriterLock(); #endregion #region Private members private bool needToRevert = false; private bool initialState = false; private bool stateWasChanged = false; private Win32.LUID luid; private readonly Thread currentThread = Thread.CurrentThread; private TlsContents tlsContents = null; private bool disposed = false; #endregion #region Privilege names public const string CreateToken = "SeCreateTokenPrivilege"; public const string AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege"; public const string LockMemory = "SeLockMemoryPrivilege"; public const string IncreaseQuota = "SeIncreaseQuotaPrivilege"; public const string UnsolicitedInput = "SeUnsolicitedInputPrivilege"; public const string MachineAccount = "SeMachineAccountPrivilege"; public const string TrustedComputingBase = "SeTcbPrivilege"; public const string Security = "SeSecurityPrivilege"; public const string TakeOwnership = "SeTakeOwnershipPrivilege"; public const string LoadDriver = "SeLoadDriverPrivilege"; public const string SystemProfile = "SeSystemProfilePrivilege"; public const string SystemTime = "SeSystemtimePrivilege"; public const string ProfileSingleProcess = "SeProfileSingleProcessPrivilege"; public const string IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege"; public const string CreatePageFile = "SeCreatePagefilePrivilege"; public const string CreatePermanent = "SeCreatePermanentPrivilege"; public const string Backup = "SeBackupPrivilege"; public const string Restore = "SeRestorePrivilege"; public const string Shutdown = "SeShutdownPrivilege"; public const string Debug = "SeDebugPrivilege"; public const string Audit = "SeAuditPrivilege"; public const string SystemEnvironment = "SeSystemEnvironmentPrivilege"; public const string ChangeNotify = "SeChangeNotifyPrivilege"; public const string RemoteShutdown = "SeRemoteShutdownPrivilege"; public const string Undock = "SeUndockPrivilege"; public const string SyncAgent = "SeSyncAgentPrivilege"; public const string EnableDelegation = "SeEnableDelegationPrivilege"; public const string ManageVolume = "SeManageVolumePrivilege"; public const string Impersonate = "SeImpersonatePrivilege"; public const string CreateGlobal = "SeCreateGlobalPrivilege"; public const string TrustedCredentialManagerAccess = "SeTrustedCredManAccessPrivilege"; public const string ReserveProcessor = "SeReserveProcessorPrivilege"; #endregion #region LUID caching logic // // This routine is a wrapper around a hashtable containing mappings // of privilege names to luids // [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] private static Win32.LUID LuidFromPrivilege(string privilege) { Win32.LUID luid; luid.LowPart = 0; luid.HighPart = 0; // // Look up the privilege LUID inside the cache // RuntimeHelpers.PrepareConstrainedRegions(); try { privilegeLock.AcquireReaderLock(Timeout.Infinite); if (luids.Contains(privilege)) { luid = (Win32.LUID)luids[privilege]; privilegeLock.ReleaseReaderLock(); } else { privilegeLock.ReleaseReaderLock(); if (!NativeMethods.LookupPrivilegeValue(null, privilege, ref luid)) { int error = Marshal.GetLastWin32Error(); if (error == Win32Error.ERROR_NOT_ENOUGH_MEMORY) { throw new InsufficientMemoryException(); } else if (error == Win32Error.ERROR_ACCESS_DENIED) { throw new UnauthorizedAccessException("Caller does not have the rights to look up " + "privilege local unique identifier"); } else if (error == Win32Error.ERROR_NO_SUCH_PRIVILEGE) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "{0} is not a valid privilege name", privilege), "privilege"); } else { throw new Win32Exception(error); } } privilegeLock.AcquireWriterLock(Timeout.Infinite); } } finally { if (privilegeLock.IsReaderLockHeld) { privilegeLock.ReleaseReaderLock(); } if (privilegeLock.IsWriterLockHeld) { if (!luids.Contains(privilege)) { luids[privilege] = luid; privileges[luid] = privilege; } privilegeLock.ReleaseWriterLock(); } } return luid; } #endregion #region Nested classes private sealed class TlsContents : IDisposable { private int referenceCount = 1; private SafeTokenHandle threadHandle = new SafeTokenHandle(IntPtr.Zero); private bool isImpersonating = false; private static SafeTokenHandle processHandle = new SafeTokenHandle(IntPtr.Zero); private static readonly object syncRoot = new object(); #region Constructor and finalizer public TlsContents() { int error = 0; int cachingError = 0; bool success = true; if (processHandle.IsInvalid) { lock (syncRoot) { if (processHandle.IsInvalid) { if (!NativeMethods.OpenProcessToken(NativeMethods.GetCurrentProcess(), TokenAccessLevels.Duplicate, ref processHandle)) { cachingError = Marshal.GetLastWin32Error(); success = false; } } } } RuntimeHelpers.PrepareConstrainedRegions(); try { // // Open the thread token; if there is no thread token, // copy the process token onto the thread // if (!NativeMethods.OpenThreadToken(NativeMethods.GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, ref this.threadHandle)) { if (success) { error = Marshal.GetLastWin32Error(); if (error != Win32Error.ERROR_NO_TOKEN) { success = false; } if (success) { error = 0; if (!NativeMethods.DuplicateTokenEx(processHandle, TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, IntPtr.Zero, TokenImpersonationLevel.Impersonation, NativeMethods.TokenType.Impersonation, ref this.threadHandle)) { error = Marshal.GetLastWin32Error(); success = false; } } if (success) { if (!NativeMethods.SetThreadToken(HANDLE.Zero, this.threadHandle)) { error = Marshal.GetLastWin32Error(); success = false; } } if (success) { // // This thread is now impersonating; it needs to be reverted to its original state // this.isImpersonating = true; } } else { error = cachingError; } } else { success = true; } } finally { if (!success) { Dispose(); } } switch (error) { case Win32Error.ERROR_NOT_ENOUGH_MEMORY: throw new InsufficientMemoryException(); case Win32Error.ERROR_CANT_OPEN_ANONYMOUS: goto case Win32Error.ERROR_ACCESS_DENIED; case Win32Error.ERROR_ACCESS_DENIED: { throw new UnauthorizedAccessException("The caller does not have the rights to perform the" + " operation"); } default: { if (error != Win32Error.ERROR_SUCCESS) { throw new Win32Exception(error); } break; } } } ~TlsContents() { Dispose(false); } #endregion #region IDisposable implementation public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { if (this.threadHandle != null) { this.threadHandle.Dispose(); this.threadHandle = null; } } if (this.isImpersonating) { NativeMethods.RevertToSelf(); this.isImpersonating = false; } } #endregion #region Reference-counting public void IncrementReferenceCount() { this.referenceCount++; } public int DecrementReferenceCount() { int result = --this.referenceCount; if (result == 0) { Dispose(); } return result; } public int ReferenceCountValue { get { return this.referenceCount; } } #endregion #region Properties public SafeTokenHandle ThreadHandle { get { return this.threadHandle; } } public bool IsImpersonating { get { return this.isImpersonating; } } #endregion } #endregion #region Constructor and Destructor public TokenPrivilege(string privilegeName) { if (privilegeName == null) { throw new ArgumentNullException("privilegeName"); } this.luid = LuidFromPrivilege(privilegeName); } ~TokenPrivilege() { Dispose(false); } #endregion #region Public methods and properties [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public void Enable() { this.ToggleState(true); } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification="Retain code adopted from MSDN Magazine sample as is")] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public void Disable() { this.ToggleState(false); } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public void Revert() { int error = 0; // // All privilege operations must take place on the same thread // if (!this.currentThread.Equals(Thread.CurrentThread)) { throw new InvalidOperationException("Operation must take place on the thread that created the object"); } if (!this.NeedToRevert) { return; } // // This code must be eagerly prepared and non-interruptible. // RuntimeHelpers.PrepareConstrainedRegions(); try { // // The payload is entirely in the finally block // This is how we ensure that the code will not be // interrupted by catastrophic exceptions // } finally { bool success = true; try { // // Only call AdjustTokenPrivileges if we're not going to be reverting to self, // on this Revert, since doing the latter obliterates the thread token anyway // if (this.stateWasChanged && (this.tlsContents.ReferenceCountValue > 1 || !this.tlsContents.IsImpersonating)) { NativeMethods.TOKEN_PRIVILEGE newState = new NativeMethods.TOKEN_PRIVILEGE(); newState.PrivilegeCount = 1; newState.Privilege.Luid = this.luid; newState.Privilege.Attributes = (this.initialState ? NativeMethods.PrivilegeAttribute.Enabled : NativeMethods.PrivilegeAttribute.Disabled); NativeMethods.TOKEN_PRIVILEGE previousState = new NativeMethods.TOKEN_PRIVILEGE(); uint previousSize = 0; if (!NativeMethods.AdjustTokenPrivileges(this.tlsContents.ThreadHandle, false, ref newState, (uint)Marshal.SizeOf(previousState), ref previousState, ref previousSize)) { error = Marshal.GetLastWin32Error(); success = false; } } } finally { if (success) { this.Reset(); } } } if (error == Win32Error.ERROR_NOT_ENOUGH_MEMORY) { throw new InsufficientMemoryException(); } else if (error == Win32Error.ERROR_ACCESS_DENIED) { throw new UnauthorizedAccessException("Caller does not have the permission to change the privilege"); } else if (error != 0) { throw new Win32Exception(error); } } public bool NeedToRevert { get { return this.needToRevert; } } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Retain code adopted from MSDN Magazine sample as is")] public static void RunWithPrivilege(string privilege, bool enabled, PrivilegedCallback callback, object state) { if (callback == null) { throw new ArgumentNullException("callback"); } using(TokenPrivilege p = new TokenPrivilege(privilege)) { RuntimeHelpers.PrepareConstrainedRegions(); try { if (enabled) { p.Enable(); } else { p.Disable(); } callback(state); } finally { p.Revert(); } } } #endregion #region IDisposable implementation public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposed) return; if (disposing) { if (tlsContents != null) { tlsContents.Dispose(); tlsContents = null; } } disposed = true; } #endregion #region Private implementation private void ToggleState(bool enable) { int error = 0; // // All privilege operations must take place on the same thread // if (!this.currentThread.Equals(Thread.CurrentThread)) { throw new InvalidOperationException("Operation must take place on the thread that created the object"); } // // This privilege was already altered and needs to be reverted before it can be altered again // if (this.NeedToRevert) { throw new InvalidOperationException("Must revert the privilege prior to attempting this operation"); } // // Need to make this block of code non-interruptible so that it would preserve // consistency of thread oken state even in the face of catastrophic exceptions // RuntimeHelpers.PrepareConstrainedRegions(); try { // // The payload is entirely in the finally block // This is how we ensure that the code will not be // interrupted by catastrophic exceptions // } finally { try { // // Retrieve TLS state // this.tlsContents = Thread.GetData(tlsSlot) as TlsContents; if (this.tlsContents == null) { this.tlsContents = new TlsContents(); Thread.SetData(tlsSlot, this.tlsContents); } else { this.tlsContents.IncrementReferenceCount(); } NativeMethods.TOKEN_PRIVILEGE newState = new NativeMethods.TOKEN_PRIVILEGE(); newState.PrivilegeCount = 1; newState.Privilege.Luid = this.luid; newState.Privilege.Attributes = (enable ? NativeMethods.PrivilegeAttribute.Enabled : NativeMethods.PrivilegeAttribute.Disabled); NativeMethods.TOKEN_PRIVILEGE previousState = new NativeMethods.TOKEN_PRIVILEGE(); uint previousSize = 0; // // Place the new privilege on the thread token and remember the previous state. // bool fResult = NativeMethods.AdjustTokenPrivileges(this.tlsContents.ThreadHandle, false, ref newState, (uint)Marshal.SizeOf(previousState), ref previousState, ref previousSize); error = Marshal.GetLastWin32Error(); if (fResult && error != Win32Error.ERROR_NOT_ALL_ASSIGNED) { // // This is the initial state that revert will have to go back to // this.initialState = ((previousState.Privilege.Attributes & NativeMethods.PrivilegeAttribute.Enabled) != 0); // // Remember whether state has changed at all // this.stateWasChanged = (this.initialState != enable); // // If we had to impersonate, or if the privilege state changed we'll need to revert // this.needToRevert = this.tlsContents.IsImpersonating || this.stateWasChanged; } } finally { if (!this.needToRevert) { this.Reset(); } } } switch (error) { case Win32Error.ERROR_NOT_ALL_ASSIGNED: throw new PrivilegeNotHeldException( privileges[this.luid] as string); case Win32Error.ERROR_NOT_ENOUGH_MEMORY: throw new InsufficientMemoryException(); case Win32Error.ERROR_CANT_OPEN_ANONYMOUS: goto case Win32Error.ERROR_ACCESS_DENIED; case Win32Error.ERROR_ACCESS_DENIED: { throw new UnauthorizedAccessException("The caller does not have the right to change the " + "privilege"); } default: { if (error != Win32Error.ERROR_SUCCESS) { throw new Win32Exception(error); } break; } } } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] private void Reset() { RuntimeHelpers.PrepareConstrainedRegions(); try { // Payload is in the finally block // as a way to guarantee execution } finally { this.stateWasChanged = false; this.initialState = false; this.needToRevert = false; if (this.tlsContents != null) { if (0 == this.tlsContents.DecrementReferenceCount()) { this.tlsContents = null; Thread.SetData(tlsSlot, null); } } } } #endregion #region Nested class for P/Invokes and native (Win32) structures static class NativeMethods { #region PInvoke advapi32 [Flags] public enum PrivilegeAttribute : uint { Disabled = 0x00000000, EnabledByDefault = 0x00000001, Enabled = 0x00000002, Removed = 0x00000004, UsedForAccess = 0x80000000 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct LUID_AND_ATTRIBUTES { public Win32.LUID Luid; public PrivilegeAttribute Attributes; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct TOKEN_PRIVILEGE { public uint PrivilegeCount; public LUID_AND_ATTRIBUTES Privilege; } [DllImport(Win32.ADVAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool AdjustTokenPrivileges( SafeTokenHandle TokenHandle, [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges, ref TOKEN_PRIVILEGE NewState, uint BufferLength, ref TOKEN_PRIVILEGE PreviousState, ref uint ReturnLength); [DllImport(Win32.ADVAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool LookupPrivilegeValue( string systemName, string privilegeName, ref Win32.LUID luid); [DllImport(Win32.ADVAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool OpenProcessToken( HANDLE ProcessToken, TokenAccessLevels DesiredAccess, ref SafeTokenHandle TokenHandle); [DllImport(Win32.ADVAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool OpenThreadToken( HANDLE ThreadToken, TokenAccessLevels DesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool OpenAsSelf, ref SafeTokenHandle TokenHandle); internal enum TokenType { Primary = 1, Impersonation = 2, } [DllImport(Win32.ADVAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DuplicateTokenEx( SafeTokenHandle ExistingToken, TokenAccessLevels DesiredAccess, IntPtr TokenAttributes, TokenImpersonationLevel ImpersonationLevel, TokenType TokenType, ref SafeTokenHandle NewToken); [DllImport(Win32.ADVAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetThreadToken(HANDLE Thread, SafeTokenHandle Token); [DllImport(Win32.ADVAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool RevertToSelf(); #endregion #region PInvoke kernel32 [DllImport(Win32.KERNEL32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public static extern HANDLE GetCurrentProcess(); [DllImport(Win32.KERNEL32_DLL, CharSet = CharSet.Unicode, SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public static extern HANDLE GetCurrentThread(); #endregion } #endregion } }