Skip to content

Commit f6ac9bb

Browse files
Add support for new WDAC API (PowerShell#17247)
* Add support for new WDAC API * Fix non-Windows platform error * Fix code factor errors * Update cgmanifest json file * Fix test failure * Update src/System.Management.Automation/resources/SecuritySupportStrings.resx Co-authored-by: Dongbo Wang <dongbow@microsoft.com> Co-authored-by: Dongbo Wang <dongbow@microsoft.com>
1 parent 56e08dc commit f6ac9bb

File tree

4 files changed

+242
-21
lines changed

4 files changed

+242
-21
lines changed

src/System.Management.Automation/CoreCLR/CorePsStub.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,20 @@ internal static bool IsClassInApprovedList(Guid clsid)
457457
{
458458
throw new NotImplementedException("SystemPolicy.IsClassInApprovedList not implemented");
459459
}
460+
461+
/// <summary>
462+
/// Gets the system wide script file policy enforcement for an open file.
463+
/// Based on system WDAC (Windows Defender Application Control) or AppLocker policies.
464+
/// </summary>
465+
/// <param name="filePath">Script file path for policy check.</param>
466+
/// <param name="fileStream">FileStream object to script file path.</param>
467+
/// <retruns>Policy check result for script file.</returns>
468+
public static SystemScriptFileEnforcement GetFilePolicyEnforcement(
469+
string filePath,
470+
System.IO.FileStream fileStream)
471+
{
472+
return SystemScriptFileEnforcement.None;
473+
}
460474
}
461475

462476
/// <summary>
@@ -473,6 +487,32 @@ internal enum SystemEnforcementMode
473487
/// Enabled, enforce restrictions
474488
Enforce = 2
475489
}
490+
491+
/// <summary>
492+
/// System wide policy enforcement for a specific script file.
493+
/// </summary>
494+
public enum SystemScriptFileEnforcement
495+
{
496+
/// <summary>
497+
/// No policy enforcement.
498+
/// </summary>
499+
None = 0,
500+
501+
/// <summary>
502+
/// Script file is blocked from running.
503+
/// </summary>
504+
Block = 1,
505+
506+
/// <summary>
507+
/// Script file is allowed to run without restrictions (FullLanguage mode).
508+
/// </summary>
509+
Allow = 2,
510+
511+
/// <summary>
512+
/// Script file is allowed to run in ConstrainedLanguage mode only.
513+
/// </summary>
514+
AllowConstrained = 3
515+
}
476516
}
477517

478518
// Porting note: Tracing is absolutely not available on Linux

src/System.Management.Automation/engine/ExternalScriptInfo.cs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -516,32 +516,45 @@ private void ReadScriptContents()
516516
using (FileStream readerStream = new FileStream(_path, FileMode.Open, FileAccess.Read))
517517
{
518518
Encoding defaultEncoding = ClrFacade.GetDefaultEncoding();
519-
Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = readerStream.SafeFileHandle;
520519

521520
using (StreamReader scriptReader = new StreamReader(readerStream, defaultEncoding))
522521
{
523522
_scriptContents = scriptReader.ReadToEnd();
524523
_originalEncoding = scriptReader.CurrentEncoding;
525524

526-
// Check if this came from a trusted path. If so, set its language mode to FullLanguage.
527-
if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.None)
525+
// Check this file against any system wide enforcement policies.
526+
SystemScriptFileEnforcement filePolicyEnforcement = SystemPolicy.GetFilePolicyEnforcement(_path, readerStream);
527+
switch (filePolicyEnforcement)
528528
{
529-
SystemEnforcementMode scriptSpecificPolicy = SystemPolicy.GetLockdownPolicy(_path, safeFileHandle);
530-
if (scriptSpecificPolicy != SystemEnforcementMode.Enforce)
531-
{
532-
this.DefiningLanguageMode = PSLanguageMode.FullLanguage;
533-
}
534-
else
535-
{
536-
this.DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage;
537-
}
538-
}
539-
else
540-
{
541-
if (this.Context != null)
542-
{
543-
this.DefiningLanguageMode = this.Context.LanguageMode;
544-
}
529+
case SystemScriptFileEnforcement.None:
530+
if (Context != null)
531+
{
532+
DefiningLanguageMode = Context.LanguageMode;
533+
}
534+
535+
break;
536+
537+
case SystemScriptFileEnforcement.Allow:
538+
DefiningLanguageMode = PSLanguageMode.FullLanguage;
539+
break;
540+
541+
case SystemScriptFileEnforcement.AllowConstrained:
542+
DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage;
543+
break;
544+
545+
case SystemScriptFileEnforcement.Block:
546+
throw new PSSecurityException(
547+
string.Format(
548+
Globalization.CultureInfo.CurrentUICulture,
549+
SecuritySupportStrings.ScriptFileBlockedBySystemPolicy,
550+
_path));
551+
552+
default:
553+
throw new PSSecurityException(
554+
string.Format(
555+
Globalization.CultureInfo.CurrentUICulture,
556+
SecuritySupportStrings.UnknownSystemScriptFileEnforcement,
557+
filePolicyEnforcement));
545558
}
546559
}
547560
}

