using System.Management.Automation.Internal;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
namespace System.Management.Automation.Security
{
///
/// How the policy is being enforced.
///
///
// Internal Note: Current code that consumes this enum assumes that anything but 'Enforce' means
// that the script is allowed, and that a system lockdown policy that is anything but 'None' means
// that the API should be called again for individual files. If any elements are added to this enum,
// callers of the GetLockdownPolicy() should be reviewed.
public enum SystemEnforcementMode
{
/// Not enforced at all
None = 0,
/// Enabled - allow, but audit
Audit = 1,
/// Enabled, enforce restrictions
Enforce = 2
}
///
/// Support class for dealing with the Windows Lockdown Policy,
/// Device Guard, and Constrained PowerShell
///
public sealed class SystemPolicy
{
private SystemPolicy()
{
}
///
/// Gets the system lockdown policy
///
/// An EnforcementMode that describes the system policy
public static SystemEnforcementMode GetSystemLockdownPolicy()
{
if (s_wasSystemPolicyDebugPolicy || (s_systemLockdownPolicy == null))
{
lock (s_systemLockdownPolicyLock)
{
if (s_wasSystemPolicyDebugPolicy || (s_systemLockdownPolicy == null))
{
s_systemLockdownPolicy = GetLockdownPolicy(null, null);
}
}
}
return s_systemLockdownPolicy.Value;
}
private static object s_systemLockdownPolicyLock = new Object();
private static Nullable s_systemLockdownPolicy = null;
private static bool s_wasSystemPolicyDebugPolicy = false;
///
/// Gets lockdown policy as applied to a file
///
/// An EnforcementMode that describes policy
public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle handle)
{
// Check the WLDP API
SystemEnforcementMode lockdownPolicy = GetWldpPolicy(path, handle);
if (lockdownPolicy == SystemEnforcementMode.Enforce)
{
return lockdownPolicy;
}
// 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.Enforce) ||
(systemWldpPolicy == SystemEnforcementMode.Audit))
{
return lockdownPolicy;
}
// Check the AppLocker API
lockdownPolicy = GetAppLockerPolicy(path, handle);
if (lockdownPolicy == SystemEnforcementMode.Enforce)
{
return lockdownPolicy;
}
// 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)
{
return lockdownPolicy;
}
// If it's not set to 'Enforce' by the platform, allow debug overrides
return GetDebugLockdownPolicy(path);
}
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")]
private static SystemEnforcementMode GetWldpPolicy(string path, SafeHandle handle)
{
// If the WLDP assembly is missing (such as windows 7 or down OS), return default/None to skip WLDP valification
if (s_hadMissingWldpAssembly || !IO.File.Exists(IO.Path.Combine(Environment.SystemDirectory, "wldp.dll")))
{
s_hadMissingWldpAssembly = true;
return s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None);
}
// If path is NULL, see if we have the cached system-wide lockdown policy.
if (String.IsNullOrEmpty(path))
{
if ((s_cachedWldpSystemPolicy != null) && (!InternalTestHooks.BypassAppLockerPolicyCaching))
{
return s_cachedWldpSystemPolicy.Value;
}
}
try
{
WLDP_HOST_INFORMATION hostInformation = new WLDP_HOST_INFORMATION();
hostInformation.dwRevision = WldpNativeConstants.WLDP_HOST_INFORMATION_REVISION;
hostInformation.dwHostId = WLDP_HOST_ID.WLDP_HOST_ID_POWERSHELL;
if (!String.IsNullOrEmpty(path))
{
hostInformation.szSource = path;
if (handle != null)
{
IntPtr fileHandle = IntPtr.Zero;
fileHandle = handle.DangerousGetHandle();
hostInformation.hSource = fileHandle;
}
}
uint pdwLockdownState = 0;
int result = WldpNativeMethods.WldpGetLockdownPolicy(ref hostInformation, ref pdwLockdownState, 0);
if (result >= 0)
{
SystemEnforcementMode resultingLockdownPolicy = GetLockdownPolicyForResult(pdwLockdownState);
// If this is a query for the system-wide lockdown policy, cache it.
if (String.IsNullOrEmpty(path))
{
s_cachedWldpSystemPolicy = resultingLockdownPolicy;
}
return resultingLockdownPolicy;
}
else
{
// API failure?
return SystemEnforcementMode.Enforce;
}
}
catch (DllNotFoundException)
{
s_hadMissingWldpAssembly = true;
return s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None);
}
}
private static SystemEnforcementMode? s_cachedWldpSystemPolicy = null;
private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle handle)
{
SaferPolicy result = SaferPolicy.Disallowed;
// If path is NULL, we're looking for the system-wide lockdown policy.
// Since there is no way to get that from AppLocker, we will test the policy
// against a random non-existent script and module. If that is allowed, then there is
// no AppLocker script policy.
if (String.IsNullOrEmpty(path))
{
if ((s_cachedSaferSystemPolicy != null) && (!InternalTestHooks.BypassAppLockerPolicyCaching))
{
result = s_cachedSaferSystemPolicy.Value;
}
else
{
//
// Temp path can sometimes be deleted. While many places in PowerShell depend on its existence,
// this one can crash PowerShell.
// A less sensitive implementation will be possible once AppLocker allows validation of files that
// don't exist.
//
string testPathScript = null;
string testPathModule = null;
try
{
// Start with the current profile temp path.
string tempPath = IO.Path.GetTempPath();
int iteration = 0;
while (iteration++ < 2)
{
bool error = false;
try
{
if (!IO.Directory.Exists(tempPath))
{
IO.Directory.CreateDirectory(tempPath);
}
testPathScript = IO.Path.Combine(tempPath, IO.Path.GetRandomFileName() + ".ps1");
testPathModule = IO.Path.Combine(tempPath, IO.Path.GetRandomFileName() + ".psm1");
// AppLocker fails when you try to check a policy on a file
// with no content. So create a scratch file and test on that.
IO.File.WriteAllText(testPathScript, "1");
IO.File.WriteAllText(testPathModule, "1");
}
catch (System.IO.IOException)
{
if (iteration == 2) throw;
error = true;
}
catch (System.UnauthorizedAccessException)
{
if (iteration == 2) throw;
error = true;
}
catch (System.Security.SecurityException)
{
if (iteration == 2) throw;
error = true;
}
if (!error) { break; }
// Try again with the AppData\LocalLow\Temp path using known folder id:
// https://msdn.microsoft.com/en-us/library/dd378457.aspx
Guid AppDatalocalLowFolderId = new Guid("A520A1A4-1780-4FF6-BD18-167343C5AF16");
tempPath = GetKnownFolderPath(AppDatalocalLowFolderId) + @"\Temp";
} // end while loop
// Test policy.
result = TestSaferPolicy(testPathScript, testPathModule);
}
catch (System.IO.IOException)
{
// If we fail to test the policy, assume the default.
result = SaferPolicy.Disallowed;
}
catch (System.UnauthorizedAccessException)
{
// This can happen during thread impersonation if the profile temp paths are not accessible.
// Allow policy if impersonated, otherwise disallow.
result =
(System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ?
SaferPolicy.Allowed : SaferPolicy.Disallowed;
}
catch (ArgumentException)
{
// This is for IO.Path.GetTempPath() call when temp paths are not accessible.
result =
(System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ?
SaferPolicy.Allowed : SaferPolicy.Disallowed;
}
finally
{
if (IO.File.Exists(testPathScript)) { IO.File.Delete(testPathScript); }
if (IO.File.Exists(testPathModule)) { IO.File.Delete(testPathModule); }
}
s_cachedSaferSystemPolicy = result;
}
if (result == SaferPolicy.Disallowed)
{
return SystemEnforcementMode.Enforce;
}
else
{
return SystemEnforcementMode.None;
}
}
else
{
// We got a path. Return the result for that path.
result = SecuritySupport.GetSaferPolicy(path, handle);
if (result == SaferPolicy.Disallowed)
{
return SystemEnforcementMode.Enforce;
}
return SystemEnforcementMode.None;
}
}
private static SaferPolicy? s_cachedSaferSystemPolicy = null;
private static string GetKnownFolderPath(Guid knownFolderId)
{
IntPtr pszPath = IntPtr.Zero;
try
{
int hr = WldpNativeMethods.SHGetKnownFolderPath(knownFolderId, 0, IntPtr.Zero, out pszPath);
if (hr >= 0)
{
return Marshal.PtrToStringAuto(pszPath);
}
throw new System.IO.IOException();
}
finally
{
if (pszPath != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pszPath);
}
}
}
private static SaferPolicy TestSaferPolicy(string testPathScript, string testPathModule)
{
SaferPolicy result = SecuritySupport.GetSaferPolicy(testPathScript, null);
if (result == SaferPolicy.Disallowed)
{
result = SecuritySupport.GetSaferPolicy(testPathModule, null);
}
return result;
}
private static SystemEnforcementMode GetDebugLockdownPolicy(string path)
{
if (PsUtils.IsRunningOnProcessorArchitectureARM())
{
return SystemEnforcementMode.Enforce;
}
s_wasSystemPolicyDebugPolicy = true;
// Support fall-back debug hook for path exclusions on non-WOA platforms
if (path != null)
{
// Assume everything under SYSTEM32 is trusted, with a purposefully sloppy
// check so that we can actually put it in the filename during testing.
if (path.IndexOf("System32", StringComparison.OrdinalIgnoreCase) >= 0)
{
return SystemEnforcementMode.None;
}
using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default))
{
using (RegistryKey wldpPolicy = hklm.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\CI\\TRSData"))
{
if (wldpPolicy != null)
{
object exclusionPathsKey = wldpPolicy.GetValue("TestPath");
wldpPolicy.Close();
hklm.Close();
if (exclusionPathsKey != null)
{
string[] exclusionPaths = (string[])exclusionPathsKey;
foreach (string exclusionPath in exclusionPaths)
{
if (path.IndexOf(exclusionPath, StringComparison.OrdinalIgnoreCase) >= 0)
{
return SystemEnforcementMode.None;
}
}
}
}
}
}
// No explicit debug allowance for the file, so return the system policy if there
// is one.
return s_systemLockdownPolicy.GetValueOrDefault(SystemEnforcementMode.None);
}
// Support fall-back debug hook for system-wide policy on non-WOA platforms
uint pdwLockdownState = 0;
Object result = Environment.GetEnvironmentVariable("__PSLockdownPolicy", EnvironmentVariableTarget.Machine);
if (result != null)
{
pdwLockdownState = LanguagePrimitives.ConvertTo(result);
return GetLockdownPolicyForResult(pdwLockdownState);
}
// If the system-wide debug policy had no preference, then there is no enforcement.
return SystemEnforcementMode.None;
}
private static bool s_hadMissingWldpAssembly = false;
///
/// Gets lockdown policy as applied to a COM object
///
/// True if the COM object is allowed, False otherwise.
internal static bool IsClassInApprovedList(Guid clsid)
{
try
{
WLDP_HOST_INFORMATION hostInformation = new WLDP_HOST_INFORMATION();
hostInformation.dwRevision = WldpNativeConstants.WLDP_HOST_INFORMATION_REVISION;
hostInformation.dwHostId = WLDP_HOST_ID.WLDP_HOST_ID_POWERSHELL;
int pIsApproved = 0;
int result = WldpNativeMethods.WldpIsClassInApprovedList(ref clsid, ref hostInformation, ref pIsApproved, 0);
if (result >= 0)
{
if (pIsApproved == 1)
{
// Hook for testability. If we've got an environmental override, say that ADODB.Parameter
// is not allowed.
// 0000050b-0000-0010-8000-00aa006d2ea4 = ADODB.Parameter
if (s_wasSystemPolicyDebugPolicy)
{
if (String.Equals(clsid.ToString(), "0000050b-0000-0010-8000-00aa006d2ea4", StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
return true;
}
}
return false;
}
catch (DllNotFoundException)
{
// Hook for testability. IsClassInApprovedList is only called when the system is in global lockdown mode,
// so this wouldn't be allowed in regular ConstrainedLanguage mode.
// f6d90f11-9c73-11d3-b32e-00c04f990bb4 = MSXML2.DOMDocument
if (String.Equals(clsid.ToString(), "f6d90f11-9c73-11d3-b32e-00c04f990bb4", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
}
private static SystemEnforcementMode GetLockdownPolicyForResult(uint pdwLockdownState)
{
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG) ==
SystemPolicy.WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG)
{
return SystemEnforcementMode.Audit;
}
else if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG) ==
WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG)
{
return SystemEnforcementMode.Enforce;
}
else
{
return SystemEnforcementMode.None;
}
}
internal static string DumpLockdownState(uint pdwLockdownState)
{
string returnValue = "";
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_DEFINED_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_DEFINED_FLAG)
{
returnValue += "WLDP_LOCKDOWN_DEFINED_FLAG\r\n";
}
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_SECUREBOOT_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_SECUREBOOT_FLAG)
{
returnValue += "WLDP_LOCKDOWN_SECUREBOOT_FLAG\r\n";
}
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_DEBUGPOLICY_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_DEBUGPOLICY_FLAG)
{
returnValue += "WLDP_LOCKDOWN_DEBUGPOLICY_FLAG\r\n";
}
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG)
{
returnValue += "WLDP_LOCKDOWN_UMCIENFORCE_FLAG\r\n";
}
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG)
{
returnValue += "WLDP_LOCKDOWN_UMCIAUDIT_FLAG\r\n";
}
return returnValue;
}
// Overrides for features that should only be enabled in debug mode
internal static bool XamlWorkflowSupported { get; set; }
///
/// Native constants for dealing with the lockdown policy
///
internal class WldpNativeConstants
{
internal const uint WLDP_HOST_INFORMATION_REVISION = 0x00000001;
internal const uint WLDP_LOCKDOWN_UNDEFINED = 0;
internal const uint WLDP_LOCKDOWN_DEFINED_FLAG = 0x80000000;
internal const uint WLDP_LOCKDOWN_SECUREBOOT_FLAG = 1;
internal const uint WLDP_LOCKDOWN_DEBUGPOLICY_FLAG = 2;
internal const uint WLDP_LOCKDOWN_UMCIENFORCE_FLAG = 4;
internal const uint WLDP_LOCKDOWN_UMCIAUDIT_FLAG = 8;
}
///
/// The different host IDs understood by the lockdown policy
///
internal enum WLDP_HOST_ID
{
WLDP_HOST_ID_UNKNOWN = 0,
WLDP_HOST_ID_GLOBAL = 1,
WLDP_HOST_ID_VBA = 2,
WLDP_HOST_ID_WSH = 3,
WLDP_HOST_ID_POWERSHELL = 4,
WLDP_HOST_ID_IE = 5,
WLDP_HOST_ID_MSI = 6,
WLDP_HOST_ID_MAX = 7,
}
///
/// Host information structure to contain the lockdown policy request
///
[StructLayoutAttribute(LayoutKind.Sequential)]
internal struct WLDP_HOST_INFORMATION
{
/// DWORD->unsigned int
internal uint dwRevision;
/// WLDP_HOST_ID->_WLDP_HOST_ID
internal WLDP_HOST_ID dwHostId;
/// PCWSTR->WCHAR*
[MarshalAsAttribute(UnmanagedType.LPWStr)]
internal string szSource;
// HANDLE->IntPtr
internal IntPtr hSource;
}
///
/// Native methods for dealing with the lockdown policy
///
internal class WldpNativeMethods
{
/// Return Type: HRESULT->LONG->int
/// pHostInformation: PWLDP_HOST_INFORMATION->_WLDP_HOST_INFORMATION*
/// pdwLockdownState: PDWORD->DWORD*
/// dwFlags: DWORD->unsigned int
[DllImportAttribute("wldp.dll", EntryPoint = "WldpGetLockdownPolicy")]
internal static extern int WldpGetLockdownPolicy(ref WLDP_HOST_INFORMATION pHostInformation, ref uint pdwLockdownState, uint dwFlags);
/// Return Type: HRESULT->LONG->int
/// rclsid: IID*
/// pHostInformation: PWLDP_HOST_INFORMATION->_WLDP_HOST_INFORMATION*
/// ptIsApproved: PBOOL->BOOL*
/// dwFlags: DWORD->unsigned int
[DllImportAttribute("wldp.dll", EntryPoint = "WldpIsClassInApprovedList")]
internal static extern int WldpIsClassInApprovedList(ref Guid rclsid, ref WLDP_HOST_INFORMATION pHostInformation, ref int ptIsApproved, uint dwFlags);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)]
Guid rfid,
int dwFlags,
IntPtr hToken,
out IntPtr pszPath);
}
}
}