[release/v7.4] Fallback to AppLocker after `WldpCanExecuteFile` by TravisEz13 · Pull Request #25229 · PowerShell/PowerShell

Expand Up @@ -6,6 +6,7 @@ // #if !UNIX
using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; Expand Down Expand Up @@ -148,7 +149,7 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() { lock (s_systemLockdownPolicyLock) { s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null); s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null, out _); } }
Expand All @@ -172,93 +173,89 @@ public static SystemScriptFileEnforcement GetFilePolicyEnforcement( 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}"; return GetLockdownPolicy(filePath, fileHandle, wldpFilePolicy); }
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); // Failed to invoke WldpCanExecuteFile, revert to legacy APIs. if (systemLockdownPolicy is SystemEnforcementMode.None) { return SystemScriptFileEnforcement.None; }
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult); // 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); }
if (hr >= 0) { switch (canExecuteResult) { case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED: return SystemScriptFileEnforcement.Allow; private static SystemScriptFileEnforcement ConvertToModernFileEnforcement(SystemEnforcementMode legacyMode) { return legacyMode switch { SystemEnforcementMode.None => SystemScriptFileEnforcement.Allow, SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit, SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained, _ => SystemScriptFileEnforcement.Block, }; }
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED: return SystemScriptFileEnforcement.Block; 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 WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX: return SystemScriptFileEnforcement.AllowConstrained; 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: // Fall through to legacy system policy checks. System.Diagnostics.Debug.Assert(false, $"Unknown execution policy returned from WldCanExecute: {canExecuteResult}"); break; } } PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult);
// 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) if (hr >= 0) { // Fall back to legacy system policy checks. s_wldpCanExecuteAvailable = false; errorException = ex; 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 (errorException != null) { PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, errorException.HResult, 0); } // If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks. }
// Original (legacy) WDAC and AppLocker system checks. if (systemLockdownPolicy == SystemEnforcementMode.None) catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException) { return SystemScriptFileEnforcement.None; // Fall back to legacy system policy checks. s_wldpCanExecuteAvailable = false; PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, ex.HResult, 0); }
// Check policy for file. switch (SystemPolicy.GetLockdownPolicy(filePath, fileHandle)) { case SystemEnforcementMode.Enforce: // File is not allowed by policy enforcement and must run in CL mode. return SystemScriptFileEnforcement.AllowConstrained;
case SystemEnforcementMode.Audit: // File is allowed but would be run in CL mode if policy was enforced and not audit. return SystemScriptFileEnforcement.AllowConstrainedAudit;
case SystemEnforcementMode.None: // No restrictions, file will run in FL mode. return SystemScriptFileEnforcement.Allow;
default: System.Diagnostics.Debug.Assert(false, "GetFilePolicyEnforcement: Unknown SystemEnforcementMode."); return SystemScriptFileEnforcement.Block; } result = default; return false; }
/// <summary> Expand All @@ -267,9 +264,32 @@ public static SystemScriptFileEnforcement GetFilePolicyEnforcement( /// <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; } Expand All @@ -281,29 +301,28 @@ public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle ha 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", Expand Down Expand Up @@ -558,7 +577,7 @@ private static SaferPolicy TestSaferPolicy(string testPathScript, string testPat return result; }
private static SystemEnforcementMode GetDebugLockdownPolicy(string path) private static SystemEnforcementMode GetDebugLockdownPolicy(string path, out SystemScriptFileEnforcement modernEnforcement) { s_allowDebugOverridePolicy = true;
Expand All @@ -569,10 +588,19 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path) // 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); }
Expand All @@ -582,10 +610,13 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path) 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; }
Expand Down