-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Fallback to AppLocker after WldpCanExecuteFile
#24912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
TravisEz13
merged 13 commits into
PowerShell:master
from
SeeminglyScience:fix-applocker-fallback
Mar 24, 2025
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
349b98b
Fallback to AppLocker after `WldpCanExecuteFile`
SeeminglyScience b596f11
Check for Audit before last fallback to AL system
SeeminglyScience 57aa0c9
Remove unused method
SeeminglyScience 37081c8
Merge branch 'master' into fix-applocker-fallback
SeeminglyScience 25ac3a5
Update windows-ci.yml
TravisEz13 b873eef
Merge branch 'master' into fix-applocker-fallback
TravisEz13 ce887a0
Update src/System.Management.Automation/security/wldpNativeMethods.cs
SeeminglyScience 9ffd83e
Merge branch 'master' into fix-applocker-fallback
TravisEz13 ec1f958
Merge branch 'master' into fix-applocker-fallback
TravisEz13 c4b6ba4
Account for `None` results
SeeminglyScience 9a52b27
Map legacy `None` to modern `Allow`
SeeminglyScience fa6e00b
Fix debug policy logic for modern enforcement
SeeminglyScience e315419
Return None when new api isn't available + no LDM
SeeminglyScience File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() | |
| { | ||
| lock (s_systemLockdownPolicyLock) | ||
| { | ||
| s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null); | ||
| s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null, out _); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -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> | ||
|
|
@@ -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, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't/shouldn't this not be the case as per the above and it would be better to throw the exception in the default branch? |
||
| 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 @@ 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 | ||
TravisEz13 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| || 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 @@ 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; | ||
|
|
||
|
|
@@ -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); | ||
| } | ||
|
|
||
|
|
@@ -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; | ||
| } | ||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.