src/System.Management.Automation/resources/SecuritySupportStrings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,10 @@
153153
<data name="InvalidSessionKey" xml:space="preserve">
154154
<value>Invalid session key data.</value>
155155
</data>
156+
<data name="ScriptFileBlockedBySystemPolicy" xml:space="preserve">
157+
<value>Script file, '{0}', is blocked from running by system policy.</value>
158+
</data>
159+
<data name="UnknownSystemScriptFileEnforcement" xml:space="preserve">
160+
<value>An unknown script file policy enforcement value was returned: {0}.</value>
161+
</data>
156162
</root>

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

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,32 @@
1212

1313
namespace System.Management.Automation.Security
1414
{
15+
/// <summary>
16+
/// System wide policy enforcement for a specific script file.
17+
/// </summary>
18+
public enum SystemScriptFileEnforcement
19+
{
20+
/// <summary>
21+
/// No policy enforcement.
22+
/// </summary>
23+
None = 0,
24+
25+
/// <summary>
26+
/// Script file is blocked from running.
27+
/// </summary>
28+
Block = 1,
29+
30+
/// <summary>
31+
/// Script file is allowed to run without restrictions (FullLanguage mode).
32+
/// </summary>
33+
Allow = 2,
34+
35+
/// <summary>
36+
/// Script file is allowed to run in ConstrainedLanguage mode only.
37+
/// </summary>
38+
AllowConstrained = 3
39+
}
40+
1541
/// <summary>
1642
/// How the policy is being enforced.
1743
/// </summary>
@@ -71,6 +97,90 @@ public static SystemEnforcementMode GetSystemLockdownPolicy()
7197
private static readonly object s_systemLockdownPolicyLock = new object();
7298
private static SystemEnforcementMode? s_systemLockdownPolicy = null;
7399
private static bool s_allowDebugOverridePolicy = false;
100+
private static bool s_wldpCanExecuteAvailable = true;
101+
102+
/// <summary>
103+
/// Gets the system wide script file policy enforcement for an open file.
104+
/// Based on system WDAC (Windows Defender Application Control) or AppLocker policies.
105+
/// </summary>
106+
/// <param name="filePath">Script file path for policy check.</param>
107+
/// <param name="fileStream">FileStream object to script file path.</param>
108+
/// <returns>Policy check result for script file.</returns>
109+
public static SystemScriptFileEnforcement GetFilePolicyEnforcement(
110+
string filePath,
111+
System.IO.FileStream fileStream)
112+
{
113+
SafeHandle fileHandle = fileStream.SafeFileHandle;
114+
115+
// First check latest WDAC APIs if available.
116+
if (s_wldpCanExecuteAvailable)
117+
{
118+
try
119+
{
120+
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
121+
string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}";
122+
123+
int hr = WldpNativeMethods.WldpCanExecuteFile(
124+
host: PowerShellHost,
125+
options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE,
126+
fileHandle: fileHandle.DangerousGetHandle(),
127+
auditInfo: auditMsg,
128+
result: out WLDP_EXECUTION_POLICY canExecuteResult);
129+
130+
if (hr >= 0)
131+
{
132+
switch (canExecuteResult)
133+
{
134+
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED:
135+
return SystemScriptFileEnforcement.Allow;
136+
137+
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED:
138+
return SystemScriptFileEnforcement.Block;
139+
140+
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX:
141+
return SystemScriptFileEnforcement.AllowConstrained;
142+
143+
default:
144+
// Fall through to legacy system policy checks.
145+
System.Diagnostics.Debug.Assert(false, $"Unknown execution policy returned from WldCanExecute: {canExecuteResult}");
146+
break;
147+
}
148+
}
149+
150+
// If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks.
151+
}
152+
catch (DllNotFoundException)
153+
{
154+
// Fall back to legacy system policy checks.
155+
s_wldpCanExecuteAvailable = false;
156+
}
157+
catch (EntryPointNotFoundException)
158+
{
159+
// Fall back to legacy system policy checks.
160+
s_wldpCanExecuteAvailable = false;
161+
}
162+
}
163+
164+
// Original (legacy) WDAC and AppLocker system checks.
165+
if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.None)
166+
{
167+
switch (SystemPolicy.GetLockdownPolicy(filePath, fileHandle))
168+
{
169+
case SystemEnforcementMode.Enforce:
170+
return SystemScriptFileEnforcement.AllowConstrained;
171+
172+
case SystemEnforcementMode.None:
173+
case SystemEnforcementMode.Audit:
174+
return SystemScriptFileEnforcement.Allow;
175+
176+
default:
177+
System.Diagnostics.Debug.Assert(false, "GetFilePolicyEnforcement: Unknown SystemEnforcementMode.");
178+
return SystemScriptFileEnforcement.Block;
179+
}
180+
}
181+
182+
return SystemScriptFileEnforcement.None;
183+
}
74184

