Skip to content

Commit 3e5477f

Browse files
[release/v7.4] Fallback to AppLocker after WldpCanExecuteFile (PowerShell#25229)
Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
1 parent f1e27f9 commit 3e5477f

1 file changed

Lines changed: 114 additions & 83 deletions

File tree

src/System.Management.Automation/security/wldpNativeMethods.cs

Lines changed: 114 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
#if !UNIX
88

9+
using System.Diagnostics;
910
using System.Diagnostics.CodeAnalysis;
1011
using System.Management.Automation.Internal;
1112
using System.Management.Automation.Runspaces;
@@ -148,7 +149,7 @@ public static SystemEnforcementMode GetSystemLockdownPolicy()
148149
{
149150
lock (s_systemLockdownPolicyLock)
150151
{
151-
s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null);
152+
s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null, out _);
152153
}
153154
}
154155

@@ -172,93 +173,89 @@ public static SystemScriptFileEnforcement GetFilePolicyEnforcement(
172173
System.IO.FileStream fileStream)
173174
{
174175
SafeHandle fileHandle = fileStream.SafeFileHandle;
175-
var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy();
176+
SystemEnforcementMode systemLockdownPolicy = GetSystemLockdownPolicy();
176177

177178
// First check latest WDAC APIs if available.
178-
// Revert to legacy APIs if system policy is in AUDIT mode or debug hook is in effect.
179-
Exception errorException = null;
180-
if (s_wldpCanExecuteAvailable && systemLockdownPolicy == SystemEnforcementMode.Enforce)
179+
if (systemLockdownPolicy is SystemEnforcementMode.Enforce
180+
&& s_wldpCanExecuteAvailable
181+
&& TryGetWldpCanExecuteFileResult(filePath, fileHandle, out SystemScriptFileEnforcement wldpFilePolicy))
181182
{
182-
try
183-
{
184-
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
185-
string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}";
183+
return GetLockdownPolicy(filePath, fileHandle, wldpFilePolicy);
184+
}
186185

187-
int hr = WldpNativeMethods.WldpCanExecuteFile(
188-
host: PowerShellHost,
189-
options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE,
190-
fileHandle: fileHandle.DangerousGetHandle(),
191-
auditInfo: auditMsg,
192-
result: out WLDP_EXECUTION_POLICY canExecuteResult);
186+
// Failed to invoke WldpCanExecuteFile, revert to legacy APIs.
187+
if (systemLockdownPolicy is SystemEnforcementMode.None)
188+
{
189+
return SystemScriptFileEnforcement.None;
190+
}
193191

194-
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult);
192+
// WldpCanExecuteFile was invoked successfully so we can skip running
193+
// legacy WDAC APIs. AppLocker must still be checked in case it is more
194+
// strict than the current WDAC policy.
195+
return GetLockdownPolicy(filePath, fileHandle, canExecuteResult: null);
196+
}
195197

196-
if (hr >= 0)
197-
{
198-
switch (canExecuteResult)
199-
{
200-
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED:
201-
return SystemScriptFileEnforcement.Allow;
198+
private static SystemScriptFileEnforcement ConvertToModernFileEnforcement(SystemEnforcementMode legacyMode)
199+
{
200+
return legacyMode switch
201+
{
202+
SystemEnforcementMode.None => SystemScriptFileEnforcement.Allow,
203+
SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit,
204+
SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained,
205+
_ => SystemScriptFileEnforcement.Block,
206+
};
207+
}
202208

203-
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED:
204-
return SystemScriptFileEnforcement.Block;
209+
private static bool TryGetWldpCanExecuteFileResult(string filePath, SafeHandle fileHandle, out SystemScriptFileEnforcement result)
210+
{
211+
try
212+
{
213+
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
214+
string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}";
205215

206-
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX:
207-
return SystemScriptFileEnforcement.AllowConstrained;
216+
int hr = WldpNativeMethods.WldpCanExecuteFile(
217+
host: PowerShellHost,
218+
options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE,
219+
fileHandle: fileHandle.DangerousGetHandle(),
220+
auditInfo: auditMsg,
221+
result: out WLDP_EXECUTION_POLICY canExecuteResult);
208222

209-
default:
210-
// Fall through to legacy system policy checks.
211-
System.Diagnostics.Debug.Assert(false, $"Unknown execution policy returned from WldCanExecute: {canExecuteResult}");
212-
break;
213-
}
214-
}
223+
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult);
215224

