You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
11 KiB

  1. // <copyright file="PrivilegeEnabler.cs" company="Nick Lowe">
  2. // Copyright © Nick Lowe 2009
  3. // </copyright>
  4. // <author>Nick Lowe</author>
  5. // <email>nick@int-r.net</email>
  6. // <url>http://processprivileges.codeplex.com/</url>
  7. namespace ProcessPrivileges
  8. {
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Diagnostics;
  12. using System.Linq;
  13. using System.Security.Permissions;
  14. /// <summary>Enables privileges on a process in a safe way, ensuring that they are returned to their original state when an operation that requires a privilege completes.</summary>
  15. /// <example>
  16. /// <code>
  17. /// using System;
  18. /// using System.Diagnostics;
  19. /// using ProcessPrivileges;
  20. ///
  21. /// internal static class PrivilegeEnablerExample
  22. /// {
  23. /// public static void Main()
  24. /// {
  25. /// Process process = Process.GetCurrentProcess();
  26. ///
  27. /// using (new PrivilegeEnabler(process, Privilege.TakeOwnership))
  28. /// {
  29. /// // Privilege is enabled within the using block.
  30. /// Console.WriteLine(
  31. /// "{0} => {1}",
  32. /// Privilege.TakeOwnership,
  33. /// process.GetPrivilegeState(Privilege.TakeOwnership));
  34. /// }
  35. ///
  36. /// // Privilege is disabled outside the using block.
  37. /// Console.WriteLine(
  38. /// "{0} => {1}",
  39. /// Privilege.TakeOwnership,
  40. /// process.GetPrivilegeState(Privilege.TakeOwnership));
  41. /// }
  42. /// }
  43. /// </code>
  44. /// </example>
  45. /// <remarks>
  46. /// <para>When disabled, privileges are enabled until the instance of the PrivilegeEnabler class is disposed.</para>
  47. /// <para>If the privilege specified is already enabled, it is not modified and will not be disabled when the instance of the PrivilegeEnabler class is disposed.</para>
  48. /// <para>If desired, multiple privileges can be specified in the constructor.</para>
  49. /// <para>If using multiple instances on the same process, do not dispose of them out-of-order. Making use of a using statement, the recommended method, enforces this.</para>
  50. /// <para>For more information on privileges, see:</para>
  51. /// <para><a href="http://msdn.microsoft.com/en-us/library/aa379306.aspx">Privileges</a></para>
  52. /// <para><a href="http://msdn.microsoft.com/en-us/library/bb530716.aspx">Privilege Constants</a></para>
  53. /// </remarks>
  54. public sealed class PrivilegeEnabler : IDisposable
  55. {
  56. private static readonly Dictionary<Privilege, PrivilegeEnabler> sharedPrivileges =
  57. new Dictionary<Privilege, PrivilegeEnabler>();
  58. private static readonly Dictionary<Process, AccessTokenHandle> accessTokenHandles =
  59. new Dictionary<Process, AccessTokenHandle>();
  60. private AccessTokenHandle accessTokenHandle;
  61. private bool disposed;
  62. private bool ownsHandle;
  63. private Process process;
  64. /// <summary>Initializes a new instance of the PrivilegeEnabler class.</summary>
  65. /// <param name="accessTokenHandle">The <see cref="AccessTokenHandle"/> for a <see cref="Process"/> on which privileges should be enabled.</param>
  66. /// <exception cref="InvalidOperationException">Thrown when another instance exists and has not been disposed.</exception>
  67. /// <permission cref="SecurityAction.LinkDemand">Requires the immediate caller to have FullTrust.</permission>
  68. [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
  69. public PrivilegeEnabler(AccessTokenHandle accessTokenHandle)
  70. {
  71. this.accessTokenHandle = accessTokenHandle;
  72. }
  73. /// <summary>Initializes a new instance of the PrivilegeEnabler class.</summary>
  74. /// <param name="process">The <see cref="Process"/> on which privileges should be enabled.</param>
  75. /// <exception cref="InvalidOperationException">Thrown when another instance exists and has not been disposed.</exception>
  76. /// <permission cref="SecurityAction.LinkDemand">Requires the immediate caller to have FullTrust.</permission>
  77. [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
  78. public PrivilegeEnabler(Process process)
  79. {
  80. lock (accessTokenHandles)
  81. {
  82. if (accessTokenHandles.ContainsKey(process))
  83. {
  84. this.accessTokenHandle = accessTokenHandles[process];
  85. }
  86. else
  87. {
  88. this.accessTokenHandle =
  89. process.GetAccessTokenHandle(TokenAccessRights.AdjustPrivileges | TokenAccessRights.Query);
  90. accessTokenHandles.Add(process, this.accessTokenHandle);
  91. this.ownsHandle = true;
  92. }
  93. }
  94. this.process = process;
  95. }
  96. /// <summary>Initializes a new instance of the PrivilegeEnabler class with the specified privileges to be enabled.</summary>
  97. /// <param name="accessTokenHandle">The <see cref="AccessTokenHandle"/> for a <see cref="Process"/> on which privileges should be enabled.</param>
  98. /// <param name="privileges">The privileges to be enabled.</param>
  99. /// <exception cref="Win32Exception">Thrown when an underlying Win32 function call does not succeed.</exception>
  100. /// <permission cref="SecurityAction.LinkDemand">Requires the immediate caller to have FullTrust.</permission>
  101. [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
  102. public PrivilegeEnabler(AccessTokenHandle accessTokenHandle, params Privilege[] privileges)
  103. : this(accessTokenHandle)
  104. {
  105. foreach (Privilege privilege in privileges)
  106. {
  107. this.EnablePrivilege(privilege);
  108. }
  109. }
  110. /// <summary>Initializes a new instance of the PrivilegeEnabler class with the specified privileges to be enabled.</summary>
  111. /// <param name="process">The <see cref="Process"/> on which privileges should be enabled.</param>
  112. /// <param name="privileges">The privileges to be enabled.</param>
  113. /// <exception cref="Win32Exception">Thrown when an underlying Win32 function call does not succeed.</exception>
  114. /// <permission cref="SecurityAction.LinkDemand">Requires the immediate caller to have FullTrust.</permission>
  115. [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
  116. public PrivilegeEnabler(Process process, params Privilege[] privileges)
  117. : this(process)
  118. {
  119. foreach (Privilege privilege in privileges)
  120. {
  121. this.EnablePrivilege(privilege);
  122. }
  123. }
  124. /// <summary>Finalizes an instance of the PrivilegeEnabler class.</summary>
  125. ~PrivilegeEnabler()
  126. {
  127. this.InternalDispose();
  128. }
  129. /// <summary>Disposes of an instance of the PrivilegeEnabler class.</summary>
  130. /// <exception cref="Win32Exception">Thrown when an underlying Win32 function call does not succeed.</exception>
  131. /// <permission cref="SecurityAction.Demand">Requires the call stack to have FullTrust.</permission>
  132. [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
  133. public void Dispose()
  134. {
  135. this.InternalDispose();
  136. GC.SuppressFinalize(this);
  137. }
  138. /// <summary>Enables the specified <see cref="Privilege"/>.</summary>
  139. /// <param name="privilege">The <see cref="Privilege"/> to be enabled.</param>
  140. /// <returns>
  141. /// <para>Result from the privilege adjustment.</para>
  142. /// <para>If the <see cref="Privilege"/> is already enabled, <see cref="AdjustPrivilegeResult.None"/> is returned.</para>
  143. /// <para>If the <see cref="Privilege"/> is owned by another instance of the PrivilegeEnabler class, <see cref="AdjustPrivilegeResult.None"/> is returned.</para>
  144. /// <para>If a <see cref="Privilege"/> is removed from a process, it cannot be enabled.</para>
  145. /// </returns>
  146. /// <remarks>
  147. /// <para>When disabled, privileges are enabled until the instance of the PrivilegeEnabler class is disposed.</para>
  148. /// <para>If the privilege specified is already enabled, it is not modified and will not be disabled when the instance of the PrivilegeEnabler class is disposed.</para>
  149. /// </remarks>
  150. /// <exception cref="Win32Exception">Thrown when an underlying Win32 function call does not succeed.</exception>
  151. /// <permission cref="SecurityAction.LinkDemand">Requires the immediate caller to have FullTrust.</permission>
  152. [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
  153. public AdjustPrivilegeResult EnablePrivilege(Privilege privilege)
  154. {
  155. lock (sharedPrivileges)
  156. {
  157. if (!sharedPrivileges.ContainsKey(privilege) &&
  158. this.accessTokenHandle.GetPrivilegeState(privilege) == PrivilegeState.Disabled &&
  159. this.accessTokenHandle.EnablePrivilege(privilege) == AdjustPrivilegeResult.PrivilegeModified)
  160. {
  161. sharedPrivileges.Add(privilege, this);
  162. return AdjustPrivilegeResult.PrivilegeModified;
  163. }
  164. return AdjustPrivilegeResult.None;
  165. }
  166. }
  167. [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
  168. private void InternalDispose()
  169. {
  170. if (!this.disposed)
  171. {
  172. lock (sharedPrivileges)
  173. {
  174. Privilege[] privileges = sharedPrivileges
  175. .Where(keyValuePair => keyValuePair.Value == this)
  176. .Select(keyValuePair => keyValuePair.Key)
  177. .ToArray();
  178. foreach (Privilege privilege in privileges)
  179. {
  180. this.accessTokenHandle.DisablePrivilege(privilege);
  181. sharedPrivileges.Remove(privilege);
  182. }
  183. if (this.ownsHandle)
  184. {
  185. this.accessTokenHandle.Dispose();
  186. lock (this.accessTokenHandle)
  187. {
  188. accessTokenHandles.Remove(this.process);
  189. }
  190. }
  191. this.accessTokenHandle = null;
  192. this.ownsHandle = false;
  193. this.process = null;
  194. this.disposed = true;
  195. }
  196. }
  197. }
  198. }
  199. }