75185
/// <summary>
76186
/// Gets lockdown policy as applied to a file.
@@ -546,18 +656,66 @@ internal struct WLDP_HOST_INFORMATION
546656
internal IntPtr hSource;
547657
}
548658

659+
/// <summary>
660+
/// Options for WldpCanExecuteFile method.
661+
/// </summary>
662+
[Flags]
663+
internal enum WLDP_EXECUTION_EVALUATION_OPTIONS
664+
{
665+
WLDP_EXECUTION_EVALUATION_OPTION_NONE = 0x0,
666+
WLDP_EXECUTION_EVALUATION_OPTION_EXECUTE_IN_INTERACTIVE_SESSION = 0x1
667+
}
668+
669+
/// <summary>
670+
/// Results from WldpCanExecuteFile method.
671+
/// </summary>
672+
internal enum WLDP_EXECUTION_POLICY
673+
{
674+
WLDP_CAN_EXECUTE_BLOCKED = 0,
675+
WLDP_CAN_EXECUTE_ALLOWED = 1,
676+
WLDP_CAN_EXECUTE_REQUIRE_SANDBOX = 2
677+
}
678+
679+
/// <summary>
680+
/// Powershell Script Host.
681+
/// </summary>
682+
internal static readonly Guid PowerShellHost = new Guid("8E9AAA7C-198B-4879-AE41-A50D47AD6458");
683+
549684
/// <summary>
550685
/// Native methods for dealing with the lockdown policy.
551686
/// </summary>
552687
internal static class WldpNativeMethods
553688
{
689+
/// <summary>
690+
/// Returns a WLDP_EXECUTION_POLICY enum value indicating if and how a script file
691+
/// should be executed.
692+
/// </summary>
693+
/// <param name="host">Host guid.</param>
694+
/// <param name="options">Evaluation options.</param>
695+
/// <param name="fileHandle">Evaluated file handle.</param>
696+
/// <param name="auditInfo">Auditing information string.</param>
697+
/// <param name="result">Evaluation result.</param>
698+
/// <returns>HResult value.</returns>
699+
[DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
700+
[DllImportAttribute("wldp.dll", EntryPoint = "WldpCanExecuteFile")]
701+
internal static extern int WldpCanExecuteFile(
702+
Guid host,
703+
WLDP_EXECUTION_EVALUATION_OPTIONS options,
704+
IntPtr fileHandle,
705+
[MarshalAs(UnmanagedType.LPWStr)]
706+
string auditInfo,
707+
out WLDP_EXECUTION_POLICY result);
708+
554709
/// Return Type: HRESULT->LONG->int
555710
/// pHostInformation: PWLDP_HOST_INFORMATION->_WLDP_HOST_INFORMATION*
556711
/// pdwLockdownState: PDWORD->DWORD*
557712
/// dwFlags: DWORD->unsigned int
558713
[DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
559714
[DllImportAttribute("wldp.dll", EntryPoint = "WldpGetLockdownPolicy")]
560-
internal static extern int WldpGetLockdownPolicy(ref WLDP_HOST_INFORMATION pHostInformation, ref uint pdwLockdownState, uint dwFlags);
715+
internal static extern int WldpGetLockdownPolicy(
716+
ref WLDP_HOST_INFORMATION pHostInformation,
717+
ref uint pdwLockdownState,
718+
uint dwFlags);
561719

562720
/// Return Type: HRESULT->LONG->int
563721
/// rclsid: IID*
@@ -566,7 +724,11 @@ internal static class WldpNativeMethods
566724
/// dwFlags: DWORD->unsigned int
567725
[DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
568726
[DllImportAttribute("wldp.dll", EntryPoint = "WldpIsClassInApprovedList")]
569-
internal static extern int WldpIsClassInApprovedList(ref Guid rclsid, ref WLDP_HOST_INFORMATION pHostInformation, ref int ptIsApproved, uint dwFlags);
727+
internal static extern int WldpIsClassInApprovedList(
728+
ref Guid rclsid,
729+
ref WLDP_HOST_INFORMATION pHostInformation,
730+
ref int ptIsApproved,
731+
uint dwFlags);
570732

571733
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
572734
internal static extern int SHGetKnownFolderPath(

0 commit comments

Comments
 (0)