216-
// If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks.
217-
}
218-
catch (DllNotFoundException ex)
219-
{
220-
// Fall back to legacy system policy checks.
221-
s_wldpCanExecuteAvailable = false;
222-
errorException = ex;
223-
}
224-
catch (EntryPointNotFoundException ex)
225+
if (hr >= 0)
225226
{
226-
// Fall back to legacy system policy checks.
227-
s_wldpCanExecuteAvailable = false;
228-
errorException = ex;
227+
switch (canExecuteResult)
228+
{
229+
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED:
230+
result = SystemScriptFileEnforcement.Allow;
231+
return true;
232+
233+
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED:
234+
result = SystemScriptFileEnforcement.Block;
235+
return true;
236+
237+
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX:
238+
result = SystemScriptFileEnforcement.AllowConstrained;
239+
return true;
240+
241+
default:
242+
// Fall through to legacy system policy checks.
243+
Debug.Assert(false, $"Unknown policy result returned from WldCanExecute: {canExecuteResult}");
244+
break;
245+
}
229246
}
230247

231-
if (errorException != null)
232-
{
233-
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, errorException.HResult, 0);
234-
}
248+
// If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks.
235249
}
236-
237-
// Original (legacy) WDAC and AppLocker system checks.
238-
if (systemLockdownPolicy == SystemEnforcementMode.None)
250+
catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException)
239251
{
240-
return SystemScriptFileEnforcement.None;
252+
// Fall back to legacy system policy checks.
253+
s_wldpCanExecuteAvailable = false;
254+
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, ex.HResult, 0);
241255
}
242256

243-
// Check policy for file.
244-
switch (SystemPolicy.GetLockdownPolicy(filePath, fileHandle))
245-
{
246-
case SystemEnforcementMode.Enforce:
247-
// File is not allowed by policy enforcement and must run in CL mode.
248-
return SystemScriptFileEnforcement.AllowConstrained;
249-
250-
case SystemEnforcementMode.Audit:
251-
// File is allowed but would be run in CL mode if policy was enforced and not audit.
252-
return SystemScriptFileEnforcement.AllowConstrainedAudit;
253-
254-
case SystemEnforcementMode.None:
255-
// No restrictions, file will run in FL mode.
256-
return SystemScriptFileEnforcement.Allow;
257-
258-
default:
259-
System.Diagnostics.Debug.Assert(false, "GetFilePolicyEnforcement: Unknown SystemEnforcementMode.");
260-
return SystemScriptFileEnforcement.Block;
261-
}
257+
result = default;
258+
return false;
262259
}
263260

264261
/// <summary>
@@ -267,9 +264,32 @@ public static SystemScriptFileEnforcement GetFilePolicyEnforcement(
267264
/// <returns>An EnforcementMode that describes policy.</returns>
268265
public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle handle)
269266
{
267+
SystemScriptFileEnforcement modernMode = GetLockdownPolicy(path, handle, canExecuteResult: null);
268+
Debug.Assert(
269+
modernMode is not SystemScriptFileEnforcement.Block,
270+
"Block should never be converted to legacy file enforcement.");
271+
272+
return modernMode switch
273+
{
274+
SystemScriptFileEnforcement.Block => SystemEnforcementMode.Enforce,
275+
SystemScriptFileEnforcement.AllowConstrained => SystemEnforcementMode.Enforce,
276+
SystemScriptFileEnforcement.AllowConstrainedAudit => SystemEnforcementMode.Audit,
277+
SystemScriptFileEnforcement.Allow => SystemEnforcementMode.None,
278+
SystemScriptFileEnforcement.None => SystemEnforcementMode.None,
279+
_ => throw new ArgumentOutOfRangeException(nameof(modernMode)),
280+
};
281+
}
282+
283+
private static SystemScriptFileEnforcement GetLockdownPolicy(
284+
string path,
285+
SafeHandle handle,
286+
SystemScriptFileEnforcement? canExecuteResult)
287+
{
288+
SystemScriptFileEnforcement wldpFilePolicy = canExecuteResult
289+
?? ConvertToModernFileEnforcement(GetWldpPolicy(path, handle));
290+
270291
// Check the WLDP File policy via API
271-
var wldpFilePolicy = GetWldpPolicy(path, handle);
272-
if (wldpFilePolicy == SystemEnforcementMode.Enforce)
292+
if (wldpFilePolicy is SystemScriptFileEnforcement.Block or SystemScriptFileEnforcement.AllowConstrained)
273293
{
274294
return wldpFilePolicy;
275295
}
@@ -281,29 +301,28 @@ public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle ha
281301
var appLockerFilePolicy = GetAppLockerPolicy(path, handle);
282302
if (appLockerFilePolicy == SystemEnforcementMode.Enforce)
283303
{
284-
return appLockerFilePolicy;
304+
return ConvertToModernFileEnforcement(appLockerFilePolicy);
285305
}
286306

287307
// At this point, LockdownPolicy = Audit or Allowed.
288308
// If there was a WLDP policy, but WLDP didn't block it,
289309
// then it was explicitly allowed. Therefore, return the result for the file.
290-
SystemEnforcementMode systemWldpPolicy = s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None);
291-
if ((systemWldpPolicy == SystemEnforcementMode.Audit) ||
292-
(systemWldpPolicy == SystemEnforcementMode.Enforce))
310+
if (s_cachedWldpSystemPolicy is SystemEnforcementMode.Audit or SystemEnforcementMode.Enforce
311+
|| wldpFilePolicy is SystemScriptFileEnforcement.AllowConstrainedAudit)
293312
{
294313
return wldpFilePolicy;
295314
}
296315

