mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
[release/v7.4] Fallback to AppLocker after WldpCanExecuteFile (#25229)
Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
This commit is contained in:
co-authored by
Patrick Meinecke
parent
f1e27f95e3
commit
3e5477f7eb
@@ -6,6 +6,7 @@
|
||||
//
|
||||
#if !UNIX
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Management.Automation.Internal;
|
||||
using System.Management.Automation.Runspaces;
|
||||
@@ -148,7 +149,7 @@ namespace System.Management.Automation.Security
|
||||
{
|
||||
lock (s_systemLockdownPolicyLock)
|
||||
{
|
||||
s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null);
|
||||
s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null, out _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,93 +173,89 @@ namespace System.Management.Automation.Security
|
||||
System.IO.FileStream fileStream)
|
||||
{
|
||||
SafeHandle fileHandle = fileStream.SafeFileHandle;
|
||||
var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy();
|
||||
SystemEnforcementMode systemLockdownPolicy = GetSystemLockdownPolicy();
|
||||
|
||||
// First check latest WDAC APIs if available.
|
||||
// Revert to legacy APIs if system policy is in AUDIT mode or debug hook is in effect.
|
||||
Exception errorException = null;
|
||||
if (s_wldpCanExecuteAvailable && systemLockdownPolicy == SystemEnforcementMode.Enforce)
|
||||
if (systemLockdownPolicy is SystemEnforcementMode.Enforce
|
||||
&& s_wldpCanExecuteAvailable
|
||||
&& TryGetWldpCanExecuteFileResult(filePath, fileHandle, out SystemScriptFileEnforcement wldpFilePolicy))
|
||||
{
|
||||
try
|
||||
{
|
||||
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
|
||||
string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}";
|
||||
|
||||
int hr = WldpNativeMethods.WldpCanExecuteFile(
|
||||
host: PowerShellHost,
|
||||
options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE,
|
||||
fileHandle: fileHandle.DangerousGetHandle(),
|
||||
auditInfo: auditMsg,
|
||||
result: out WLDP_EXECUTION_POLICY canExecuteResult);
|
||||
|
||||
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult);
|
||||
|
||||
if (hr >= 0)
|
||||
{
|
||||
switch (canExecuteResult)
|
||||
{
|
||||
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED:
|
||||
return SystemScriptFileEnforcement.Allow;
|
||||
|
||||
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED:
|
||||
return SystemScriptFileEnforcement.Block;
|
||||
|
||||
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX:
|
||||
return SystemScriptFileEnforcement.AllowConstrained;
|
||||
|
||||
default:
|
||||
// Fall through to legacy system policy checks.
|
||||
System.Diagnostics.Debug.Assert(false, $"Unknown execution policy returned from WldCanExecute: {canExecuteResult}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks.
|
||||
}
|
||||
catch (DllNotFoundException ex)
|
||||
{
|
||||
// Fall back to legacy system policy checks.
|
||||
s_wldpCanExecuteAvailable = false;
|
||||
errorException = ex;
|
||||
}
|
||||
catch (EntryPointNotFoundException ex)
|
||||
{
|
||||
// Fall back to legacy system policy checks.
|
||||
s_wldpCanExecuteAvailable = false;
|
||||
errorException = ex;
|
||||
}
|
||||
|
||||
if (errorException != null)
|
||||
{
|
||||
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, errorException.HResult, 0);
|
||||
}
|
||||
return GetLockdownPolicy(filePath, fileHandle, wldpFilePolicy);
|
||||
}
|
||||
|
||||
// Original (legacy) WDAC and AppLocker system checks.
|
||||
if (systemLockdownPolicy == SystemEnforcementMode.None)
|
||||
// Failed to invoke WldpCanExecuteFile, revert to legacy APIs.
|
||||
if (systemLockdownPolicy is SystemEnforcementMode.None)
|
||||
{
|
||||
return SystemScriptFileEnforcement.None;
|
||||
}
|
||||
|
||||
// Check policy for file.
|
||||
switch (SystemPolicy.GetLockdownPolicy(filePath, fileHandle))
|
||||
// WldpCanExecuteFile was invoked successfully so we can skip running
|
||||
// legacy WDAC APIs. AppLocker must still be checked in case it is more
|
||||
// strict than the current WDAC policy.
|
||||
return GetLockdownPolicy(filePath, fileHandle, canExecuteResult: null);
|
||||
}
|
||||
|
||||
private static SystemScriptFileEnforcement ConvertToModernFileEnforcement(SystemEnforcementMode legacyMode)
|
||||
{
|
||||
return legacyMode switch
|
||||
{
|
||||
case SystemEnforcementMode.Enforce:
|
||||
// File is not allowed by policy enforcement and must run in CL mode.
|
||||
return SystemScriptFileEnforcement.AllowConstrained;
|
||||
SystemEnforcementMode.None => SystemScriptFileEnforcement.Allow,
|
||||
SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit,
|
||||
SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained,
|
||||
_ => SystemScriptFileEnforcement.Block,
|
||||
};
|
||||
}
|
||||
|
||||
case SystemEnforcementMode.Audit:
|
||||
// File is allowed but would be run in CL mode if policy was enforced and not audit.
|
||||
return SystemScriptFileEnforcement.AllowConstrainedAudit;
|
||||
private static bool TryGetWldpCanExecuteFileResult(string filePath, SafeHandle fileHandle, out SystemScriptFileEnforcement result)
|
||||
{
|
||||
try
|
||||
{
|
||||
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
|
||||
string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}";
|
||||
|
||||
case SystemEnforcementMode.None:
|
||||
// No restrictions, file will run in FL mode.
|
||||
return SystemScriptFileEnforcement.Allow;
|
||||
int hr = WldpNativeMethods.WldpCanExecuteFile(
|
||||
host: PowerShellHost,
|
||||
options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE,
|
||||
fileHandle: fileHandle.DangerousGetHandle(),
|
||||
auditInfo: auditMsg,
|
||||
result: out WLDP_EXECUTION_POLICY canExecuteResult);
|
||||
|
||||
default:
|
||||
System.Diagnostics.Debug.Assert(false, "GetFilePolicyEnforcement: Unknown SystemEnforcementMode.");
|
||||
return SystemScriptFileEnforcement.Block;
|
||||
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult);
|
||||
|
||||
if (hr >= 0)
|
||||
{
|
||||
switch (canExecuteResult)
|
||||
{
|
||||
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED:
|
||||
result = SystemScriptFileEnforcement.Allow;
|
||||
return true;
|
||||
|
||||
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED:
|
||||
result = SystemScriptFileEnforcement.Block;
|
||||
return true;
|
||||
|
||||
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX:
|
||||
result = SystemScriptFileEnforcement.AllowConstrained;
|
||||
return true;
|
||||
|
||||
default:
|
||||
// Fall through to legacy system policy checks.
|
||||
Debug.Assert(false, $"Unknown policy result returned from WldCanExecute: {canExecuteResult}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks.
|
||||
}
|
||||
catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException)
|
||||
{
|
||||
// Fall back to legacy system policy checks.
|
||||
s_wldpCanExecuteAvailable = false;
|
||||
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, ex.HResult, 0);
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -267,9 +264,32 @@ namespace System.Management.Automation.Security
|
||||
/// <returns>An EnforcementMode that describes policy.</returns>
|
||||
public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle handle)
|
||||
{
|
||||
SystemScriptFileEnforcement modernMode = GetLockdownPolicy(path, handle, canExecuteResult: null);
|
||||
Debug.Assert(
|
||||
modernMode is not SystemScriptFileEnforcement.Block,
|
||||
"Block should never be converted to legacy file enforcement.");
|
||||
|
||||
return modernMode switch
|
||||
{
|
||||
SystemScriptFileEnforcement.Block => SystemEnforcementMode.Enforce,
|
||||
SystemScriptFileEnforcement.AllowConstrained => SystemEnforcementMode.Enforce,
|
||||
SystemScriptFileEnforcement.AllowConstrainedAudit => SystemEnforcementMode.Audit,
|
||||
SystemScriptFileEnforcement.Allow => SystemEnforcementMode.None,
|
||||
SystemScriptFileEnforcement.None => SystemEnforcementMode.None,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(modernMode)),
|
||||
};
|
||||
}
|
||||
|
||||
private static SystemScriptFileEnforcement GetLockdownPolicy(
|
||||
string path,
|
||||
SafeHandle handle,
|
||||
SystemScriptFileEnforcement? canExecuteResult)
|
||||
{
|
||||
SystemScriptFileEnforcement wldpFilePolicy = canExecuteResult
|
||||
?? ConvertToModernFileEnforcement(GetWldpPolicy(path, handle));
|
||||
|
||||
// Check the WLDP File policy via API
|
||||
var wldpFilePolicy = GetWldpPolicy(path, handle);
|
||||
if (wldpFilePolicy == SystemEnforcementMode.Enforce)
|
||||
if (wldpFilePolicy is SystemScriptFileEnforcement.Block or SystemScriptFileEnforcement.AllowConstrained)
|
||||
{
|
||||
return wldpFilePolicy;
|
||||
}
|
||||
@@ -281,29 +301,28 @@ namespace System.Management.Automation.Security
|
||||
var appLockerFilePolicy = GetAppLockerPolicy(path, handle);
|
||||
if (appLockerFilePolicy == SystemEnforcementMode.Enforce)
|
||||
{
|
||||
return appLockerFilePolicy;
|
||||
return ConvertToModernFileEnforcement(appLockerFilePolicy);
|
||||
}
|
||||
|
||||
// At this point, LockdownPolicy = Audit or Allowed.
|
||||
// If there was a WLDP policy, but WLDP didn't block it,
|
||||
// then it was explicitly allowed. Therefore, return the result for the file.
|
||||
SystemEnforcementMode systemWldpPolicy = s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None);
|
||||
if ((systemWldpPolicy == SystemEnforcementMode.Audit) ||
|
||||
(systemWldpPolicy == SystemEnforcementMode.Enforce))
|
||||
if (s_cachedWldpSystemPolicy is SystemEnforcementMode.Audit or SystemEnforcementMode.Enforce
|
||||
|| wldpFilePolicy is SystemScriptFileEnforcement.AllowConstrainedAudit)
|
||||
{
|
||||
return wldpFilePolicy;
|
||||
}
|
||||
|
||||
// If there was a system-wide AppLocker policy, but AppLocker didn't block it,
|
||||
// then return AppLocker's status.
|
||||
if (s_cachedSaferSystemPolicy.GetValueOrDefault(SaferPolicy.Allowed) ==
|
||||
SaferPolicy.Disallowed)
|
||||
if (s_cachedSaferSystemPolicy is SaferPolicy.Disallowed)
|
||||
{
|
||||
return appLockerFilePolicy;
|
||||
return ConvertToModernFileEnforcement(appLockerFilePolicy);
|
||||
}
|
||||
|
||||
// If it's not set to 'Enforce' by the platform, allow debug overrides
|
||||
return GetDebugLockdownPolicy(path);
|
||||
GetDebugLockdownPolicy(path, out SystemScriptFileEnforcement debugPolicy);
|
||||
return debugPolicy;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
|
||||
@@ -558,7 +577,7 @@ namespace System.Management.Automation.Security
|
||||
return result;
|
||||
}
|
||||
|
||||
private static SystemEnforcementMode GetDebugLockdownPolicy(string path)
|
||||
private static SystemEnforcementMode GetDebugLockdownPolicy(string path, out SystemScriptFileEnforcement modernEnforcement)
|
||||
{
|
||||
s_allowDebugOverridePolicy = true;
|
||||
|
||||
@@ -569,10 +588,19 @@ namespace System.Management.Automation.Security
|
||||
// check so that we can actually put it in the filename during testing.
|
||||
if (path.Contains("System32", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modernEnforcement = SystemScriptFileEnforcement.Allow;
|
||||
return SystemEnforcementMode.None;
|
||||
}
|
||||
|
||||
// No explicit debug allowance for the file, so return the system policy if there is one.
|
||||
modernEnforcement = s_systemLockdownPolicy switch
|
||||
{
|
||||
SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained,
|
||||
SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit,
|
||||
SystemEnforcementMode.None => SystemScriptFileEnforcement.None,
|
||||
_ => SystemScriptFileEnforcement.None,
|
||||
};
|
||||
|
||||
return s_systemLockdownPolicy.GetValueOrDefault(SystemEnforcementMode.None);
|
||||
}
|
||||
|
||||
@@ -582,10 +610,13 @@ namespace System.Management.Automation.Security
|
||||
if (result != null)
|
||||
{
|
||||
pdwLockdownState = LanguagePrimitives.ConvertTo<uint>(result);
|
||||
return GetLockdownPolicyForResult(pdwLockdownState);
|
||||
SystemEnforcementMode policy = GetLockdownPolicyForResult(pdwLockdownState);
|
||||
modernEnforcement = ConvertToModernFileEnforcement(policy);
|
||||
return policy;
|
||||
}
|
||||
|
||||
// If the system-wide debug policy had no preference, then there is no enforcement.
|
||||
modernEnforcement = SystemScriptFileEnforcement.None;
|
||||
return SystemEnforcementMode.None;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user