| File: System\Security\AccessControl\Privilege.cs | Web Access |
| Project: src\runtime\src\libraries\System.Security.AccessControl\src\System.Security.AccessControl.csproj (System.Security.AccessControl) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Principal; using System.Threading; using Microsoft.Win32; using Microsoft.Win32.SafeHandles; using static System.Security.Principal.Win32; using CultureInfo = System.Globalization.CultureInfo; using Luid = Interop.Advapi32.LUID; namespace System.Security.AccessControl { /// <summary> /// Managed wrapper for NT privileges /// </summary> internal sealed class Privilege { [ThreadStatic] private static TlsContents? t_tlsSlotData; private static readonly Dictionary<Luid, string> s_privileges = new Dictionary<Luid, string>(); private static readonly Dictionary<string, Luid> s_luids = new Dictionary<string, Luid>(); private static readonly ReaderWriterLockSlim s_privilegeLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); private bool _needToRevert; private bool _initialState; private bool _stateWasChanged; private Luid _luid; private readonly Thread _currentThread = Thread.CurrentThread; private TlsContents? _tlsContents; 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"; // This routine is a wrapper around a hashtable containing mappings // of privilege names to LUIDs private static Luid LuidFromPrivilege(string privilege) { Luid luid; luid.LowPart = 0; luid.HighPart = 0; // Look up the privilege LUID inside the cache try { s_privilegeLock.EnterReadLock(); if (s_luids.TryGetValue(privilege, out luid)) { s_privilegeLock.ExitReadLock(); } else { s_privilegeLock.ExitReadLock(); if (!Interop.Advapi32.LookupPrivilegeValue(null, privilege, out luid)) { int error = Marshal.GetLastPInvokeError(); if (error == Interop.Errors.ERROR_NOT_ENOUGH_MEMORY) { throw new OutOfMemoryException(); } else if (error == Interop.Errors.ERROR_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } else if (error == Interop.Errors.ERROR_NO_SUCH_PRIVILEGE) { throw new ArgumentException( SR.Format(SR.Argument_InvalidPrivilegeName, privilege)); } else { System.Diagnostics.Debug.Fail($"LookupPrivilegeValue() failed with unrecognized error code {error}"); throw new InvalidOperationException(); } } s_privilegeLock.EnterWriteLock(); } } finally { if (s_privilegeLock.IsReadLockHeld) { s_privilegeLock.ExitReadLock(); } if (s_privilegeLock.IsWriteLockHeld) { if (s_luids.TryAdd(privilege, luid)) { s_privileges[luid] = privilege; } s_privilegeLock.ExitWriteLock(); } } return luid; } private sealed class TlsContents : IDisposable { private bool _disposed; private int _referenceCount = 1; private SafeTokenHandle? _threadHandle = new SafeTokenHandle(IntPtr.Zero); private readonly bool _isImpersonating; private static SafeTokenHandle s_processHandle = new SafeTokenHandle(IntPtr.Zero); private static readonly object s_syncRoot = new object(); public TlsContents() { int error = 0; int cachingError = 0; bool success = true; if (s_processHandle.IsInvalid) { lock (s_syncRoot) { if (s_processHandle.IsInvalid) { SafeTokenHandle localProcessHandle; if (!Interop.Advapi32.OpenProcessToken( Interop.Kernel32.GetCurrentProcess(), TokenAccessLevels.Duplicate, out localProcessHandle)) { cachingError = Marshal.GetLastPInvokeError(); success = false; } s_processHandle = localProcessHandle; } } } try { // Open the thread token; if there is no thread token, get one from // the process token by impersonating self. SafeTokenHandle? threadHandleBefore = _threadHandle; error = OpenThreadToken( TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, WinSecurityContext.Process, out _threadHandle); unchecked { error &= ~(int)0x80070000; } if (error != 0) { if (success) { _threadHandle = threadHandleBefore; if (error != Interop.Errors.ERROR_NO_TOKEN) { success = false; } System.Diagnostics.Debug.Assert(!_isImpersonating, "Incorrect isImpersonating state"); if (success) { error = 0; if (!Interop.Advapi32.DuplicateTokenEx( s_processHandle, TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, IntPtr.Zero, Interop.Advapi32.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, System.Security.Principal.TokenType.TokenImpersonation, ref _threadHandle)) { error = Marshal.GetLastPInvokeError(); success = false; } } if (success) { error = SetThreadToken(_threadHandle); unchecked { error &= ~(int)0x80070000; } if (error != 0) { success = false; } } if (success) { _isImpersonating = true; } } else { error = cachingError; } } else { success = true; } } finally { if (!success) { Dispose(); } } if (error == Interop.Errors.ERROR_NOT_ENOUGH_MEMORY) { throw new OutOfMemoryException(); } else if (error == Interop.Errors.ERROR_ACCESS_DENIED || error == Interop.Errors.ERROR_CANT_OPEN_ANONYMOUS) { throw new UnauthorizedAccessException(); } else if (error != 0) { System.Diagnostics.Debug.Fail($"WindowsIdentity.GetCurrentThreadToken() failed with unrecognized error code {error}"); throw new InvalidOperationException(); } } ~TlsContents() { if (!_disposed) { Dispose(false); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) return; if (disposing) { if (_threadHandle != null) { _threadHandle.Dispose(); _threadHandle = null!; } } if (_isImpersonating) { Interop.Advapi32.RevertToSelf(); } _disposed = true; } public void IncrementReferenceCount() { _referenceCount++; } public int DecrementReferenceCount() { int result = --_referenceCount; if (result == 0) { Dispose(); } return result; } public int ReferenceCountValue { get { return _referenceCount; } } public SafeTokenHandle ThreadHandle { get { return _threadHandle!; } } public bool IsImpersonating { get { return _isImpersonating; } } } public Privilege(string privilegeName) { ArgumentNullException.ThrowIfNull(privilegeName); _luid = LuidFromPrivilege(privilegeName); } // Finalizer simply ensures that the privilege was not leaked ~Privilege() { System.Diagnostics.Debug.Assert(!_needToRevert, "Must revert privileges that you alter!"); if (_needToRevert) { Revert(); } } public void Enable() { ToggleState(true); } public bool NeedToRevert { get { return _needToRevert; } } private unsafe void ToggleState(bool enable) { int error = 0; // All privilege operations must take place on the same thread if (!_currentThread.Equals(Thread.CurrentThread)) { throw new InvalidOperationException(SR.InvalidOperation_MustBeSameThread); } // This privilege was already altered and needs to be reverted before it can be altered again if (_needToRevert) { throw new InvalidOperationException(SR.InvalidOperation_MustRevertPrivilege); } try { // Retrieve TLS state _tlsContents = t_tlsSlotData; if (_tlsContents == null) { _tlsContents = new TlsContents(); t_tlsSlotData = _tlsContents; } else { _tlsContents.IncrementReferenceCount(); } Interop.Advapi32.TOKEN_PRIVILEGE newState; newState.PrivilegeCount = 1; newState.Privileges.Luid = _luid; newState.Privileges.Attributes = enable ? Interop.Advapi32.SEPrivileges.SE_PRIVILEGE_ENABLED : Interop.Advapi32.SEPrivileges.SE_PRIVILEGE_DISABLED; Interop.Advapi32.TOKEN_PRIVILEGE previousState = default; uint previousSize = 0; // Place the new privilege on the thread token and remember the previous state. if (!Interop.Advapi32.AdjustTokenPrivileges( _tlsContents.ThreadHandle, false, &newState, (uint)sizeof(Interop.Advapi32.TOKEN_PRIVILEGE), &previousState, &previousSize)) { error = Marshal.GetLastPInvokeError(); } else if (Interop.Errors.ERROR_NOT_ALL_ASSIGNED == Marshal.GetLastPInvokeError()) { error = Interop.Errors.ERROR_NOT_ALL_ASSIGNED; } else { // This is the initial state that revert will have to go back to _initialState = ((previousState.Privileges.Attributes & Interop.Advapi32.SEPrivileges.SE_PRIVILEGE_ENABLED) != 0); // Remember whether state has changed at all _stateWasChanged = (_initialState != enable); // If we had to impersonate, or if the privilege state changed we'll need to revert _needToRevert = _tlsContents.IsImpersonating || _stateWasChanged; } } finally { if (!_needToRevert) { Reset(); } } if (error == Interop.Errors.ERROR_NOT_ALL_ASSIGNED) { throw new PrivilegeNotHeldException(s_privileges[_luid]); } if (error == Interop.Errors.ERROR_NOT_ENOUGH_MEMORY) { throw new OutOfMemoryException(); } else if (error == Interop.Errors.ERROR_ACCESS_DENIED || error == Interop.Errors.ERROR_CANT_OPEN_ANONYMOUS) { throw new UnauthorizedAccessException(); } else if (error != 0) { System.Diagnostics.Debug.Fail($"AdjustTokenPrivileges() failed with unrecognized error code {error}"); throw new InvalidOperationException(); } } public unsafe void Revert() { int error = 0; if (!_currentThread.Equals(Thread.CurrentThread)) { throw new InvalidOperationException(SR.InvalidOperation_MustBeSameThread); } if (!NeedToRevert) { return; } 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 (_stateWasChanged && (_tlsContents!.ReferenceCountValue > 1 || !_tlsContents.IsImpersonating)) { Interop.Advapi32.TOKEN_PRIVILEGE newState; newState.PrivilegeCount = 1; newState.Privileges.Luid = _luid; newState.Privileges.Attributes = (_initialState ? Interop.Advapi32.SEPrivileges.SE_PRIVILEGE_ENABLED : Interop.Advapi32.SEPrivileges.SE_PRIVILEGE_DISABLED); if (!Interop.Advapi32.AdjustTokenPrivileges( _tlsContents.ThreadHandle, false, &newState, 0, null, null)) { error = Marshal.GetLastPInvokeError(); success = false; } } } finally { if (success) { Reset(); } } if (error == Interop.Errors.ERROR_NOT_ENOUGH_MEMORY) { throw new OutOfMemoryException(); } else if (error == Interop.Errors.ERROR_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } else if (error != 0) { System.Diagnostics.Debug.Fail($"AdjustTokenPrivileges() failed with unrecognized error code {error}"); throw new InvalidOperationException(); } } private void Reset() { _stateWasChanged = false; _initialState = false; _needToRevert = false; if (_tlsContents != null) { if (0 == _tlsContents.DecrementReferenceCount()) { _tlsContents = null; t_tlsSlotData = null; } } } } }