mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Performance improvements to security checks when group policies are in effect for ExecutionPolicy (#2588)
* Caching the parent id of the current process In certain scenarios, like a domain joined machine with an executionpolicy set via GPO, a lot of checks are done for the ExecutionPolicy, and this is in the hot path. * Caching HasGPScriptParent. It is expensive to determine if a script was run as a result of applying a group policy. This does not change during the lifetime of a process. This only affect the scenario when an execution policy has been set by a group policy. * Using available Microsoft.PowerShell.ProcessCodeMethods.GetParentPid
This commit is contained in:
committed by
Mike Richmond
parent
1d42862e21
commit
f0eda031d6
@@ -258,6 +258,69 @@ namespace System.Management.Automation.Internal
|
||||
return ExecutionPolicy.Restricted;
|
||||
}
|
||||
|
||||
|
||||
private static bool? _hasGpScriptParent;
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating that the current process was launched by GPScript.exe
|
||||
/// Used to determine execution policy when group policies are in effect
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is somewhat expensive to determine and does not change within the lifetime of the current process
|
||||
/// </remarks>
|
||||
private static bool HasGpScriptParent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_hasGpScriptParent.HasValue)
|
||||
{
|
||||
_hasGpScriptParent = IsCurrentProcessLaunchedByGpScript();
|
||||
}
|
||||
return _hasGpScriptParent.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsCurrentProcessLaunchedByGpScript()
|
||||
{
|
||||
Process currentProcess = Process.GetCurrentProcess();
|
||||
string gpScriptPath = IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.System),
|
||||
"gpscript.exe");
|
||||
|
||||
bool foundGpScriptParent = false;
|
||||
try
|
||||
{
|
||||
while (currentProcess != null)
|
||||
{
|
||||
if (String.Equals(gpScriptPath,
|
||||
PsUtils.GetMainModule(currentProcess).FileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foundGpScriptParent = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentProcess = PsUtils.GetParentProcess(currentProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception)
|
||||
{
|
||||
// If you attempt to retrieve the MainModule of a 64-bit process
|
||||
// from a WOW64 (32-bit) process, the Win32 API has a fatal
|
||||
// flaw that causes this to return the error:
|
||||
// "Only part of a ReadProcessMemory or WriteProcessMemory
|
||||
// request was completed."
|
||||
// In this case, we just catch the exception and eat it.
|
||||
// The implication is that logon / logoff scripts that somehow
|
||||
// launch the Wow64 version of PowerShell will be subject
|
||||
// to the execution policy deployed by Group Policy (where
|
||||
// our goal here is to not have the Group Policy execution policy
|
||||
// affect logon / logoff scripts.
|
||||
}
|
||||
return foundGpScriptParent;
|
||||
}
|
||||
|
||||
internal static ExecutionPolicy GetExecutionPolicy(string shellId, ExecutionPolicyScope scope)
|
||||
{
|
||||
#if UNIX
|
||||
@@ -292,55 +355,15 @@ namespace System.Management.Automation.Internal
|
||||
case ExecutionPolicyScope.MachinePolicy:
|
||||
{
|
||||
string groupPolicyPreference = GetGroupPolicyValue(shellId, scope);
|
||||
if (!String.IsNullOrEmpty(groupPolicyPreference))
|
||||
|
||||
// Be sure we aren't being called by Group Policy
|
||||
// itself. A group policy should never block a logon /
|
||||
// logoff script.
|
||||
if (String.IsNullOrEmpty(groupPolicyPreference) || HasGpScriptParent)
|
||||
{
|
||||
// Be sure we aren't being called by Group Policy
|
||||
// itself. A group policy should never block a logon /
|
||||
// logoff script.
|
||||
Process currentProcess = Process.GetCurrentProcess();
|
||||
string gpScriptPath = IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.System),
|
||||
"gpscript.exe");
|
||||
bool foundGpScriptParent = false;
|
||||
|
||||
try
|
||||
{
|
||||
while (currentProcess != null)
|
||||
{
|
||||
if (String.Equals(gpScriptPath,
|
||||
PsUtils.GetMainModule(currentProcess).FileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foundGpScriptParent = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentProcess = PsUtils.GetParentProcess(currentProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception)
|
||||
{
|
||||
// If you attempt to retrieve the MainModule of a 64-bit process
|
||||
// from a WOW64 (32-bit) process, the Win32 API has a fatal
|
||||
// flaw that causes this to return the error:
|
||||
// "Only part of a ReadProcessMemory or WriteProcessMemory
|
||||
// request was completed."
|
||||
// In this case, we just catch the exception and eat it.
|
||||
// The implication is that logon / logoff scripts that somehow
|
||||
// launch the Wow64 version of PowerShell will be subject
|
||||
// to the execution policy deployed by Group Policy (where
|
||||
// our goal here is to not have the Group Policy execution policy
|
||||
// affect logon / logoff scripts.
|
||||
}
|
||||
|
||||
if (!foundGpScriptParent)
|
||||
{
|
||||
return ParseExecutionPolicy(groupPolicyPreference);
|
||||
}
|
||||
}
|
||||
|
||||
return ExecutionPolicy.Undefined;
|
||||
return ExecutionPolicy.Undefined;
|
||||
}
|
||||
return ParseExecutionPolicy(groupPolicyPreference);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,52 +86,42 @@ namespace System.Management.Automation
|
||||
return mainModule;
|
||||
}
|
||||
|
||||
// Cache of the current process' parentId
|
||||
private static int? s_currentParentProcessId;
|
||||
private static readonly int s_currentProcessId = Process.GetCurrentProcess().Id;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the parent process of a process.
|
||||
///
|
||||
/// Previously this code used WMI, but WMI is causing a CPU spike whenever the query gets called as it results in
|
||||
/// tzres.dll and tzres.mui.dll being loaded into every process to conver the time information to local format.
|
||||
/// For perf reasons, we result to P/Invoke.
|
||||
/// tzres.dll and tzres.mui.dll being loaded into every process to convert the time information to local format.
|
||||
/// For perf reasons, we resort to P/Invoke.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="current">The process we want to find the
|
||||
/// parent of</param>
|
||||
internal static Process GetParentProcess(Process current)
|
||||
{
|
||||
int parentPid = 0;
|
||||
var processId = current.Id;
|
||||
|
||||
#if !UNIX
|
||||
PlatformInvokes.PROCESSENTRY32 pe32 = new PlatformInvokes.PROCESSENTRY32 { };
|
||||
pe32.dwSize = (uint)ClrFacade.SizeOf<PlatformInvokes.PROCESSENTRY32>();
|
||||
// This is a common query (parent id for the current process)
|
||||
// Use cached value if available
|
||||
var parentProcessId = processId == s_currentProcessId && s_currentParentProcessId.HasValue ?
|
||||
s_currentParentProcessId.Value :
|
||||
Microsoft.PowerShell.ProcessCodeMethods.GetParentPid(current);
|
||||
|
||||
using (PlatformInvokes.SafeSnapshotHandle hSnapshot = PlatformInvokes.CreateToolhelp32Snapshot(PlatformInvokes.SnapshotFlags.Process, (uint)current.Id))
|
||||
// cache the current process parent pid if it hasn't been done yet
|
||||
if (processId == s_currentProcessId && !s_currentParentProcessId.HasValue)
|
||||
{
|
||||
if (!PlatformInvokes.Process32First(hSnapshot, ref pe32))
|
||||
{
|
||||
int errno = Marshal.GetLastWin32Error();
|
||||
if (errno == PlatformInvokes.ERROR_NO_MORE_FILES)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
do
|
||||
{
|
||||
if (pe32.th32ProcessID == (uint)current.Id)
|
||||
{
|
||||
parentPid = (int)pe32.th32ParentProcessID;
|
||||
break;
|
||||
}
|
||||
|
||||
} while (PlatformInvokes.Process32Next(hSnapshot, ref pe32));
|
||||
s_currentParentProcessId = parentProcessId;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (parentPid == 0)
|
||||
if (parentProcessId == 0)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
Process returnProcess = Process.GetProcessById(parentPid);
|
||||
Process returnProcess = Process.GetProcessById(parentProcessId);
|
||||
|
||||
// Ensure the process started before the current
|
||||
// process, as it could have gone away and had the
|
||||
|
||||
Reference in New Issue
Block a user