297316
// If there was a system-wide AppLocker policy, but AppLocker didn't block it,
298317
// then return AppLocker's status.
299-
if (s_cachedSaferSystemPolicy.GetValueOrDefault(SaferPolicy.Allowed) ==
300-
SaferPolicy.Disallowed)
318+
if (s_cachedSaferSystemPolicy is SaferPolicy.Disallowed)
301319
{
302-
return appLockerFilePolicy;
320+
return ConvertToModernFileEnforcement(appLockerFilePolicy);
303321
}
304322

305323
// If it's not set to 'Enforce' by the platform, allow debug overrides
306-
return GetDebugLockdownPolicy(path);
324+
GetDebugLockdownPolicy(path, out SystemScriptFileEnforcement debugPolicy);
325+
return debugPolicy;
307326
}
308327

309328
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
@@ -558,7 +577,7 @@ private static SaferPolicy TestSaferPolicy(string testPathScript, string testPat
558577
return result;
559578
}
560579

561-
private static SystemEnforcementMode GetDebugLockdownPolicy(string path)
580+
private static SystemEnforcementMode GetDebugLockdownPolicy(string path, out SystemScriptFileEnforcement modernEnforcement)
562581
{
563582
s_allowDebugOverridePolicy = true;
564583

@@ -569,10 +588,19 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path)
569588
// check so that we can actually put it in the filename during testing.
570589
if (path.Contains("System32", StringComparison.OrdinalIgnoreCase))
571590
{
591+
modernEnforcement = SystemScriptFileEnforcement.Allow;
572592
return SystemEnforcementMode.None;
573593
}
574594

575595
// No explicit debug allowance for the file, so return the system policy if there is one.
596+
modernEnforcement = s_systemLockdownPolicy switch
597+
{
598+
SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained,
599+
SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit,
600+
SystemEnforcementMode.None => SystemScriptFileEnforcement.None,
601+
_ => SystemScriptFileEnforcement.None,
602+
};
603+
576604
return s_systemLockdownPolicy.GetValueOrDefault(SystemEnforcementMode.None);
577605
}
578606

@@ -582,10 +610,13 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path)
582610
if (result != null)
583611
{
584612
pdwLockdownState = LanguagePrimitives.ConvertTo<uint>(result);
585-
return GetLockdownPolicyForResult(pdwLockdownState);
613+
SystemEnforcementMode policy = GetLockdownPolicyForResult(pdwLockdownState);
614+
modernEnforcement = ConvertToModernFileEnforcement(policy);
615+
return policy;
586616
}
587617

588618
// If the system-wide debug policy had no preference, then there is no enforcement.
619+
modernEnforcement = SystemScriptFileEnforcement.None;
589620
return SystemEnforcementMode.None;
590621
}
591622

0 commit comments

Comments
 (0)