Consolidation of all Windows PowerShell work ported to PSCore6 (#8257)

Consolidation of all Windows PowerShell work ported to PSCore6

* Added ps1 file import restriction.  Refactored InvokeLanguageModeTestingSupportCmdlet to HelpersSecurity module
* JEA loop back fix.  Debugger running commands in CL mode.
* Support for new AMSI codes.  Changed to use AMSI buffer API.  Unhandled exception fix.
* Fixes for module bugs while running in ConstrainedLanguage mode
* Untrusted input tracking work
* Configuration keyword bug fix, PSRP protocol version check for reconstruct reconnect, Sharing InitialSessionState in runspace pool.
* Restricted remote session in UMCI, Applocker detection collision, Help command exposing functions, Null reference exception fix.
* Added mitigation for debugger function exposure
This commit is contained in:
Travis Plunk 2018-11-13 16:16:29 -08:00 committed by GitHub
parent 605994fbf4
commit 79f21b41de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 4519 additions and 414 deletions

View file

@ -74,6 +74,7 @@ namespace Microsoft.PowerShell.Commands
/// The source code of this generated type.
/// </summary>
[Parameter(Mandatory = true, Position = 0, ParameterSetName = FromSourceParameterSetName)]
[ValidateTrustedData]
public String TypeDefinition
{
get
@ -90,6 +91,7 @@ namespace Microsoft.PowerShell.Commands
/// The name of the type (class) used for auto-generated types.
/// </summary>
[Parameter(Mandatory = true, Position = 0, ParameterSetName = FromMemberParameterSetName)]
[ValidateTrustedData]
public String Name { get; set; }
/// <summary>
@ -137,6 +139,7 @@ namespace Microsoft.PowerShell.Commands
/// The path to the source code or DLL to load.
/// </summary>
[Parameter(Mandatory = true, Position = 0, ParameterSetName = FromPathParameterSetName)]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public string[] Path
{
@ -183,6 +186,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[Parameter(Mandatory = true, ParameterSetName = FromLiteralPathParameterSetName)]
[Alias("PSPath", "LP")]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public string[] LiteralPath
{
@ -269,6 +273,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[Parameter(Mandatory = true, ParameterSetName = FromAssemblyNameParameterSetName)]
[Alias("AN")]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public String[] AssemblyName { get; set; }

View file

@ -96,6 +96,7 @@ namespace Microsoft.PowerShell.Commands
/// The command allowed in the data file. If unspecified, then ConvertFrom-StringData is allowed.
/// </summary>
[Parameter]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]
public string[] SupportedCommand
{
@ -205,6 +206,13 @@ namespace Microsoft.PowerShell.Commands
else
{
variable.Value = result;
if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage)
{
// Mark untrusted values for assignments to 'Global:' variables, and 'Script:' variables in
// a module scope, if it's necessary.
ExecutionContext.MarkObjectAsUntrustedForVariableAssignment(variable, scope, Context.EngineSessionState);
}
}
}

View file

@ -20,6 +20,7 @@ namespace Microsoft.PowerShell.Commands
/// Command to execute.
/// </summary>
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)]
[ValidateTrustedData]
public string Command { get; set; }
#endregion parameters

View file

@ -941,6 +941,16 @@ namespace Microsoft.PowerShell.Commands
if (varValue != AutomationNull.Value)
{
matchingVariable.Value = varValue;
if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage)
{
// In 'ConstrainedLanguage' we want to monitor untrusted values assigned to 'Global:' variables
// and 'Script:' variables, because they may be set from 'ConstrainedLanguage' environment and
// referenced within trusted script block, and thus result in security issues.
// Here we are setting the value of an existing variable and don't know what scope this variable
// is from, so we mark the value as untrusted, regardless of the scope.
ExecutionContext.MarkObjectAsUntrusted(matchingVariable.Value);
}
}
if (Description != null)

View file

@ -28,6 +28,7 @@ namespace Microsoft.PowerShell.Commands
/// <summary> the number</summary>
[Parameter(ParameterSetName = netSetName, Mandatory = true, Position = 0)]
[ValidateTrustedData]
public string TypeName { get; set; } = null;
#if !UNIX
@ -36,6 +37,7 @@ namespace Microsoft.PowerShell.Commands
/// The ProgID of the Com object.
/// </summary>
[Parameter(ParameterSetName = "Com", Mandatory = true, Position = 0)]
[ValidateTrustedData]
public string ComObject { get; set; } = null;
#endif
@ -44,6 +46,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
/// <value></value>
[Parameter(ParameterSetName = netSetName, Mandatory = false, Position = 1)]
[ValidateTrustedData]
[Alias("Args")]
public object[] ArgumentList { get; set; } = null;
@ -58,6 +61,7 @@ namespace Microsoft.PowerShell.Commands
/// gets the properties to be set.
/// </summary>
[Parameter]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public IDictionary Property { get; set; }

View file

@ -1657,10 +1657,7 @@ namespace Microsoft.PowerShell
// If the system lockdown policy says "Enforce", do so. Do this after types / formatting, default functions, etc
// are loaded so that they are trusted. (Validation of their signatures is done in F&O)
if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce)
{
_runspaceRef.Runspace.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage;
}
Utils.EnforceSystemLockDownLanguageMode(_runspaceRef.Runspace.ExecutionContext);
string allUsersProfile = HostUtilities.GetFullProfileFileName(null, false);
string allUsersHostSpecificProfile = HostUtilities.GetFullProfileFileName(shellId, false);

View file

@ -165,6 +165,13 @@ namespace System.Management.Automation
throw new ArgumentTransformationMetadataException(e.Message, e);
}
// Track the flow of untrusted object during the conversion when it's called directly from ParameterBinderBase.
// When it's called from the override Transform method, the tracking is taken care of in the base type.
if (bindingParameters || bindingScriptCmdlet)
{
ExecutionContext.PropagateInputSource(inputData, result, engineIntrinsics.SessionState.Internal.LanguageMode);
}
return result;
}

View file

@ -1745,6 +1745,36 @@ namespace System.Management.Automation
string[] GetValidValues();
}
/// <summary>
/// Validates that each parameter argument is Trusted data
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ValidateTrustedDataAttribute : ValidateArgumentsAttribute
{
/// <summary>
/// Validates that the parameter argument is not untrusted
/// </summary>
/// <param name="arguments">Object to validate</param>
/// <param name="engineIntrinsics">
/// The engine APIs for the context under which the validation is being
/// evaluated.
/// </param>
/// <exception cref="ValidationMetadataException">
/// if the argument is untrusted.
/// </exception>
protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics)
{
if (ExecutionContext.HasEverUsedConstrainedLanguage &&
engineIntrinsics.SessionState.Internal.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage)
{
if (ExecutionContext.IsMarkedAsUntrusted(arguments))
{
throw new ValidationMetadataException("ValidateTrustedDataFailure", null, Metadata.ValidateTrustedDataFailure, arguments);
}
}
}
}
#region Allow
/// <summary>
@ -2151,6 +2181,27 @@ namespace System.Management.Automation
/// <exception cref="ArgumentTransformationMetadataException">should be thrown for any problems during transformation</exception>
public abstract object Transform(EngineIntrinsics engineIntrinsics, object inputData);
/// <summary>
/// Transform inputData and track the flow of untrusted object.
/// NOTE: All internal handling of ArgumentTransformationAttribute should use this method to track the trustworthiness of
/// the data input source by default.
/// </summary>
/// <remarks>
/// The default value for <paramref name="trackDataInputSource"/> is True.
/// You should stick to the default value for this parameter in most cases so that data input source is tracked during the transformation.
/// The only acceptable exception is when this method is used in Compiler or Binder where you can generate extra code to track input source
/// when it's necessary. This is to minimize the overhead when tracking is not needed.
/// </remarks>
internal object TransformInternal(EngineIntrinsics engineIntrinsics, object inputData, bool trackDataInputSource = true)
{
object result = Transform(engineIntrinsics, inputData);
if (trackDataInputSource && engineIntrinsics != null)
{
ExecutionContext.PropagateInputSource(inputData, result, engineIntrinsics.SessionState.Internal.LanguageMode);
}
return result;
}
/// <summary>
/// The property is only checked when:
/// a) The parameter is not mandatory

View file

@ -61,6 +61,9 @@ namespace System.Management.Automation
_helpFilePath = helpFile;
_PSSnapin = PSSnapin;
_options = ScopedItemOptions.ReadOnly;
// CmdletInfo represents cmdlets exposed from assemblies. On a locked down system, only trusted
// assemblies will be loaded. Therefore, a CmdletInfo instance will always be trusted.
this.DefiningLanguageMode = PSLanguageMode.FullLanguage;
}

View file

@ -308,7 +308,7 @@ namespace System.Management.Automation
try
{
// Tab expansion is called from a trusted function - we should apply ConstrainedLanguage if necessary.
if (ExecutionContext.HasEverUsedConstrainedLanguage)
if (completionContext.ExecutionContext.HasRunspaceEverUsedConstrainedLanguageMode)
{
previousLanguageMode = completionContext.ExecutionContext.LanguageMode;
completionContext.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage;

View file

@ -6741,7 +6741,7 @@ namespace System.Management.Automation
try
{
// ConstrainedLanguage has already been applied as necessary when we construct CompletionContext
Diagnostics.Assert(!(ExecutionContext.HasEverUsedConstrainedLanguage && executionContext.LanguageMode != PSLanguageMode.ConstrainedLanguage),
Diagnostics.Assert(!(executionContext.HasRunspaceEverUsedConstrainedLanguageMode && executionContext.LanguageMode != PSLanguageMode.ConstrainedLanguage),
"If the runspace has ever used constrained language mode, then the current language mode should already be set to constrained language");
// We're passing 'true' here for isTrustedInput, because SafeExprEvaluator ensures that the AST

View file

@ -963,7 +963,7 @@ namespace System.Management.Automation.Language
try
{
// Tab expansion is called from a trusted function - we should apply ConstrainedLanguage if necessary.
if (ExecutionContext.HasEverUsedConstrainedLanguage)
if (executionContext.HasRunspaceEverUsedConstrainedLanguageMode)
{
previousLanguageMode = executionContext.LanguageMode;
executionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage;

View file

@ -208,7 +208,44 @@ namespace System.Management.Automation
this.Command != null,
"CommandProcessor did not initialize Command\n" + this.CommandInfo.Name);
BindCommandLineParameters();
PSLanguageMode? oldLanguageMode = null;
bool? oldLangModeTransitionStatus = null;
try
{
var scriptCmdletInfo = this.CommandInfo as IScriptCommandInfo;
if (scriptCmdletInfo != null &&
scriptCmdletInfo.ScriptBlock.LanguageMode.HasValue &&
scriptCmdletInfo.ScriptBlock.LanguageMode != Context.LanguageMode)
{
// Set the language mode before parameter binding if it's necessary for a script cmdlet, so that the language
// mode is appropriately applied for evaluating parameter defaults and argument type conversion.
oldLanguageMode = Context.LanguageMode;
Context.LanguageMode = scriptCmdletInfo.ScriptBlock.LanguageMode.Value;
// If it's from ConstrainedLanguage to FullLanguage, indicate the transition before parameter binding takes place.
if (oldLanguageMode == PSLanguageMode.ConstrainedLanguage && Context.LanguageMode == PSLanguageMode.FullLanguage)
{
oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding;
Context.LanguageModeTransitionInParameterBinding = true;
}
}
BindCommandLineParameters();
}
finally
{
if (oldLanguageMode.HasValue)
{
// Revert to the original language mode after doing the parameter binding
Context.LanguageMode = oldLanguageMode.Value;
}
if (oldLangModeTransitionStatus.HasValue)
{
// Revert the transition state to old value after doing the parameter binding
Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus.Value;
}
}
}
protected override void OnSetCurrentScope()

View file

@ -8,6 +8,7 @@ using System.IO;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Runtime.CompilerServices;
using Microsoft.PowerShell;
@ -314,16 +315,38 @@ namespace System.Management.Automation
// caches. After that, the binding rules encode the language mode.
if (value == PSLanguageMode.ConstrainedLanguage)
{
ExecutionContext.HasEverUsedConstrainedLanguage = true;
HasRunspaceEverUsedConstrainedLanguageMode = true;
System.Management.Automation.Language.PSSetMemberBinder.InvalidateCache();
System.Management.Automation.Language.PSInvokeMemberBinder.InvalidateCache();
System.Management.Automation.Language.PSConvertBinder.InvalidateCache();
System.Management.Automation.Language.PSBinaryOperationBinder.InvalidateCache();
System.Management.Automation.Language.PSGetIndexBinder.InvalidateCache();
System.Management.Automation.Language.PSSetIndexBinder.InvalidateCache();
System.Management.Automation.Language.PSCreateInstanceBinder.InvalidateCache();
// If 'ExecutionContext.HasEverUsedConstrainedLanguage' is already set to True, then we have
// already invalidated all cached binders, and binders already started to generate code with
// consideration of 'LanguageMode'. In such case, we don't need to invalidate cached binders
// again.
// Note that when executing script blocks marked as 'FullLanguage' in a 'ConstrainedLanguage'
// environment, we will set and Restore 'context.LanguageMode' very often. But we should not
// invalidate the cached binders every time we restore to 'ConstrainedLanguage'.
if (!ExecutionContext.HasEverUsedConstrainedLanguage)
{
lock (lockObject)
{
// If another thread has already set 'ExecutionContext.HasEverUsedConstrainedLanguage'
// while we are waiting on the lock, then nothing needs to be done.
if (!ExecutionContext.HasEverUsedConstrainedLanguage)
{
PSSetMemberBinder.InvalidateCache();
PSInvokeMemberBinder.InvalidateCache();
PSConvertBinder.InvalidateCache();
PSBinaryOperationBinder.InvalidateCache();
PSGetIndexBinder.InvalidateCache();
PSSetIndexBinder.InvalidateCache();
PSCreateInstanceBinder.InvalidateCache();
// Set 'HasEverUsedConstrainedLanguage' at the very end to guarantee other threads to wait until
// all invalidation operations are done.
UntrustedObjects = new ConditionalWeakTable<object, object>();
ExecutionContext.HasEverUsedConstrainedLanguage = true;
}
}
}
}
// Conversion caches don't have version info / binding rules, so must be
@ -340,12 +363,106 @@ namespace System.Management.Automation
/// </summary>
internal bool HasRunspaceEverUsedConstrainedLanguageMode { get; private set; }
/// <summary>
/// Indicate if a parameter binding is happening that transitions the execution from ConstrainedLanguage
/// mode to a trusted FullLanguage command.
/// </summary>
internal bool LanguageModeTransitionInParameterBinding { get; set; }
/// <summary>
/// True if we've ever used ConstrainedLanguage. If this is the case, then the binding restrictions
/// need to also validate against the language mode.
/// </summary>
internal static bool HasEverUsedConstrainedLanguage { get; private set; }
#region Variable Tracking
/// <summary>
/// Initialized when 'ConstrainedLanguage' is applied.
/// The objects contained in this table are considered to be untrusted.
/// </summary>
private static ConditionalWeakTable<object, object> UntrustedObjects { get; set; }
/// <summary>
/// Helper for checking if the given value is marked as untrusted.
/// </summary>
internal static bool IsMarkedAsUntrusted(object value)
{
bool result = false;
var baseValue = PSObject.Base(value);
if (baseValue != null && baseValue != NullString.Value)
{
object unused;
result = UntrustedObjects.TryGetValue(baseValue, out unused);
}
return result;
}
/// <summary>
/// Helper for marking a value as untrusted.
/// </summary>
internal static void MarkObjectAsUntrusted(object value)
{
// If the value is a PSObject, then we mark its base object untrusted
var baseValue = PSObject.Base(value);
if (baseValue != null && baseValue != NullString.Value)
{
// It's actually setting a key value pair when the key doesn't exist
UntrustedObjects.GetValue(baseValue, key => null);
try
{
// If it's a PSReference object, we need to also mark the value it's holding on.
// This could result in a recursion if psRef.Value points to itself directly or indirectly, so we check if psRef.Value is already
// marked before making a recursive call. The additional check adds extra overhead for handling PSReference object, but it should
// be rare in practice.
var psRef = baseValue as PSReference;
if (psRef != null && !IsMarkedAsUntrusted(psRef.Value))
{
MarkObjectAsUntrusted(psRef.Value);
}
}
catch { /* psRef.Value may call PSVariable.Value under the hood, which may throw arbitrary exception */ }
}
}
/// <summary>
/// Helper for setting the untrusted value of an assignment to either a 'Global:' variable, or a 'Script:' variable in a module scope.
/// </summary>
/// <remarks>
/// This method is for tracking assignment to global variables and module script scope varaibles in ConstrainedLanguage mode. Those variables
/// can go across boundaries between ConstrainedLanguage and FullLanguage, and make it easy for a trusted script to use data from an untrusted
/// environment. Therefore, in ConstrainedLanguage mode, we need to mark the value objects assigned to those variables as untrusted.
/// </remarks>
internal static void MarkObjectAsUntrustedForVariableAssignment(PSVariable variable, SessionStateScope scope, SessionStateInternal sessionState)
{
if (scope.Parent == null || // If it's the global scope, OR
(sessionState.Module != null && // it's running in a module AND
scope.ScriptScope == scope && scope.Parent.Parent == null)) // it's the module's script scope (scope.Parent is global scope and scope.ScriptScope points to itself)
{
// We are setting value for either a 'Global:' variable, or a 'Script:' variable within a module in 'ConstrainedLanguage' mode.
// Global variable may be referenced within trusted script block (scriptBlock.LanguageMode == 'FullLanguage'), and users could
// also set a 'Script:' variable in a trusted module scope from 'ConstrainedLanguage' environment via '& $mo { $script:<var> }'.
// So we need to mark the value as untrusted.
MarkObjectAsUntrusted(variable.Value);
}
}
/// <summary>
/// The result object is assumed generated by operating on the original object.
/// So if the original object is from an untrusted input source, we mark the result object as untrusted.
/// </summary>
internal static void PropagateInputSource(object originalObject, object resultObject, PSLanguageMode currentLanguageMode)
{
// The untrusted flag is populated only in FullLanguage mode and ConstrainedLanguage has been used in the process before.
if (ExecutionContext.HasEverUsedConstrainedLanguage && currentLanguageMode == PSLanguageMode.FullLanguage && IsMarkedAsUntrusted(originalObject))
{
MarkObjectAsUntrusted(resultObject);
}
}
#endregion
/// <summary>
/// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode
/// </summary>
@ -1469,9 +1586,10 @@ namespace System.Management.Automation
Modules = new ModuleIntrinsics(this);
}
private static object lockObject = new Object();
#if !CORECLR // System.AppDomain is not in CoreCLR
private static bool _assemblyEventHandlerSet = false;
private static object lockObject = new Object();
/// <summary>
/// AssemblyResolve event handler that will look in the assembly cache to see

View file

@ -211,7 +211,7 @@ namespace System.Management.Automation
}
// parse the script into an expression tree...
ScriptBlock newScriptBlock = ScriptBlock.Create(new Parser(), _path, ScriptContents);
ScriptBlock newScriptBlock = ParseScriptContents(new Parser(), _path, ScriptContents, DefiningLanguageMode);
this.ScriptBlock = newScriptBlock;
}
@ -229,6 +229,31 @@ namespace System.Management.Automation
private ScriptBlock _scriptBlock;
private ScriptBlockAst _scriptBlockAst;
private static ScriptBlock ParseScriptContents(Parser parser, string fileName, string fileContents, PSLanguageMode? definingLanguageMode)
{
// If we are in ConstrainedLanguage mode but the defining language mode is FullLanguage, then we need
// to parse the script contents in FullLanguage mode context. Otherwise we will get bogus parsing errors
// such as "Configuration keyword not allowed".
if (definingLanguageMode.HasValue && (definingLanguageMode == PSLanguageMode.FullLanguage))
{
var context = LocalPipeline.GetExecutionContextFromTLS();
if ((context != null) && (context.LanguageMode == PSLanguageMode.ConstrainedLanguage))
{
context.LanguageMode = PSLanguageMode.FullLanguage;
try
{
return ScriptBlock.Create(parser, fileName, fileContents);
}
finally
{
context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
}
}
}
return ScriptBlock.Create(parser, fileName, fileContents);
}
internal ScriptBlockAst GetScriptBlockAst()
{
var scriptContents = ScriptContents;
@ -244,7 +269,29 @@ namespace System.Management.Automation
{
ParseError[] errors;
Parser parser = new Parser();
_scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default);
// If we are in ConstrainedLanguage mode but the defining language mode is FullLanguage, then we need
// to parse the script contents in FullLanguage mode context. Otherwise we will get bogus parsing errors
// such as "Configuration keyword not allowed".
var context = LocalPipeline.GetExecutionContextFromTLS();
if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage &&
DefiningLanguageMode == PSLanguageMode.FullLanguage)
{
context.LanguageMode = PSLanguageMode.FullLanguage;
try
{
_scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default);
}
finally
{
context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
}
}
else
{
_scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default);
}
if (errors.Length == 0)
{
this.ScriptBlock = new ScriptBlock(_scriptBlockAst, isFilter: false);

View file

@ -190,6 +190,7 @@ namespace System.Management.Automation
internal void Update(ScriptBlock newFunction, bool force, ScopedItemOptions options)
{
Update(newFunction, force, options, null);
this.DefiningLanguageMode = newFunction.LanguageMode;
}
/// <summary/>

View file

@ -12,6 +12,7 @@ using System.Linq;
using System.Management.Automation.Internal;
using System.Management.Automation.Provider;
using System.Management.Automation.Language;
using System.Management.Automation.Security;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
@ -761,6 +762,13 @@ namespace System.Management.Automation.Runspaces
HelpFile = helpFile;
}
internal static SessionStateFunctionEntry GetDelayParsedFunctionEntry(string name, string definition, bool isProductCode, PSLanguageMode languageMode)
{
var fnEntry = GetDelayParsedFunctionEntry(name, definition, isProductCode);
fnEntry.ScriptBlock.LanguageMode = languageMode;
return fnEntry;
}
internal static SessionStateFunctionEntry GetDelayParsedFunctionEntry(string name, string definition, bool isProductCode)
{
var sb = ScriptBlock.CreateDelayParsedScriptBlock(definition, isProductCode);
@ -2374,7 +2382,8 @@ namespace System.Management.Automation.Runspaces
// If a user has any module with the same name as that of the core module( or nested module inside the core module)
// in his module path, then that will get loaded instead of the actual nested module (from the GAC - in our case)
// Hence, searching only from the system module path while loading the core modules
ProcessImportModule(initializedRunspace, CoreModulesToImport, ModuleIntrinsics.GetPSHomeModulePath(), publicCommands);
var unresolvedCmdsToExpose = new HashSet<string>(this.UnresolvedCommandsToExpose, StringComparer.OrdinalIgnoreCase);
ProcessImportModule(initializedRunspace, CoreModulesToImport, ModuleIntrinsics.GetPSHomeModulePath(), publicCommands, unresolvedCmdsToExpose);
// Win8:328748 - functions defined in global scope end up in a module
// Since we import the core modules, EngineSessionState's module is set to the last imported module. So, if a function is defined in global scope, it ends up in that module.
@ -2384,7 +2393,7 @@ namespace System.Management.Automation.Runspaces
// Set the SessionStateDrive here since we have all the provider information at this point
SetSessionStateDrive(initializedRunspace.ExecutionContext, true);
Exception moduleImportException = ProcessImportModule(initializedRunspace, ModuleSpecificationsToImport, string.Empty, publicCommands);
Exception moduleImportException = ProcessImportModule(initializedRunspace, ModuleSpecificationsToImport, string.Empty, publicCommands, unresolvedCmdsToExpose);
if (moduleImportException != null)
{
runspaceInitTracer.WriteLine(
@ -2394,10 +2403,10 @@ namespace System.Management.Automation.Runspaces
// If we still have unresolved commands after importing specified modules, then try finding associated module for
// each unresolved command and import that module.
string[] foundModuleList = GetModulesForUnResolvedCommands(UnresolvedCommandsToExpose, initializedRunspace.ExecutionContext);
string[] foundModuleList = GetModulesForUnResolvedCommands(unresolvedCmdsToExpose, initializedRunspace.ExecutionContext);
if (foundModuleList.Length > 0)
{
ProcessImportModule(initializedRunspace, foundModuleList, string.Empty, publicCommands);
ProcessImportModule(initializedRunspace, foundModuleList, string.Empty, publicCommands, unresolvedCmdsToExpose);
}
ProcessDynamicVariables(initializedRunspace);
@ -2807,7 +2816,12 @@ namespace System.Management.Automation.Runspaces
return null;
}
private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initializedRunspace, IEnumerable moduleList, string path, HashSet<CommandInfo> publicCommands)
private RunspaceOpenModuleLoadException ProcessImportModule(
Runspace initializedRunspace,
IEnumerable moduleList,
string path,
HashSet<CommandInfo> publicCommands,
HashSet<string> unresolvedCmdsToExpose)
{
RunspaceOpenModuleLoadException exceptionToReturn = null;
@ -2877,7 +2891,7 @@ namespace System.Management.Automation.Runspaces
if (exceptionToReturn == null)
{
// Now go through the list of commands not yet resolved to ensure they are public if requested
foreach (string unresolvedCommand in UnresolvedCommandsToExpose.ToArray<string>())
foreach (string unresolvedCommand in unresolvedCmdsToExpose.ToArray<string>())
{
string moduleName;
string commandToMakeVisible = Utils.ParseCommandName(unresolvedCommand, out moduleName);
@ -2910,7 +2924,7 @@ namespace System.Management.Automation.Runspaces
if (found && !WildcardPattern.ContainsWildcardCharacters(commandToMakeVisible))
{
UnresolvedCommandsToExpose.Remove(unresolvedCommand);
unresolvedCmdsToExpose.Remove(unresolvedCommand);
}
}
}
@ -4728,21 +4742,28 @@ end
internal const string DefaultSetDriveFunctionText = "Set-Location $MyInvocation.MyCommand.Name";
internal static ScriptBlock SetDriveScriptBlock = ScriptBlock.CreateDelayParsedScriptBlock(DefaultSetDriveFunctionText, isProductCode: true);
private static PSLanguageMode systemLanguageMode = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ? PSLanguageMode.ConstrainedLanguage : PSLanguageMode.FullLanguage;
internal static SessionStateFunctionEntry[] BuiltInFunctions = new SessionStateFunctionEntry[]
{
// Functions. Only the name and definitions are used
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("prompt", DefaultPromptFunctionText, isProductCode: true),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("TabExpansion2", s_tabExpansionFunctionText, isProductCode: true),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Clear-Host", GetClearHostFunctionText(), isProductCode: true),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("help", GetHelpPagingFunctionText(), isProductCode: true),
// Porting note: we remove mkdir on Linux because it is a conflict
#if !UNIX
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("mkdir", GetMkdirFunctionText(), isProductCode: true),
#endif
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("oss", GetOSTFunctionText(), isProductCode: true),
// Functions that don't require full language mode
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd..", "Set-Location ..", isProductCode: true, languageMode: systemLanguageMode),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd\\", "Set-Location \\", isProductCode: true, languageMode: systemLanguageMode),
// Win8: 320909. Retaining the original definition to ensure backward compatability.
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Pause",
string.Concat("$null = Read-Host '", CodeGeneration.EscapeSingleQuotedStringContent(RunspaceInit.PauseDefinitionString),"'"), isProductCode: true, languageMode: systemLanguageMode),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("help", GetHelpPagingFunctionText(), isProductCode: true, languageMode: systemLanguageMode),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("prompt", DefaultPromptFunctionText, isProductCode: true, languageMode: systemLanguageMode),
// Porting note: we remove the drive functions from Linux because they make no sense
// Functions that require full language mode and are trusted
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Clear-Host", GetClearHostFunctionText(), isProductCode: true, languageMode: PSLanguageMode.FullLanguage),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("TabExpansion2", s_tabExpansionFunctionText, isProductCode: true, languageMode: PSLanguageMode.FullLanguage),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("oss", GetOSTFunctionText(), isProductCode: true, languageMode: PSLanguageMode.FullLanguage),
#if !UNIX
// Porting note: we remove mkdir on Linux because of a conflict
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("mkdir", GetMkdirFunctionText(), isProductCode: true, languageMode: PSLanguageMode.FullLanguage),
#endif
#if !UNIX
// Porting note: we remove the drive functions from Linux because they make no sense in that environment
// Default drives
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("A:", DefaultSetDriveFunctionText, SetDriveScriptBlock),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("B:", DefaultSetDriveFunctionText, SetDriveScriptBlock),
@ -4769,13 +4790,8 @@ end
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("W:", DefaultSetDriveFunctionText, SetDriveScriptBlock),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("X:", DefaultSetDriveFunctionText, SetDriveScriptBlock),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Y:", DefaultSetDriveFunctionText, SetDriveScriptBlock),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Z:", DefaultSetDriveFunctionText, SetDriveScriptBlock),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Z:", DefaultSetDriveFunctionText, SetDriveScriptBlock)
#endif
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd..", "Set-Location ..", isProductCode: true),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd\\", "Set-Location \\", isProductCode: true),
SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Pause",
string.Concat("$null = Read-Host '", CodeGeneration.EscapeSingleQuotedStringContent(RunspaceInit.PauseDefinitionString),"'"), isProductCode: true)
};
internal static void RemoveAllDrivesForProvider(ProviderInfo pi, SessionStateInternal ssi)

View file

@ -162,6 +162,7 @@ namespace Microsoft.PowerShell.Commands
/// The property or method name
/// </summary>
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "PropertyAndMethodSet")]
[ValidateTrustedData]
[ValidateNotNullOrEmpty]
public string MemberName
{
@ -177,6 +178,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]
[Parameter(ParameterSetName = "PropertyAndMethodSet", ValueFromRemainingArguments = true)]
[ValidateTrustedData]
[Alias("Args")]
public object[] ArgumentList
{

View file

@ -3851,7 +3851,12 @@ namespace System.Management.Automation
ExecutionContext ecFromTLS = LocalPipeline.GetExecutionContextFromTLS();
object result = null;
if (ecFromTLS == null || ecFromTLS.LanguageMode == PSLanguageMode.FullLanguage)
// Setting arbitrary properties is dangerous, so we allow this only if
// - It's running on a thread without Runspace; Or
// - It's in FullLanguage but not because it's part of a parameter binding that is transitioning from ConstrainedLanguage to FullLanguage
// When this is invoked from a parameter binding in transition from ConstrainedLanguage environment to FullLanguage command, we disallow
// the property conversion because it's dangerous.
if (ecFromTLS == null || (ecFromTLS.LanguageMode == PSLanguageMode.FullLanguage && !ecFromTLS.LanguageModeTransitionInParameterBinding))
{
result = _constructor();
var psobject = valueToConvert as PSObject;

View file

@ -142,6 +142,16 @@ namespace Microsoft.PowerShell.Commands
ThrowTerminatingError(er);
}
// Prevent script injection attack by disallowing ExportModuleMemberCommand to export module members across
// language boundaries. This will prevent injected untrusted script from exporting private trusted module functions.
if (Context.EngineSessionState.Module != null &&
Context.LanguageMode != Context.EngineSessionState.Module.LanguageMode)
{
var se = new PSSecurityException(Modules.CannotExportMembersAccrossLanguageBoundaries);
var er = new ErrorRecord(se, "Modules_CannotExportMembersAccrossLanguageBoundaries", ErrorCategory.SecurityError, this);
ThrowTerminatingError(er);
}
ModuleIntrinsics.ExportModuleMembers(this,
this.Context.EngineSessionState,
_functionPatterns, _cmdletPatterns, _aliasPatterns, _variablePatterns, null);

View file

@ -77,6 +77,7 @@ namespace Microsoft.PowerShell.Commands
[Parameter(ParameterSetName = ParameterSet_Name, Mandatory = true, ValueFromPipeline = true, Position = 0)]
[Parameter(ParameterSetName = ParameterSet_ViaPsrpSession, Mandatory = true, ValueFromPipeline = true, Position = 0)]
[Parameter(ParameterSetName = ParameterSet_ViaCimSession, Mandatory = true, ValueFromPipeline = true, Position = 0)]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]
public string[] Name { set; get; } = Utils.EmptyArray<string>();
@ -85,6 +86,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[Parameter(ParameterSetName = ParameterSet_FQName, Mandatory = true, ValueFromPipeline = true, Position = 0)]
[Parameter(ParameterSetName = ParameterSet_FQName_ViaPsrpSession, Mandatory = true, ValueFromPipeline = true, Position = 0)]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]
public ModuleSpecification[] FullyQualifiedName { get; set; }
@ -93,6 +95,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]
[Parameter(ParameterSetName = ParameterSet_Assembly, Mandatory = true, ValueFromPipeline = true, Position = 0)]
[ValidateTrustedData]
public Assembly[] Assembly { get; set; }
/// <summary>
@ -294,6 +297,7 @@ namespace Microsoft.PowerShell.Commands
/// This parameter specifies the current pipeline object
/// </summary>
[Parameter(ParameterSetName = ParameterSet_ModuleInfo, Mandatory = true, ValueFromPipeline = true, Position = 0)]
[ValidateTrustedData]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]
public PSModuleInfo[] ModuleInfo { set; get; } = Utils.EmptyArray<PSModuleInfo>();
@ -387,7 +391,7 @@ namespace Microsoft.PowerShell.Commands
try
{
PSModuleInfo alreadyLoadedModule = null;
Context.Modules.ModuleTable.TryGetValue(module.Path, out alreadyLoadedModule);
TryGetFromModuleTable(module.Path, out alreadyLoadedModule);
if (!BaseForce && DoesAlreadyLoadedModuleSatisfyConstraints(alreadyLoadedModule))
{
AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, alreadyLoadedModule);
@ -418,7 +422,7 @@ namespace Microsoft.PowerShell.Commands
else
{
PSModuleInfo moduleToRemove;
if (Context.Modules.ModuleTable.TryGetValue(module.Path, out moduleToRemove))
if (TryGetFromModuleTable(module.Path, out moduleToRemove, toRemove: true))
{
Dbg.Assert(BaseForce, "We should only remove and reload if -Force was specified");
RemoveModule(moduleToRemove);
@ -579,12 +583,11 @@ namespace Microsoft.PowerShell.Commands
// TODO/FIXME: use IsModuleAlreadyLoaded to get consistent behavior
// TODO/FIXME: (for example checking ModuleType != Manifest below seems incorrect - cdxml modules also declare their own version)
// PSModuleInfo alreadyLoadedModule = null;
// Context.Modules.ModuleTable.TryGetValue(rootedPath, out alreadyLoadedModule);
// TryGetFromModuleTable(rootedPath, out alreadyLoadedModule);
// if (!BaseForce && IsModuleAlreadyLoaded(alreadyLoadedModule))
// If the module has already been loaded, just emit it and continue...
PSModuleInfo module;
if (!BaseForce && Context.Modules.ModuleTable.TryGetValue(rootedPath, out module))
if (!BaseForce && TryGetFromModuleTable(rootedPath, out PSModuleInfo module))
{
if (module.ModuleType != ModuleType.Manifest
|| ModuleIntrinsics.IsVersionMatchingConstraints(module.Version, RequiredVersion, BaseMinimumVersion, BaseMaximumVersion))
@ -623,7 +626,7 @@ namespace Microsoft.PowerShell.Commands
if (File.Exists(rootedPath))
{
PSModuleInfo moduleToRemove;
if (Context.Modules.ModuleTable.TryGetValue(rootedPath, out moduleToRemove))
if (TryGetFromModuleTable(rootedPath, out moduleToRemove, toRemove: true))
{
RemoveModule(moduleToRemove);
}
@ -1018,9 +1021,8 @@ namespace Microsoft.PowerShell.Commands
//
// make sure the temporary folder gets removed when the module is removed
//
PSModuleInfo moduleInfo;
string psm1Path = Path.Combine(temporaryModulePath, Path.GetFileName(temporaryModulePath) + ".psm1");
if (!this.Context.Modules.ModuleTable.TryGetValue(psm1Path, out moduleInfo))
if (!TryGetFromModuleTable(psm1Path, out PSModuleInfo moduleInfo, toRemove: true))
{
if (Directory.Exists(temporaryModulePath))
{

View file

@ -18,6 +18,8 @@ using System.Reflection;
using System.Text;
using System.Xml;
using Microsoft.PowerShell.Cmdletization;
using System.Management.Automation.Language;
using Dbg = System.Management.Automation.Diagnostics;
//
@ -88,6 +90,12 @@ namespace Microsoft.PowerShell.Commands
/// If Scope parameter is Local, this is true.
/// </summary>
internal bool Local;
/// <summary>
/// Lets nested module import to export all of its functions, regardless of language boundaries.
/// This will be allowed when the manifest explicitly exports functions which will limit all visible module functions.
/// </summary>
internal bool AllowNestedModuleFunctionsToExport;
}
/// <summary>
@ -603,7 +611,7 @@ namespace Microsoft.PowerShell.Commands
private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, ModuleSpecification moduleSpecification, string moduleBase, bool searchModulePath,
string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, bool loadTypesFiles,
bool loadFormatFiles, object privateData, out bool found, string shortModuleName)
bool loadFormatFiles, object privateData, out bool found, string shortModuleName, PSLanguageMode? manifestLanguageMode)
{
PSModuleInfo module = null;
PSModuleInfo tempModuleInfoFromVerification = null;
@ -786,6 +794,21 @@ namespace Microsoft.PowerShell.Commands
}
}
if (manifestLanguageMode.HasValue && found && (module != null) && module.LanguageMode.HasValue)
{
// Check for script module language mode consistency. All loaded script modules must have the same language mode as the manifest.
// If not then this indicates a malformed module and a possible exploit to make trusted private functions visible in a
// Constrained Language session.
if (module.LanguageMode != manifestLanguageMode)
{
var languageModeError = PSTraceSource.NewInvalidOperationException(
Modules.MismatchedLanguageModes,
module.Name, manifestLanguageMode, module.LanguageMode);
languageModeError.SetErrorId("Modules_MismatchedLanguageModes");
throw languageModeError;
}
}
// At this point, we haven't found an actual module, so try loading it as a
// PSSnapIn and then finally as an assembly in the GAC...
if ((found == false) && (moduleSpecification.Guid == null) && (moduleSpecification.Version == null) && (moduleSpecification.RequiredVersion == null) && (moduleSpecification.MaximumVersion == null))
@ -1393,7 +1416,7 @@ namespace Microsoft.PowerShell.Commands
/// Routine to process the module manifest data language script.
/// </summary>
/// <param name="moduleManifestPath">The path to the manifest file</param>
/// <param name="scriptInfo">The script info for the manifest script</param>
/// <param name="manifestScriptInfo">The script info for the manifest script</param>
/// <param name="data">Contents of the module manifest</param>
/// <param name="localizedData">Contents of the localized module manifest</param>
/// <param name="manifestProcessingFlags">processing flags (whether to write errors / load elements)</param>
@ -1406,7 +1429,7 @@ namespace Microsoft.PowerShell.Commands
/// <returns></returns>
internal PSModuleInfo LoadModuleManifest(
string moduleManifestPath,
ExternalScriptInfo scriptInfo,
ExternalScriptInfo manifestScriptInfo,
Hashtable data,
Hashtable localizedData,
ManifestProcessingFlags manifestProcessingFlags,
@ -1570,14 +1593,14 @@ namespace Microsoft.PowerShell.Commands
string mtpExtension = Path.GetExtension(rootedPath);
if (!string.IsNullOrEmpty(mtpExtension) && ModuleIntrinsics.IsPowerShellModuleExtension(mtpExtension))
{
Context.Modules.ModuleTable.TryGetValue(rootedPath, out loadedModule);
TryGetFromModuleTable(rootedPath, out loadedModule);
}
else
{
foreach (string extensionToTry in ModuleIntrinsics.PSModuleExtensions)
{
rootedPath = this.FixupFileName(moduleBase, actualRootModule, extensionToTry);
Context.Modules.ModuleTable.TryGetValue(rootedPath, out loadedModule);
TryGetFromModuleTable(rootedPath, out loadedModule);
if (loadedModule != null)
break;
}
@ -2893,6 +2916,7 @@ namespace Microsoft.PowerShell.Commands
BaseDisableNameChecking = true;
SessionStateInternal oldSessionState = Context.EngineSessionState;
var exportedFunctionsContainsWildcards = ModuleIntrinsics.PatternContainsWildcard(exportedFunctions);
try
{
if (importingModule)
@ -2903,6 +2927,7 @@ namespace Microsoft.PowerShell.Commands
if (ss != null)
{
ss.Internal.ManifestWithExplicitFunctionExport = !exportedFunctionsContainsWildcards;
Context.EngineSessionState = ss.Internal;
}
@ -2911,6 +2936,11 @@ namespace Microsoft.PowerShell.Commands
// For nested modules, we need to set importmoduleoptions to false as they should not use the options set for parent module
ImportModuleOptions nestedModuleOptions = new ImportModuleOptions();
// If the nested manifest explicitly (no wildcards) specifies functions to be exported then allow all functions to be exported
// into the session state function table (regardless of language boundaries), because the manifest will filter them later to the
// specified function list.
nestedModuleOptions.AllowNestedModuleFunctionsToExport = ((exportedFunctions != null) && !exportedFunctionsContainsWildcards);
foreach (ModuleSpecification nestedModuleSpecification in nestedModules)
{
bool found = false;
@ -2919,19 +2949,20 @@ namespace Microsoft.PowerShell.Commands
this.BaseGlobal = false;
PSModuleInfo nestedModule = LoadModuleNamedInManifest(
manifestInfo,
nestedModuleSpecification, // moduleName
moduleBase,
true, // searchModulePath
string.Empty, // prefix: no -Prefix added for nested modules
null,
nestedModuleOptions,
manifestProcessingFlags,
true,
true,
privateData,
out found,
shortModuleName: null);
parentModule: manifestInfo,
moduleSpecification: nestedModuleSpecification,
moduleBase: moduleBase,
searchModulePath: true,
prefix: string.Empty,
ss: null,
options: nestedModuleOptions,
manifestProcessingFlags: manifestProcessingFlags,
loadTypesFiles: true,
loadFormatFiles: true,
privateData: privateData,
found: out found,
shortModuleName: null,
manifestLanguageMode: ((manifestScriptInfo != null) ? manifestScriptInfo.DefiningLanguageMode.GetValueOrDefault() : (PSLanguageMode?)null));
this.BaseGlobal = oldGlobal;
@ -3020,16 +3051,21 @@ namespace Microsoft.PowerShell.Commands
try
{
bool found;
newManifestInfo = LoadModuleNamedInManifest(null, new ModuleSpecification(actualRootModule),
moduleBase, /* searchModulePath */ false,
resolvedCommandPrefix, ss, options, manifestProcessingFlags,
// If types files already loaded, don't load snapin files
(exportedTypeFiles == null || 0 == exportedTypeFiles.Count),
// if format files already loaded, don't load snapin files
(exportedFormatFiles == null || 0 == exportedFormatFiles.Count),
privateData,
out found,
null);
newManifestInfo = LoadModuleNamedInManifest(
parentModule: null,
moduleSpecification: new ModuleSpecification(actualRootModule),
moduleBase: moduleBase,
searchModulePath: false,
prefix: resolvedCommandPrefix,
ss: ss,
options: options,
manifestProcessingFlags: manifestProcessingFlags,
loadTypesFiles: (exportedTypeFiles == null || 0 == exportedTypeFiles.Count), // If types files already loaded, don't load snapin files
loadFormatFiles: (exportedFormatFiles == null || 0 == exportedFormatFiles.Count), // if format files already loaded, don't load snapin files
privateData: privateData,
found: out found,
shortModuleName: null,
manifestLanguageMode: ((manifestScriptInfo != null) ? manifestScriptInfo.DefiningLanguageMode.GetValueOrDefault() : (PSLanguageMode?)null));
if (!found || (newManifestInfo == null))
{
@ -3352,9 +3388,17 @@ namespace Microsoft.PowerShell.Commands
// implicitly export functions and cmdlets.
if ((ss != null) && (!ss.Internal.UseExportList))
{
ModuleIntrinsics.ExportModuleMembers(this,
// For cross language boundaries, implicitly import all functions only if
// this manifest *does* exort functions explicitly.
List<WildcardPattern> fnMatchPattern = (
(manifestScriptInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage) &&
(Context.LanguageMode != PSLanguageMode.FullLanguage) &&
(exportedFunctions == null)
) ? null : MatchAll;
ModuleIntrinsics.ExportModuleMembers(cmdlet: this,
sessionState: ss.Internal,
functionPatterns: MatchAll,
functionPatterns: fnMatchPattern,
cmdletPatterns: MatchAll,
aliasPatterns: null,
variablePatterns: null,
@ -3366,6 +3410,22 @@ namespace Microsoft.PowerShell.Commands
{
if (ss != null)
{
// If module (psm1) functions were not exported because of cross language boundary restrictions,
// then implicitly export them here so that they can be filtered by the exportedFunctions list.
// Unless exportedFunctions contains the wildcard character that isn't allowed across language
// boundaries.
if (!ss.Internal.FunctionsExported && !exportedFunctionsContainsWildcards)
{
ModuleIntrinsics.ExportModuleMembers(
cmdlet: this,
sessionState: ss.Internal,
functionPatterns: MatchAll,
cmdletPatterns: null,
aliasPatterns: null,
variablePatterns: null,
doNotExportCmdlets: null);
}
Dbg.Assert(ss.Internal.ExportedFunctions != null,
"ss.Internal.ExportedFunctions should not be null");
@ -3462,6 +3522,8 @@ namespace Microsoft.PowerShell.Commands
ImportModuleMembers(manifestInfo, resolvedCommandPrefix, options);
}
manifestInfo.LanguageMode = (manifestScriptInfo != null) ? manifestScriptInfo.DefiningLanguageMode : (PSLanguageMode?)null;
return manifestInfo;
}
@ -5047,8 +5109,7 @@ namespace Microsoft.PowerShell.Commands
/// </returns>
internal PSModuleInfo IsModuleImportUnnecessaryBecauseModuleIsAlreadyLoaded(string modulePath, string prefix, ImportModuleOptions options)
{
PSModuleInfo alreadyLoadedModule;
if (this.Context.Modules.ModuleTable.TryGetValue(modulePath, out alreadyLoadedModule))
if (TryGetFromModuleTable(modulePath, out PSModuleInfo alreadyLoadedModule))
{
if (this.DoesAlreadyLoadedModuleSatisfyConstraints(alreadyLoadedModule))
{
@ -5156,7 +5217,6 @@ namespace Microsoft.PowerShell.Commands
string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found, out bool moduleFileFound)
{
string[] extensions;
PSModuleInfo module;
moduleFileFound = false;
if (!string.IsNullOrEmpty(extension))
@ -5187,11 +5247,11 @@ namespace Microsoft.PowerShell.Commands
}
// If the module has already been loaded, just emit it and continue...
Context.Modules.ModuleTable.TryGetValue(fileName, out module);
TryGetFromModuleTable(fileName, out PSModuleInfo module);
if (!BaseForce && importingModule && DoesAlreadyLoadedModuleSatisfyConstraints(module))
{
moduleFileFound = true;
module = Context.Modules.ModuleTable[fileName];
// If the module has already been loaded, then while loading it the second time, we should load it with the DefaultCommandPrefix specified in the module manifest. (If there is no Prefix from command line)
if (string.IsNullOrEmpty(prefix))
{
@ -5395,6 +5455,15 @@ namespace Microsoft.PowerShell.Commands
}
PSModuleInfo module = null;
// Block ps1 files from being imported in constrained language.
if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && ext.Equals(StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase))
{
InvalidOperationException invalidOp = new InvalidOperationException(Modules.ImportPSFileNotAllowedInConstrainedLanguage);
ErrorRecord er = new ErrorRecord(invalidOp, "Modules_ImportPSFileNotAllowedInConstrainedLanguage",
ErrorCategory.PermissionDenied, null);
ThrowTerminatingError(er);
}
// If MinimumVersion/RequiredVersion/MaximumVersion has been specified, then only try to process manifest modules...
if (BaseMinimumVersion != null || BaseMaximumVersion != null || BaseRequiredVersion != null || BaseGuid != null)
{
@ -5408,7 +5477,7 @@ namespace Microsoft.PowerShell.Commands
// If the module is in memory and the versions don't match don't return it.
// This will allow the search to continue and load a different version of the module.
if (Context.Modules.ModuleTable.TryGetValue(fileName, out module))
if (TryGetFromModuleTable(fileName, out module))
{
if (!ModuleIntrinsics.IsVersionMatchingConstraints(module.Version, minimumVersion: BaseMinimumVersion, maximumVersion: BaseMaximumVersion))
{
@ -5421,7 +5490,18 @@ namespace Microsoft.PowerShell.Commands
found = false;
string scriptName;
ExternalScriptInfo scriptInfo = null;
//
// !!NOTE!!
// If a new module type to load is ever added and if that new module type is based on a script file,
// such as the existing .psd1 and .psm1 files,
// then be sure to include the script file LanguageMode in the moduleInfo type created for the loaded module.
// The PSModuleInfo.LanguageMode property is used to check consistency between the manifest (.psd1) file
// and all other script (.psm1) file based modules being loaded by that manifest.
// Use the PSModuleInfo class constructor that takes the PSLanguageMode parameter argument.
// Look at the LoadModuleNamedInManifest() method to see how the language mode check works.
// !!NOTE!!
//
string _origModuleBeingProcessed = Context.ModuleBeingProcessed;
try
@ -5450,15 +5530,15 @@ namespace Microsoft.PowerShell.Commands
}
else
{
scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
var psm1ScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
try
{
Context.Modules.IncrementModuleNestingDepth(this, scriptInfo.Path);
Context.Modules.IncrementModuleNestingDepth(this, psm1ScriptInfo.Path);
// Create the module object...
try
{
module = Context.Modules.CreateModule(fileName, scriptInfo, MyInvocation.ScriptPosition, ss, privateData, BaseArgumentList);
module = Context.Modules.CreateModule(fileName, psm1ScriptInfo, MyInvocation.ScriptPosition, ss, privateData, BaseArgumentList);
module.SetModuleBase(moduleBase);
SetModuleLoggingInformation(module);
@ -5467,9 +5547,39 @@ namespace Microsoft.PowerShell.Commands
// implicitly export functions and cmdlets.
if (!module.SessionState.Internal.UseExportList)
{
ModuleIntrinsics.ExportModuleMembers(this, module.SessionState.Internal, functionPatterns: MatchAll,
cmdletPatterns: MatchAll, aliasPatterns: MatchAll, variablePatterns: null, doNotExportCmdlets: null);
// For cross language boundaries don't implicitly export all functions, unless they are allowed nested modules.
// Implict function export is allowed when any of the following is true:
// - Nested modules are allowed by module manifest
// - The import context language mode is FullLanguage
// - This script module not running as trusted (FullLanguage)
module.ModuleAutoExportsAllFunctions = options.AllowNestedModuleFunctionsToExport ||
Context.LanguageMode == PSLanguageMode.FullLanguage ||
psm1ScriptInfo.DefiningLanguageMode != PSLanguageMode.FullLanguage;
List<WildcardPattern> fnMatchPattern = module.ModuleAutoExportsAllFunctions ? MatchAll : null;
ModuleIntrinsics.ExportModuleMembers(
cmdlet: this,
sessionState: module.SessionState.Internal,
functionPatterns: fnMatchPattern,
cmdletPatterns: MatchAll,
aliasPatterns: MatchAll,
variablePatterns: null,
doNotExportCmdlets: null);
}
else if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) &&
(module.LanguageMode == PSLanguageMode.FullLanguage) &&
module.SessionState.Internal.FunctionsExportedWithWildcard &&
!module.SessionState.Internal.ManifestWithExplicitFunctionExport)
{
// When in a constrained environment and functions are being exported from this module using wildcards, make sure
// exported functions only come from this module and not from any imported nested modules.
// Unless there is a parent manifest that explicitly filters all exported functions (no wildcards).
// This prevents unintended public exposure of imported functions running in FullLanguage.
ModuleIntrinsics.RemoveNestedModuleFunctions(module);
}
CheckForDisallowedDotSourcing(module.SessionState, psm1ScriptInfo, options);
// Add it to the all module tables
ImportModuleMembers(module, prefix, options);
@ -5530,16 +5640,15 @@ namespace Microsoft.PowerShell.Commands
// Removing the module will not remove the commands dot-sourced from the .ps1 file.
// This module info is created so that we can keep the behavior consistent between scripts imported as modules and other kind of modules(all of them should have a PSModuleInfo).
// Auto-loading expects we always have a PSModuleInfo object for any module. This is how this issue was found.
module = new PSModuleInfo(ModuleIntrinsics.GetModuleName(fileName), fileName, Context, ss);
scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
var ps1ScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
Dbg.Assert(ps1ScriptInfo != null, "Scriptinfo for dotted file can't be null");
module = new PSModuleInfo(ModuleIntrinsics.GetModuleName(fileName), fileName, Context, ss, ps1ScriptInfo.DefiningLanguageMode);
message = StringUtil.Format(Modules.DottingScriptFile, fileName);
WriteVerbose(message);
try
{
Dbg.Assert(scriptInfo != null, "Scriptinfo for dotted file can't be null");
found = true;
InvocationInfo oldInvocationInfo = (InvocationInfo)Context.GetVariableValue(SpecialVariables.MyInvocationVarPath);
@ -5548,9 +5657,8 @@ namespace Microsoft.PowerShell.Commands
try
{
InvocationInfo invocationInfo = new InvocationInfo(scriptInfo, scriptInfo.ScriptBlock.Ast.Extent,
Context);
scriptInfo.ScriptBlock.InvokeWithPipe(
InvocationInfo invocationInfo = new InvocationInfo(ps1ScriptInfo, ps1ScriptInfo.ScriptBlock.Ast.Extent, Context);
ps1ScriptInfo.ScriptBlock.InvokeWithPipe(
useLocalScope: false,
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
dollarUnder: AutomationNull.Value,
@ -5610,11 +5718,11 @@ namespace Microsoft.PowerShell.Commands
}
else if (ext.Equals(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))
{
scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
var psd1ScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
found = true;
Dbg.Assert(scriptInfo != null, "Scriptinfo for module manifest (.psd1) can't be null");
Dbg.Assert(psd1ScriptInfo != null, "Scriptinfo for module manifest (.psd1) can't be null");
module = LoadModuleManifest(
scriptInfo,
psd1ScriptInfo,
manifestProcessingFlags,
BaseMinimumVersion,
BaseMaximumVersion,
@ -5624,6 +5732,8 @@ namespace Microsoft.PowerShell.Commands
if (module != null)
{
CheckForDisallowedDotSourcing(module.SessionState, psd1ScriptInfo, options);
if (importingModule)
{
// Add it to all the module tables
@ -5646,6 +5756,9 @@ namespace Microsoft.PowerShell.Commands
moduleBase, ss, options, manifestProcessingFlags, prefix, true, true, out found);
if (found && module != null)
{
// LanguageMode does not apply to binary modules
module.LanguageMode = (PSLanguageMode?)null;
if (importingModule)
{
// Add it to all the module tables
@ -5674,12 +5787,12 @@ namespace Microsoft.PowerShell.Commands
try
{
string moduleName = ModuleIntrinsics.GetModuleName(fileName);
scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
var cdxmlScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true);
try
{
// generate cmdletization proxies
var cmdletizationXmlReader = new StringReader(scriptInfo.ScriptContents);
var cmdletizationXmlReader = new StringReader(cdxmlScriptInfo.ScriptContents);
var cmdletizationProxyModuleWriter = new StringWriter(CultureInfo.InvariantCulture);
var scriptWriter = new ScriptWriter(
cmdletizationXmlReader,
@ -5690,7 +5803,7 @@ namespace Microsoft.PowerShell.Commands
if (!importingModule)
{
module = new PSModuleInfo(fileName, null, null);
module = new PSModuleInfo(null, fileName, null, null, cdxmlScriptInfo.DefiningLanguageMode);
scriptWriter.PopulatePSModuleInfo(module);
scriptWriter.ReportExportedCommands(module, prefix);
}
@ -5780,6 +5893,45 @@ namespace Microsoft.PowerShell.Commands
return module;
}
private void CheckForDisallowedDotSourcing(
SessionState ss,
ExternalScriptInfo scriptInfo,
ImportModuleOptions options)
{
if (ss == null || ss.Internal == null)
{ return; }
// A manifest with explicit function export is detected through a shared session state or the nested module options, because nested
// module processing does not use a shared session state.
var manifestWithExplicitFunctionExport = ss.Internal.ManifestWithExplicitFunctionExport || options.AllowNestedModuleFunctionsToExport;
// If system is in lock down mode, we disallow trusted modules that use the dotsource operator while simultaneously using
// wild cards for exporting module functions, unless there is an overriding manifest that explicitly exports functions
// without wild cards.
// This is because dotsourcing brings functions into module scope and it is too easy to inadvertently or maliciously
// expose harmful private functions that run in trusted (FullLanguage) mode.
if (!manifestWithExplicitFunctionExport && ss.Internal.FunctionsExportedWithWildcard &&
(SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) &&
(scriptInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage))
{
var dotSourceOperator = scriptInfo.GetScriptBlockAst().FindAll(ast =>
{
var cmdAst = ast as CommandAst;
return (cmdAst?.InvocationOperator == TokenKind.Dot);
},
searchNestedScriptBlocks: true).FirstOrDefault();
if (dotSourceOperator != null)
{
var errorRecord = new ErrorRecord(
new PSSecurityException(Modules.CannotUseDotSourceWithWildCardFunctionExport),
"Modules_SystemLockDown_CannotUseDotSourceWithWildCardFunctionExport",
ErrorCategory.SecurityError, null);
ThrowTerminatingError(errorRecord);
}
}
}
private static bool ShouldProcessScriptModule(PSModuleInfo parentModule, ref bool found)
{
bool shouldProcessModule = true;
@ -7286,6 +7438,54 @@ namespace Microsoft.PowerShell.Commands
return null;
}
/// <summary>
/// Returns the context cached ModuleTable module for import only if found and has safe language boundaries while
/// exporting all functions by default.
///
/// This protects cached trusted modules that exported all functions in a trusted context, from being re-used
/// in an untrusted context and thus exposing functions that were meant to be private in that context.
///
/// Returning false forces module import to re-import the module from file with the current context and prevent
/// all module functions from being exported by default.
///
/// Note that module loading order is important with this check when the system is *locked down with DeviceGuard*.
/// If a submodule that does not explicitly export any functions is imported from the command line, its useless
/// because no functions are exported (default fn export is explictly disallowed on locked down systems).
/// But if a parentmodule that imports the submodule is then imported, it will get the useless version of the
/// module from the ModuleTable and the parent module will not work.
/// $mSub = import-module SubModule # No functions exported, useless
/// $mParent = import-module ParentModule # This internally imports SubModule
/// $mParent.DoSomething # This will likely be broken because SubModule functions are not accessible
/// But this is not a realistic scenario because SubModule is useless with DeviceGuard lock down and must explicitly
/// export its functions to become useful, at which point this check is no longer in effect and there is no issue.
/// $mSub = import-module SubModule # Explictly exports functions, useful
/// $mParent = import-module ParentModule # This internally imports SubModule
/// $mParent.DoSomething # This works because SubModule functions are exported and accessible
/// </summary>
/// <param name="key">Key</param>
/// <param name="moduleInfo">PSModuleInfo</param>
/// <param name="toRemove">True if module item is to be removed</param>
/// <returns>True if module found in table and is safe to use</returns>
internal bool TryGetFromModuleTable(string key, out PSModuleInfo moduleInfo, bool toRemove = false)
{
var foundModule = Context.Modules.ModuleTable.TryGetValue(key, out moduleInfo);
// Check for unsafe language modes between module load context and current context.
// But only for script modules that exported all functions in a trusted (FL) context.
if (foundModule &&
!toRemove &&
moduleInfo.ModuleType == ModuleType.Script &&
Context.LanguageMode == PSLanguageMode.ConstrainedLanguage &&
moduleInfo.LanguageMode == PSLanguageMode.FullLanguage &&
moduleInfo.ModuleAutoExportsAllFunctions)
{
moduleInfo = null;
return false;
}
return foundModule;
}
} // end ModuleCmdletBase
/// <summary>

View file

@ -194,6 +194,7 @@ namespace System.Management.Automation
throw PSTraceSource.NewInvalidOperationException();
sb.SessionStateInternal = ss.Internal;
module.LanguageMode = sb.LanguageMode;
InvocationInfo invocationInfo = new InvocationInfo(scriptInfo, scriptPosition);
@ -1454,6 +1455,29 @@ namespace System.Management.Automation
return null;
}
/// <summary>
/// Removes all functions not belonging to the parent module.
/// </summary>
/// <param name="module">Parent module</param>
static internal void RemoveNestedModuleFunctions(PSModuleInfo module)
{
var input = module.SessionState?.Internal?.ExportedFunctions;
if ((input == null) || (input.Count == 0))
{ return; }
List<FunctionInfo> output = new List<FunctionInfo>(input.Count);
foreach (var fnInfo in input)
{
if (module.Name.Equals(fnInfo.ModuleName, StringComparison.OrdinalIgnoreCase))
{
output.Add(fnInfo);
}
}
input.Clear();
input.AddRange(output);
}
private static void SortAndRemoveDuplicates<T>(List<T> input, Func<T, string> keyGetter)
{
Dbg.Assert(input != null, "Caller should verify that input != null");
@ -1512,6 +1536,12 @@ namespace System.Management.Automation
if (functionPatterns != null)
{
sessionState.FunctionsExported = true;
if (PatternContainsWildcard(functionPatterns))
{
sessionState.FunctionsExportedWithWildcard = true;
}
IDictionary<string, FunctionInfo> ft = sessionState.ModuleScope.FunctionTable;
foreach (KeyValuePair<string, FunctionInfo> entry in ft)
@ -1650,6 +1680,27 @@ namespace System.Management.Automation
}
}
/// <summary>
/// Checks pattern list for wildcard characters.
/// </summary>
/// <param name="list">Pattern list</param>
/// <returns>True if pattern contains '*'</returns>
internal static bool PatternContainsWildcard(List<WildcardPattern> list)
{
if (list != null)
{
foreach (var item in list)
{
if (WildcardPattern.ContainsWildcardCharacters(item.Pattern))
{
return true;
}
}
}
return false;
}
private static AliasInfo NewAliasInfo(AliasInfo alias, SessionStateInternal sessionState)
{
Dbg.Assert(alias != null, "alias should not be null");

View file

@ -51,6 +51,20 @@ namespace System.Management.Automation
{
}
/// <summary>
/// This object describes a PowerShell module...
/// </summary>
/// <param name="name">The name to use for the module. If null, get it from the path name</param>
/// <param name="path">The absolute path to the module</param>
/// <param name="context">The execution context for this engine instance</param>
/// <param name="sessionState">The module's sessionstate object - this may be null if the module is a dll.</param>
/// <param name="languageMode">Language mode for script based modules</param>
internal PSModuleInfo(string name, string path, ExecutionContext context, SessionState sessionState, PSLanguageMode? languageMode)
: this(name, path, context, sessionState)
{
LanguageMode = languageMode;
}
/// <summary>
/// This object describes a PowerShell module...
/// </summary>
@ -134,6 +148,8 @@ namespace System.Management.Automation
SessionState = new SessionState(context, true, true);
SessionState.Internal.Module = this;
LanguageMode = scriptBlock.LanguageMode;
// Now set up the module's session state to be the current session state
SessionStateInternal oldSessionState = context.EngineSessionState;
try
@ -164,6 +180,20 @@ namespace System.Management.Automation
}
}
/// <summary>
/// Specifies the language mode for script based modules
/// </summary>
internal PSLanguageMode? LanguageMode
{
get;
set;
} = PSLanguageMode.FullLanguage;
/// <summary>
/// Set to true when script module automatically exports all functions by default
/// </summary>
internal bool ModuleAutoExportsAllFunctions { get; set; }
internal bool ModuleHasPrivateMembers { get; set; }
/// <summary>

View file

@ -414,7 +414,7 @@ namespace System.Management.Automation
parameterMetadata.CannotBeNull ||
dma.TransformNullOptionalParameters)))
{
parameterValue = dma.Transform(_engine, parameterValue);
parameterValue = dma.TransformInternal(_engine, parameterValue);
}
}
@ -991,6 +991,7 @@ namespace System.Management.Automation
collectionTypeInfo = new ParameterCollectionTypeInformation(toType);
}
object originalValue = currentValue;
object result = currentValue;
using (bindingTracer.TraceScope(
@ -1225,29 +1226,23 @@ namespace System.Management.Automation
bindingTracer.WriteLine(
"CONVERT arg type to param type using LanguagePrimitives.ConvertTo");
// If we are in constrained language mode and the target command is trusted,
// allow type conversion to the target command's parameter type.
// Don't allow Hashtable-to-Object conversion (PSObject and IDictionary), though,
// as those can lead to property setters that probably aren't expected.
bool changeLanguageModeForTrustedCommand = false;
if (_context.LanguageMode == PSLanguageMode.ConstrainedLanguage)
{
var basedObject = PSObject.Base(currentValue);
var supportsPropertyConversion = basedObject is PSObject;
var supportsIDictionaryConversion = (basedObject != null) &&
(typeof(IDictionary).IsAssignableFrom(basedObject.GetType()));
changeLanguageModeForTrustedCommand =
(this.Command.CommandInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage) &&
(!supportsPropertyConversion) &&
(!supportsIDictionaryConversion);
}
// If we are in constrained language mode and the target command is trusted, which is often
// the case for C# cmdlets, then we allow type conversion to the target parameter type.
//
// However, we don't allow Hashtable-to-Object conversion (PSObject and IDictionary) because
// those can lead to property setters that probably aren't expected. This is enforced by
// setting 'Context.LanguageModeTransitionInParameterBinding' to true before the conversion.
bool changeLanguageModeForTrustedCommand =
Context.LanguageMode == PSLanguageMode.ConstrainedLanguage &&
this.Command.CommandInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage;
bool oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding;
try
{
if (changeLanguageModeForTrustedCommand)
{
_context.LanguageMode = PSLanguageMode.FullLanguage;
Context.LanguageMode = PSLanguageMode.FullLanguage;
Context.LanguageModeTransitionInParameterBinding = true;
}
result = LanguagePrimitives.ConvertTo(currentValue, toType, CultureInfo.CurrentCulture);
@ -1256,7 +1251,8 @@ namespace System.Management.Automation
{
if (changeLanguageModeForTrustedCommand)
{
_context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
Context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus;
}
}
@ -1311,6 +1307,13 @@ namespace System.Management.Automation
throw pbe;
}
} // TraceScope
if (result != null)
{
// Set the converted result object untrusted if necessary
ExecutionContext.PropagateInputSource(originalValue, result, Context.LanguageMode);
}
return result;
} // CoerceTypeAsNeeded
@ -1444,6 +1447,7 @@ namespace System.Management.Automation
bool coerceElementTypeIfNeeded,
out bool coercionRequired)
{
object originalValue = currentValue;
object result = null;
coercionRequired = false;
@ -1870,6 +1874,9 @@ namespace System.Management.Automation
if (!coercionRequired)
{
result = resultCollection;
// Set the converted result object untrusted if necessary
ExecutionContext.PropagateInputSource(originalValue, result, Context.LanguageMode);
}
} while (false);

View file

@ -476,7 +476,26 @@ namespace System.Management.Automation
Context.LanguageMode = newLanguageMode.Value;
}
EnterScope();
bool? oldLangModeTransitionStatus = null;
try
{
// If it's from ConstrainedLanguage to FullLanguage, indicate the transition before parameter binding takes place.
if (oldLanguageMode == PSLanguageMode.ConstrainedLanguage && newLanguageMode == PSLanguageMode.FullLanguage)
{
oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding;
Context.LanguageModeTransitionInParameterBinding = true;
}
EnterScope();
}
finally
{
if (oldLangModeTransitionStatus.HasValue)
{
// Revert the transition state to old value after doing the parameter binding
Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus.Value;
}
}
if (commandRuntime.ErrorMergeTo == MshCommandRuntime.MergeDataStream.Output)
{

View file

@ -27,7 +27,7 @@ namespace System.Management.Automation
FunctionInfo fn = this.SetFunction(entry.Name, sb, null, entry.Options, false, CommandOrigin.Internal, this.ExecutionContext, entry.HelpFile, true);
fn.Visibility = entry.Visibility;
fn.Module = entry.Module;
fn.ScriptBlock.LanguageMode = PSLanguageMode.FullLanguage;
fn.ScriptBlock.LanguageMode = entry.ScriptBlock.LanguageMode ?? PSLanguageMode.FullLanguage;
}
/// <summary>
@ -105,6 +105,33 @@ namespace System.Management.Automation
internal bool UseExportList { get; set; } = false;
/// <summary>
/// Set to true when module functions are being explicitly exported using Export-ModuleMember
/// </summary>
internal bool FunctionsExported { get; set; }
/// <summary>
/// Set to true when any processed module functions are being explicitly exported using '*' wildcard
/// </summary>
internal bool FunctionsExportedWithWildcard
{
get { return _functionsExportedWithWildcard; }
set
{
Dbg.Assert((value == true), "This property should never be set/reset to false");
if (value == true)
{
_functionsExportedWithWildcard = value;
}
}
}
private bool _functionsExportedWithWildcard;
/// <summary>
/// Set to true if module loading is performed under a manifest that explicitly exports functions (no wildcards)
/// </summary>
internal bool ManifestWithExplicitFunctionExport { get; set; }
/// <summary>
/// Get a functions out of session state.
/// </summary>
@ -138,9 +165,41 @@ namespace System.Management.Automation
{
result = ((IEnumerator<FunctionInfo>)searcher).Current;
}
return result;
return (IsFunctionVisibleInDebugger(result, origin)) ? result : null;
} // GetFunction
private bool IsFunctionVisibleInDebugger(FunctionInfo fnInfo, CommandOrigin origin)
{
// Ensure the returned function item is not exposed across language boundaries when in
// a debugger breakpoint or nested prompt.
// A debugger breakpoint/nested prompt has access to all current scoped functions.
// This includes both running commands from the prompt or via a debugger Action scriptblock.
// Early out.
// Always allow built-in functions needed for command line debugging.
if ((this.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage) ||
(fnInfo == null) ||
(fnInfo.Name.Equals("prompt", StringComparison.OrdinalIgnoreCase)) ||
(fnInfo.Name.Equals("TabExpansion2", StringComparison.OrdinalIgnoreCase)) ||
(fnInfo.Name.Equals("Clear-Host", StringComparison.Ordinal)))
{
return true;
}
// Check both InNestedPrompt and Debugger.InBreakpoint to ensure we don't miss a case.
// Function is not visible if function and context language modes are different.
var runspace = this.ExecutionContext.CurrentRunspace;
if ((runspace != null) &&
(runspace.InNestedPrompt || (runspace.Debugger?.InBreakpoint == true)) &&
(fnInfo.DefiningLanguageMode.HasValue && (fnInfo.DefiningLanguageMode != this.ExecutionContext.LanguageMode)))
{
return false;
}
return true;
}
/// <summary>
/// Get a functions out of session state.
/// </summary>
@ -720,4 +779,4 @@ namespace System.Management.Automation
#endregion Functions
} // SessionStateInternal class
}
}

View file

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;
namespace System.Management.Automation
{
@ -484,19 +485,9 @@ namespace System.Management.Automation
variable = (LocalsTuple != null ? LocalsTuple.TrySetVariable(name, value) : null) ?? new PSVariable(name, value);
}
// Don't let people set AllScope variables in ConstrainedLanguage,
// as they can be used to interfere with the session state of
// trusted commands.
if (ExecutionContext.HasEverUsedConstrainedLanguage)
{
var context = System.Management.Automation.Runspaces.LocalPipeline.GetExecutionContextFromTLS();
if ((context != null) &&
(context.LanguageMode == PSLanguageMode.ConstrainedLanguage) &&
((variable.Options & ScopedItemOptions.AllScope) == ScopedItemOptions.AllScope))
{
throw new PSNotSupportedException();
}
CheckVariableChangeInConstrainedLanguage(variable);
}
_variables[name] = variable;
@ -592,19 +583,9 @@ namespace System.Management.Automation
variable = newVariable;
}
// Don't let people set AllScope variables in ConstrainedLanguage,
// as they can be used to interfere with the session state of
// trusted commands.
if (ExecutionContext.HasEverUsedConstrainedLanguage)
{
var context = System.Management.Automation.Runspaces.LocalPipeline.GetExecutionContextFromTLS();
if ((context != null) &&
(context.LanguageMode == PSLanguageMode.ConstrainedLanguage) &&
((variable.Options & ScopedItemOptions.AllScope) == ScopedItemOptions.AllScope))
{
throw new PSNotSupportedException();
}
CheckVariableChangeInConstrainedLanguage(variable);
}
_variables[variable.Name] = variable;
@ -1978,6 +1959,24 @@ namespace System.Management.Automation
}
}
private void CheckVariableChangeInConstrainedLanguage(PSVariable variable)
{
var context = LocalPipeline.GetExecutionContextFromTLS();
if (context?.LanguageMode == PSLanguageMode.ConstrainedLanguage)
{
if ((variable.Options & ScopedItemOptions.AllScope) == ScopedItemOptions.AllScope)
{
// Don't let people set AllScope variables in ConstrainedLanguage, as they can be used to
// interfere with the session state of trusted commands.
throw new PSNotSupportedException();
}
// Mark untrusted values for assignments to 'Global:' variables, and 'Script:' variables in
// a module scope, if it's necessary.
ExecutionContext.MarkObjectAsUntrustedForVariableAssignment(variable, this, context.EngineSessionState);
}
}
#endregion
} // class SessionStateScope
} // namespace System.Management.Automation

View file

@ -499,7 +499,7 @@ namespace System.Management.Automation
attribute as ArgumentTransformationAttribute;
if (transformationAttribute != null)
{
result = transformationAttribute.Transform(engine, result);
result = transformationAttribute.TransformInternal(engine, result);
}
}
return result;

View file

@ -1362,6 +1362,48 @@ namespace System.Management.Automation
#endif
}
/// <summary>
/// EnforceSystemLockDownLanguageMode
/// FullLangauge -> ConstrainedLanguage
/// RestrictedLanguage -> NoLanguage
/// ConstrainedLanguage -> ConstrainedLanguage
/// NoLanguage -> NoLanguage
/// </summary>
/// <param name="context">ExecutionContext</param>
/// <returns>Previous language mode or null for no language mode change</returns>
internal static PSLanguageMode? EnforceSystemLockDownLanguageMode(ExecutionContext context)
{
PSLanguageMode? oldMode = null;
if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce)
{
switch (context.LanguageMode)
{
case PSLanguageMode.FullLanguage:
oldMode = context.LanguageMode;
context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
break;
case PSLanguageMode.RestrictedLanguage:
oldMode = context.LanguageMode;
context.LanguageMode = PSLanguageMode.NoLanguage;
break;
case PSLanguageMode.ConstrainedLanguage:
case PSLanguageMode.NoLanguage:
break;
default:
Diagnostics.Assert(false, "Unexpected PSLanguageMode");
oldMode = context.LanguageMode;
context.LanguageMode = PSLanguageMode.NoLanguage;
break;
}
}
return oldMode;
}
#region Implicit Remoting Batching
// Commands allowed to run on target remote session along with implicit remote commands

View file

@ -131,7 +131,7 @@ namespace System.Management.Automation
engine = context.EngineIntrinsics;
}
variableValue = argumentTransformation.Transform(engine, variableValue);
variableValue = argumentTransformation.TransformInternal(engine, variableValue);
}
if (!PSVariable.IsValidValue(variableValue, item))

View file

@ -1725,6 +1725,22 @@ namespace System.Management.Automation
// Ignore, it means they don't have the default prompt
}
// Change the context language mode before updating the prompt script.
// This way the new prompt scriptblock will pick up the current context language mode.
PSLanguageMode? originalLanguageMode = null;
if (_context.UseFullLanguageModeInDebugger &&
(_context.LanguageMode != PSLanguageMode.FullLanguage))
{
originalLanguageMode = _context.LanguageMode;
_context.LanguageMode = PSLanguageMode.FullLanguage;
}
else if (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() ==
System.Management.Automation.Security.SystemEnforcementMode.Enforce)
{
// If there is a system lockdown in place, enforce it
originalLanguageMode = Utils.EnforceSystemLockDownLanguageMode(this._context);
}
// Update the prompt to the debug prompt
if (hadDefaultPrompt)
{
@ -1743,21 +1759,6 @@ namespace System.Management.Automation
}
}
PSLanguageMode? originalLanguageMode = null;
if (_context.UseFullLanguageModeInDebugger &&
(_context.LanguageMode != PSLanguageMode.FullLanguage))
{
originalLanguageMode = _context.LanguageMode;
_context.LanguageMode = PSLanguageMode.FullLanguage;
}
else if (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() ==
System.Management.Automation.Security.SystemEnforcementMode.Enforce)
{
// If there is a system lockdown in place, enforce it
originalLanguageMode = _context.LanguageMode;
_context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
}
RunspaceAvailability previousAvailability = _context.CurrentRunspace.RunspaceAvailability;
_context.CurrentRunspace.UpdateRunspaceAvailability(

View file

@ -853,6 +853,7 @@ namespace System.Management.Automation
e);
}
remoteRunspace.IsConfiguredLoopBack = true;
return remoteRunspace;
}

View file

@ -847,7 +847,12 @@ namespace System.Management.Automation.Runspaces
try
{
CommandOrigin commandOrigin = command.CommandOrigin;
if (IsNested)
// Do not set command origin to internal if this is a script debugger originated command (which always
// runs nested commands). This prevents the script debugger command line from seeing private commands.
if (IsNested &&
!LocalRunspace.InNestedPrompt &&
!((LocalRunspace.Debugger != null) && (LocalRunspace.Debugger.InBreakpoint)))
{
commandOrigin = CommandOrigin.Internal;
}

View file

@ -1263,10 +1263,7 @@ namespace System.Management.Automation.Runspaces.Internal
result.Open();
// Enforce the system lockdown policy if one is defined.
if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce)
{
result.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage;
}
Utils.EnforceSystemLockDownLanguageMode(result.ExecutionContext);
result.Events.ForwardEvent += OnRunspaceForwardEvent; // this must be done after open since open initializes the ExecutionContext

View file

@ -783,6 +783,7 @@ namespace System.Management.Automation
{ typeof(ValidateRangeAttribute), new[] { "ValidateRange" } },
{ typeof(ValidateScriptAttribute), new[] { "ValidateScript" } },
{ typeof(ValidateSetAttribute), new[] { "ValidateSet" } },
{ typeof(ValidateTrustedDataAttribute), new[] { "ValidateTrustedData" } },
{ typeof(ValidateUserDriveAttribute), new[] { "ValidateUserDrive"} },
{ typeof(Version), new[] { "version" } },
{ typeof(void), new[] { "void" } },

View file

@ -89,7 +89,11 @@ namespace System.Management.Automation.Remoting
ExecutionContext context = localRunspace.ExecutionContext;
// This is trusted input as long as we're in FullLanguage mode
bool isTrustedInput = (localRunspace.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage);
// and if we are not in a loopback configuration mode, in which case we always force remote script commands
// to be parsed and evaluated on the remote session (not in the current local session).
RemoteRunspace remoteRunspace = _runspaceRef.Value as RemoteRunspace;
bool isConfiguredLoopback = (remoteRunspace != null) ? remoteRunspace.IsConfiguredLoopBack : false;
bool isTrustedInput = !isConfiguredLoopback && (localRunspace.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage);
// Create PowerShell from ScriptBlock.
ScriptBlock scriptBlock = ScriptBlock.Create(context, line);

View file

@ -489,6 +489,15 @@ namespace System.Management.Automation
get { return RunspacePool.RemoteRunspacePoolInternal.AvailableForConnection; }
}
/// <summary>
/// This is used to indicate a special loopback remote session used for JEA restrictions.
/// </summary>
internal bool IsConfiguredLoopBack
{
get;
set;
}
/// <summary>
/// Debugger
/// </summary>

View file

@ -35,6 +35,7 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[Parameter(Position = 0, Mandatory = true,
ParameterSetName = StartJobCommand.DefinitionNameParameterSet)]
[ValidateTrustedData]
[ValidateNotNullOrEmpty]
public string DefinitionName
{
@ -106,6 +107,7 @@ namespace Microsoft.PowerShell.Commands
[Parameter(Position = 0,
Mandatory = true,
ParameterSetName = StartJobCommand.ComputerNameParameterSet)]
[ValidateTrustedData]
[Alias("Command")]
public override ScriptBlock ScriptBlock
{
@ -284,6 +286,7 @@ namespace Microsoft.PowerShell.Commands
Position = 0,
Mandatory = true,
ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)]
[ValidateTrustedData]
public override string FilePath
{
get
@ -302,6 +305,7 @@ namespace Microsoft.PowerShell.Commands
[Parameter(
Mandatory = true,
ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)]
[ValidateTrustedData]
[Alias("PSPath","LP")]
public string LiteralPath
{
@ -433,6 +437,7 @@ namespace Microsoft.PowerShell.Commands
ParameterSetName = StartJobCommand.ComputerNameParameterSet)]
[Parameter(Position = 1,
ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)]
[ValidateTrustedData]
public virtual ScriptBlock InitializationScript
{
get { return _initScript; }
@ -485,6 +490,7 @@ namespace Microsoft.PowerShell.Commands
ParameterSetName = StartJobCommand.ComputerNameParameterSet)]
[Parameter(ValueFromPipeline = true,
ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)]
[ValidateTrustedData]
public override PSObject InputObject
{
get { return base.InputObject; }
@ -497,6 +503,7 @@ namespace Microsoft.PowerShell.Commands
[Parameter(ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)]
[Parameter(ParameterSetName = StartJobCommand.ComputerNameParameterSet)]
[Parameter(ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)]
[ValidateTrustedData]
[Alias("Args")]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public override Object[] ArgumentList

View file

@ -632,11 +632,7 @@ namespace System.Management.Automation
// If the system lockdown policy says "Enforce", do so (unless it's in the
// more restrictive NoLanguage mode)
if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) &&
(args.Runspace.ExecutionContext.LanguageMode != PSLanguageMode.NoLanguage))
{
args.Runspace.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage;
}
Utils.EnforceSystemLockDownLanguageMode(args.Runspace.ExecutionContext);
// Set the current location to MyDocuments folder for this runspace.
// This used to be set to the Personal folder but was changed to MyDocuments folder for

View file

@ -1005,8 +1005,9 @@ namespace System.Management.Automation.Remoting
// Win10 server can support reconstruct/reconnect for all 2.x protocol versions
// that support reconstruct/reconnect, Protocol 2.2+
// Major protocol version differences (2.x -> 3.x) are not supported.
if ((serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) &&
(clientProtocolVersion.Major == serverProtocolVersion.Major))
// A reconstruct can only be initiated by a client that understands disconnect (2.2+),
// so we only need to check major versions from client and this server for compatibility.
if (clientProtocolVersion.Major == RemotingConstants.ProtocolVersion.Major)
{
if (clientProtocolVersion.Minor == RemotingConstants.ProtocolVersionWin8RTM.Minor)
{

View file

@ -201,11 +201,21 @@ namespace System.Management.Automation
// Call the AMSI API to determine if the script block has malicious content
var scriptExtent = scriptBlockAst.Extent;
if (AmsiUtils.ScanContent(scriptExtent.Text, scriptExtent.File) == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
var amsiResult = AmsiUtils.ScanContent(scriptExtent.Text, scriptExtent.File);
if (amsiResult == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
{
var parseError = new ParseError(scriptExtent, "ScriptContainedMaliciousContent", ParserStrings.ScriptContainedMaliciousContent);
throw new ParseException(new[] { parseError });
}
else if ((amsiResult >= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_BEGIN) &&
(amsiResult <= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_END))
{
// Certain policies set by an administrator blocked this content on this machine
var parseError = new ParseError(scriptExtent, "ScriptHasAdminBlockedContent",
StringUtil.Format(ParserStrings.ScriptHasAdminBlockedContent, amsiResult));
throw new ParseException(new[] { parseError });
}
if (ScriptBlock.CheckSuspiciousContent(scriptBlockAst) != null)
{

View file

@ -51,6 +51,14 @@ namespace System.Management.Automation
throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),
null, "CantInvokeInNonImportedModule", ParserStrings.CantInvokeInNonImportedModule, mi.Name);
}
else if (((invocationToken == TokenKind.Ampersand) || (invocationToken == TokenKind.Dot)) && (mi.LanguageMode != context.LanguageMode))
{
// Disallow FullLanguage "& (Get-Module MyModule) MyPrivateFn" from ConstrainedLanguage because it always
// runs "internal" origin and so has access to all functions, including non-exported functions.
// Otherwise we end up leaking non-exported functions that run in FullLanguage.
throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null,
"CantInvokeCallOperatorAcrossLanguageBoundaries", ParserStrings.CantInvokeCallOperatorAcrossLanguageBoundaries);
}
commandSessionState = mi.SessionState.Internal;
commandIndex += 1;
}
@ -295,6 +303,15 @@ namespace System.Management.Automation
internal static IEnumerable<CommandParameterInternal> Splat(object splattedValue, Ast splatAst)
{
splattedValue = PSObject.Base(splattedValue);
var markUntrustedData = false;
if (ExecutionContext.HasEverUsedConstrainedLanguage)
{
// If the value to be splatted is untrusted, then make sure sub-values held by it are
// also marked as untrusted.
markUntrustedData = ExecutionContext.IsMarkedAsUntrusted(splattedValue);
}
IDictionary splattedTable = splattedValue as IDictionary;
if (splattedTable != null)
{
@ -304,6 +321,7 @@ namespace System.Management.Automation
object parameterValue = de.Value;
string parameterText = GetParameterText(parameterName);
if (markUntrustedData) { ExecutionContext.MarkObjectAsUntrusted(parameterValue); }
yield return CommandParameterInternal.CreateParameterWithArgument(
splatAst, parameterName, parameterText,
splatAst, parameterValue, false);
@ -316,6 +334,7 @@ namespace System.Management.Automation
{
foreach (object obj in enumerableValue)
{
if (markUntrustedData) { ExecutionContext.MarkObjectAsUntrusted(obj); }
yield return SplatEnumerableElement(obj, splatAst);
}
}

View file

@ -46,6 +46,8 @@ namespace System.Management.Automation
: GetAttributeCollection(attributeAsts);
var = new PSVariable(variablePath.UnqualifiedPath, value, ScopedItemOptions.None, attributes);
// Marking untrusted values for assignments in 'ConstrainedLanguage' mode is done in
// SessionStateScope.SetVariable.
sessionState.SetVariable(variablePath, var, false, origin);
if (executionContext._debuggingMode > 0)
@ -53,48 +55,58 @@ namespace System.Management.Automation
executionContext.Debugger.CheckVariableWrite(variablePath.UnqualifiedPath);
}
}
else if (attributeAsts != null)
{
// Use bytewise operation directly instead of 'var.IsReadOnly || var.IsConstant' on
// a hot path (setting variable with type constraint) to get better performance.
if ((var.Options & (ScopedItemOptions.ReadOnly | ScopedItemOptions.Constant)) != ScopedItemOptions.None)
{
SessionStateUnauthorizedAccessException e =
new SessionStateUnauthorizedAccessException(
var.Name,
SessionStateCategory.Variable,
"VariableNotWritable",
SessionStateStrings.VariableNotWritable);
throw e;
}
var attributes = GetAttributeCollection(attributeAsts);
value = PSVariable.TransformValue(attributes, value);
if (!PSVariable.IsValidValue(attributes, value))
{
ValidationMetadataException e = new ValidationMetadataException(
"ValidateSetFailure",
null,
Metadata.InvalidValueFailure,
var.Name,
((value != null) ? value.ToString() : "$null"));
throw e;
}
var.SetValueRaw(value, true);
// Don't update the PSVariable's attributes until we successfully set the value
var.Attributes.Clear();
var.AddParameterAttributesNoChecks(attributes);
if (executionContext._debuggingMode > 0)
{
executionContext.Debugger.CheckVariableWrite(variablePath.UnqualifiedPath);
}
}
else
{
// The setter will handle checking for variable writes.
var.Value = value;
if (attributeAsts != null)
{
// Use bytewise operation directly instead of 'var.IsReadOnly || var.IsConstant' on
// a hot path (setting variable with type constraint) to get better performance.
if ((var.Options & (ScopedItemOptions.ReadOnly | ScopedItemOptions.Constant)) != ScopedItemOptions.None)
{
SessionStateUnauthorizedAccessException e =
new SessionStateUnauthorizedAccessException(
var.Name,
SessionStateCategory.Variable,
"VariableNotWritable",
SessionStateStrings.VariableNotWritable);
throw e;
}
var attributes = GetAttributeCollection(attributeAsts);
value = PSVariable.TransformValue(attributes, value);
if (!PSVariable.IsValidValue(attributes, value))
{
ValidationMetadataException e = new ValidationMetadataException(
"ValidateSetFailure",
null,
Metadata.InvalidValueFailure,
var.Name,
((value != null) ? value.ToString() : "$null"));
throw e;
}
var.SetValueRaw(value, true);
// Don't update the PSVariable's attributes until we successfully set the value
var.Attributes.Clear();
var.AddParameterAttributesNoChecks(attributes);
if (executionContext._debuggingMode > 0)
{
executionContext.Debugger.CheckVariableWrite(variablePath.UnqualifiedPath);
}
}
else
{
// The setter will handle checking for variable writes.
var.Value = value;
}
if (executionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage)
{
// Mark untrusted values for assignments to 'Global:' variables, and 'Script:' variables in
// a module scope, if it's necessary.
ExecutionContext.MarkObjectAsUntrustedForVariableAssignment(var, scope, sessionState);
}
}
return value;

View file

@ -249,4 +249,7 @@
<data name="InvalidEnumArgument" xml:space="preserve">
<value>The Enum member '{0}' is not a valid value for the parameter '{1}'. Specify one of the following members and try again: {2}.</value>
</data>
<data name="ValidateTrustedDataFailure" xml:space="preserve">
<value>Cannot process input. The argument "{0}" is not trusted.</value>
</data>
</root>

View file

@ -618,4 +618,16 @@
<data name="SkipEditionCheckNotSupportedWithoutListAvailable" xml:space="preserve">
<value>The -SkipEditionCheck switch parameter cannot be used without the -ListAvailable switch parameter.</value>
</data>
<data name="ImportPSFileNotAllowedInConstrainedLanguage" xml:space="preserve">
<value>Importing *.ps1 files as modules is not allowed in ConstrainedLanguage mode.</value>
</data>
<data name="MismatchedLanguageModes" xml:space="preserve">
<value>An error has occurred while loading script module {0} because it has a different language mode than the module manifest. The manifest language mode is {1} and the module language mode is {2}. Ensure all module files are signed or otherwise part of your application allow list configuration.</value>
</data>
<data name="CannotUseDotSourceWithWildCardFunctionExport" xml:space="preserve">
<value>This module uses the dot-source operator while exporting functions using wildcard characters, and this is disallowed when the system is under application verification enforcement.</value>
</data>
<data name="CannotExportMembersAccrossLanguageBoundaries" xml:space="preserve">
<value>Cannot export module members from a module that has a different language mode from the running session.</value>
</data>
</root>

View file

@ -1488,4 +1488,10 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent
<data name="ImplicitRemotingPipelineBatchingSuccess" xml:space="preserve">
<value>Implicit remoting command pipeline has been batched for execution on remote target.</value>
</data>
<data name="ScriptHasAdminBlockedContent" xml:space="preserve">
<value>This script contains content that has been flagged as suspicious through a policy setting and has been blocked with error code {0}. Contact your administrator for more information.</value>
</data>
<data name = "CantInvokeCallOperatorAcrossLanguageBoundaries" xml:space="preserve">
<value>Cannot use '&amp;' or '.' operators to invoke a module scope command across language boundaries.</value>
</data>
</root>

View file

@ -1475,6 +1475,11 @@ namespace System.Management.Automation
internal class AmsiUtils
{
private static string GetProcessHostName(string processName)
{
return string.Concat("PowerShell_", processName, ".exe_0.0.0.0");
}
internal static int Init()
{
Diagnostics.Assert(s_amsiContext == IntPtr.Zero, "Init should be called just once");
@ -1492,10 +1497,13 @@ namespace System.Management.Automation
catch (ComponentModel.Win32Exception)
{
// This exception can be thrown during thread impersonation (Access Denied for process module access).
// Use command line arguments or process name.
string[] cmdLineArgs = Environment.GetCommandLineArgs();
string processPath = (cmdLineArgs.Length > 0) ? cmdLineArgs[0] : currentProcess.ProcessName;
hostname = string.Concat("PowerShell_", processPath, ".exe_0.0.0.0");
hostname = GetProcessHostName(currentProcess.ProcessName);
}
catch (FileNotFoundException)
{
// This exception can occur if the file is renamed or moved to some other folder
// (This has occurred during Exchange set up).
hostname = GetProcessHostName(currentProcess.ProcessName);
}
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
@ -1589,12 +1597,21 @@ namespace System.Management.Automation
AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN;
hr = AmsiNativeMethods.AmsiScanString(
s_amsiContext,
content,
sourceMetadata,
s_amsiSession,
ref result);
// Run AMSI content scan
unsafe
{
fixed (char* buffer = content)
{
var buffPtr = new IntPtr(buffer);
hr = AmsiNativeMethods.AmsiScanBuffer(
s_amsiContext,
buffPtr,
(uint)(content.Length * sizeof(char)),
sourceMetadata,
s_amsiSession,
ref result);
}
}
if (!Utils.Succeeded(hr))
{
@ -1704,6 +1721,10 @@ namespace System.Management.Automation
/// AMSI_RESULT_NOT_DETECTED -> 1
AMSI_RESULT_NOT_DETECTED = 1,
/// Certain policies set by administrator blocked this content on this machine
AMSI_RESULT_BLOCKED_BY_ADMIN_BEGIN = 0x4000,
AMSI_RESULT_BLOCKED_BY_ADMIN_END = 0x4fff,
/// AMSI_RESULT_DETECTED -> 32768
AMSI_RESULT_DETECTED = 32768,
}

View file

@ -174,6 +174,8 @@ namespace System.Management.Automation.Security
}
private static SystemEnforcementMode? s_cachedWldpSystemPolicy = null;
private const string AppLockerTestFileName = "__PSScriptPolicyTest_";
private const string AppLockerTestFileContents = "# PowerShell test file to determine AppLocker lockdown mode ";
private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle handle)
{
SaferPolicy result = SaferPolicy.Disallowed;
@ -216,13 +218,14 @@ namespace System.Management.Automation.Security
IO.Directory.CreateDirectory(tempPath);
}
testPathScript = IO.Path.Combine(tempPath, IO.Path.GetRandomFileName() + ".ps1");
testPathModule = IO.Path.Combine(tempPath, IO.Path.GetRandomFileName() + ".psm1");
testPathScript = IO.Path.Combine(tempPath, AppLockerTestFileName + IO.Path.GetRandomFileName() + ".ps1");
testPathModule = IO.Path.Combine(tempPath, AppLockerTestFileName + 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");
String dtAppLockerTestFileContents = AppLockerTestFileContents + DateTime.Now;
IO.File.WriteAllText(testPathScript, dtAppLockerTestFileContents);
IO.File.WriteAllText(testPathModule, dtAppLockerTestFileContents);
}
catch (System.IO.IOException)
{

View file

@ -99,3 +99,329 @@ Describe 'Argument transformation attribute on optional argument with explicit $
Invoke-CSharpCmdletTakesUInt64 -Address $null | Should -Be 42
}
}
Describe "Custom type conversion in parameter binding" -Tags 'Feature' {
BeforeAll {
## Prepare the script module
$content = @'
function Test-ScriptCmdlet {
[CmdletBinding(DefaultParameterSetName = "File")]
param(
[Parameter(Mandatory, ParameterSetName = "File")]
[System.IO.FileInfo] $File,
[Parameter(Mandatory, ParameterSetName = "StartInfo")]
[System.Diagnostics.ProcessStartInfo] $StartInfo
)
if ($PSCmdlet.ParameterSetName -eq "File") {
$File.Name
} else {
$StartInfo.FileName
}
}
function Test-ScriptFunction {
param(
[System.IO.FileInfo] $File,
[System.Diagnostics.ProcessStartInfo] $StartInfo
)
if ($null -ne $File) {
$File.Name
}
if ($null -ne $StartInfo) {
$StartInfo.FileName
}
}
'@
Set-Content -Path $TestDrive\module.psm1 -Value $content -Force
## Prepare the C# module
$code = @'
using System.IO;
using System.Diagnostics;
using System.Management.Automation;
namespace Test
{
[Cmdlet("Test", "BinaryCmdlet", DefaultParameterSetName = "File")]
public class TestCmdletCommand : PSCmdlet
{
[Parameter(Mandatory = true, ParameterSetName = "File")]
public FileInfo File { get; set; }
[Parameter(Mandatory = true, ParameterSetName = "StartInfo")]
public ProcessStartInfo StartInfo { get; set; }
protected override void ProcessRecord()
{
if (this.ParameterSetName == "File")
{
WriteObject(File.Name);
}
else
{
WriteObject(StartInfo.FileName);
}
}
}
}
'@
$asmFile = [System.IO.Path]::GetTempFileName() + ".dll"
Add-Type -TypeDefinition $code -OutputAssembly $asmFile
## Helper function to execute script
function Execute-Script {
[CmdletBinding(DefaultParameterSetName = "Script")]
param(
[Parameter(Mandatory)]
[powershell]$ps,
[Parameter(Mandatory, ParameterSetName = "Script")]
[string]$Script,
[Parameter(Mandatory, ParameterSetName = "Command")]
[string]$Command,
[Parameter(Mandatory, ParameterSetName = "Command")]
[string]$ParameterName,
[Parameter(Mandatory, ParameterSetName = "Command")]
[object]$Argument
)
$ps.Commands.Clear()
$ps.Streams.ClearStreams()
if ($PSCmdlet.ParameterSetName -eq "Script") {
$ps.AddScript($Script).Invoke()
} else {
$ps.AddCommand($Command).AddParameter($ParameterName, $Argument).Invoke()
}
}
## Helper command strings
$changeToConstrainedLanguage = '$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"'
$getLanguageMode = '$ExecutionContext.SessionState.LanguageMode'
$importScriptModule = "Import-Module $TestDrive\module.psm1"
$importCSharpModule = "Import-Module $asmFile"
}
AfterAll {
## Set the LanguageMode to force rebuilding the type conversion cache.
## This is needed because type conversions happen in the new powershell runspace with 'ConstrainedLanguage' mode
## will be put in the type conversion cache, and that may affect the default session.
$ExecutionContext.SessionState.LanguageMode = "FullLanguage"
}
It "Custom type conversion in parameter binding is allowed in FullLanguage" {
## Create a powershell instance for the test
$ps = [powershell]::Create()
try {
## Import the modules in FullLanguage mode
Execute-Script -ps $ps -Script $importScriptModule
Execute-Script -ps $ps -Script $importCSharpModule
$languageMode = Execute-Script -ps $ps -Script $getLanguageMode
$languageMode | Should Be 'FullLanguage'
$result1 = Execute-Script -ps $ps -Script "Test-ScriptCmdlet -File fileToUse"
$result1 | Should Be "fileToUse"
$result2 = Execute-Script -ps $ps -Script "Test-ScriptFunction -File fileToUse"
$result2 | Should Be "fileToUse"
$result3 = Execute-Script -ps $ps -Script "Test-BinaryCmdlet -File fileToUse"
$result3 | Should Be "fileToUse"
## Conversion involves setting properties of an instance of the target type is allowed in FullLanguage mode
$hashValue = @{ FileName = "filename"; Arguments = "args" }
$psobjValue = [PSCustomObject] $hashValue
## Test 'Test-ScriptCmdlet -StartInfo' with IDictionary and PSObject with properties
$result4 = Execute-Script -ps $ps -Command "Test-ScriptCmdlet" -ParameterName "StartInfo" -Argument $hashValue
$result4 | Should Be "filename"
$result5 = Execute-Script -ps $ps -Command "Test-ScriptCmdlet" -ParameterName "StartInfo" -Argument $psobjValue
$result5 | Should Be "filename"
## Test 'Test-ScriptFunction -StartInfo' with IDictionary and PSObject with properties
$result6 = Execute-Script -ps $ps -Command "Test-ScriptFunction" -ParameterName "StartInfo" -Argument $hashValue
$result6 | Should Be "filename"
$result7 = Execute-Script -ps $ps -Command "Test-ScriptFunction" -ParameterName "StartInfo" -Argument $psobjValue
$result7 | Should Be "filename"
## Test 'Test-BinaryCmdlet -StartInfo' with IDictionary and PSObject with properties
$result8 = Execute-Script -ps $ps -Command "Test-BinaryCmdlet" -ParameterName "StartInfo" -Argument $hashValue
$result8 | Should Be "filename"
$result9 = Execute-Script -ps $ps -Command "Test-BinaryCmdlet" -ParameterName "StartInfo" -Argument $psobjValue
$result9 | Should Be "filename"
}
finally {
$ps.Dispose()
}
}
It "Some custom type conversion in parameter binding is allowed for trusted cmdlets in ConstrainedLanguage" {
## Create a powershell instance for the test
$ps = [powershell]::Create()
try {
## Import the modules in FullLanguage mode
Execute-Script -ps $ps -Script $importScriptModule
Execute-Script -ps $ps -Script $importCSharpModule
$languageMode = Execute-Script -ps $ps -Script $getLanguageMode
$languageMode | Should Be 'FullLanguage'
## Change to ConstrainedLanguage mode
Execute-Script -ps $ps -Script $changeToConstrainedLanguage
$languageMode = Execute-Script -ps $ps -Script $getLanguageMode
$languageMode | Should Be 'ConstrainedLanguage'
$result1 = Execute-Script -ps $ps -Script "Test-ScriptCmdlet -File fileToUse"
$result1 | Should Be "fileToUse"
$result2 = Execute-Script -ps $ps -Script "Test-ScriptFunction -File fileToUse"
$result2 | Should Be "fileToUse"
$result3 = Execute-Script -ps $ps -Script "Test-BinaryCmdlet -File fileToUse"
$result3 | Should Be "fileToUse"
## If the conversion involves setting properties of an instance of the target type,
## then it's disallowed even for trusted cmdlets.
$hashValue = @{ FileName = "filename"; Arguments = "args" }
$psobjValue = [PSCustomObject] $hashValue
## Test 'Test-ScriptCmdlet -StartInfo' with IDictionary and PSObject with properties
try {
Execute-Script -ps $ps -Command "Test-ScriptCmdlet" -ParameterName "StartInfo" -Argument $hashValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
try {
Execute-Script -ps $ps -Command "Test-ScriptCmdlet" -ParameterName "StartInfo" -Argument $psobjValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
## Test 'Test-ScriptFunction -StartInfo' with IDictionary and PSObject with properties
try {
Execute-Script -ps $ps -Command "Test-ScriptFunction" -ParameterName "StartInfo" -Argument $hashValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
try {
Execute-Script -ps $ps -Command "Test-ScriptFunction" -ParameterName "StartInfo" -Argument $psobjValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
## Test 'Test-BinaryCmdlet -StartInfo' with IDictionary and PSObject with properties
try {
Execute-Script -ps $ps -Command "Test-BinaryCmdlet" -ParameterName "StartInfo" -Argument $hashValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingException,Execute-Script"
}
try {
Execute-Script -ps $ps -Command "Test-BinaryCmdlet" -ParameterName "StartInfo" -Argument $psobjValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingException,Execute-Script"
}
}
finally {
$ps.Dispose()
}
}
It "Custom type conversion in parameter binding is NOT allowed for untrusted cmdlets in ConstrainedLanguage" {
## Create a powershell instance for the test
$ps = [powershell]::Create()
try {
$languageMode = Execute-Script -ps $ps -Script $getLanguageMode
$languageMode | Should Be 'FullLanguage'
## Change to ConstrainedLanguage mode
Execute-Script -ps $ps -Script $changeToConstrainedLanguage
$languageMode = Execute-Script -ps $ps -Script $getLanguageMode
$languageMode | Should Be 'ConstrainedLanguage'
## Import the modules in ConstrainedLanguage mode
Execute-Script -ps $ps -Script $importScriptModule
Execute-Script -ps $ps -Script $importCSharpModule
$result1 = Execute-Script -ps $ps -Script "Test-ScriptCmdlet -File fileToUse"
$result1 | Should Be $null
$ps.Streams.Error.Count | Should Be 1
$ps.Streams.Error[0].FullyQualifiedErrorId | Should Be "ParameterArgumentTransformationError,Test-ScriptCmdlet"
$result2 = Execute-Script -ps $ps -Script "Test-ScriptFunction -File fileToUse"
$result2 | Should Be $null
$ps.Streams.Error.Count | Should Be 1
$ps.Streams.Error[0].FullyQualifiedErrorId | Should Be "ParameterArgumentTransformationError,Test-ScriptFunction"
## Binary cmdlets are always marked as trusted because only trusted assemblies can be loaded on DeviceGuard machine.
$result3 = Execute-Script -ps $ps -Script "Test-BinaryCmdlet -File fileToUse"
$result3 | Should Be "fileToUse"
## Conversion that involves setting properties of an instance of the target type is disallowed.
$hashValue = @{ FileName = "filename"; Arguments = "args" }
$psobjValue = [PSCustomObject] $hashValue
## Test 'Test-ScriptCmdlet -StartInfo' with IDictionary and PSObject with properties
try {
Execute-Script -ps $ps -Command "Test-ScriptCmdlet" -ParameterName "StartInfo" -Argument $hashValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
try {
Execute-Script -ps $ps -Command "Test-ScriptCmdlet" -ParameterName "StartInfo" -Argument $psobjValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
## Test 'Test-ScriptFunction -StartInfo' with IDictionary and PSObject with properties
try {
Execute-Script -ps $ps -Command "Test-ScriptFunction" -ParameterName "StartInfo" -Argument $hashValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
try {
Execute-Script -ps $ps -Command "Test-ScriptFunction" -ParameterName "StartInfo" -Argument $psobjValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingArgumentTransformationException,Execute-Script"
}
## Test 'Test-BinaryCmdlet -StartInfo' with IDictionary and PSObject with properties
try {
Execute-Script -ps $ps -Command "Test-BinaryCmdlet" -ParameterName "StartInfo" -Argument $hashValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingException,Execute-Script"
}
try {
Execute-Script -ps $ps -Command "Test-BinaryCmdlet" -ParameterName "StartInfo" -Argument $psobjValue
throw "Expected exception was not thrown!"
} catch {
$_.FullyQualifiedErrorId | Should Be "ParameterBindingException,Execute-Script"
}
}
finally {
$ps.Dispose()
}
}
}

View file

@ -406,11 +406,11 @@ Describe "Type accelerators" -Tags "CI" {
if ( !$IsWindows )
{
$totalAccelerators = 98
$totalAccelerators = 99
}
else
{
$totalAccelerators = 103
$totalAccelerators = 104
$extraFullPSAcceleratorTestCases = @(
@{

View file

@ -0,0 +1,427 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
##
## ----------
## Test Note:
## ----------
## Since these tests change session and system state (constrained language and system lockdown)
## they will all use try/finally blocks instead of Pester AfterEach/AfterAll to ensure session
## and system state is restored.
## Pester AfterEach, AfterAll is not reliable when the session is constrained language or locked down.
##
Import-Module HelpersSecurity
try
{
$defaultParamValues = $PSDefaultParameterValues.Clone()
$PSDefaultParameterValues["it:Skip"] = !$IsWindows
Describe "Trusted module on locked down machine should not expose private functions to script debugger command processing" -Tags 'CI','RequireAdminOnWindows' {
BeforeAll {
# Debugger test type definition
$debuggerTestTypeDef = @'
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace TestRunner
{
public class DebuggerTester
{
private Runspace _runspace;
private readonly string _privateFnName;
[Flags]
public enum TestResults
{
NoResult = 0x0,
DebuggerStopHandled = 0x1,
PrivateFnFound = 0x2
};
public TestResults TestResult
{
private set;
get;
}
public Exception ScriptException
{
private set;
get;
}
public DebuggerTester(Runspace runspace, string privateFnName)
{
if (runspace.Debugger == null)
{
throw new PSArgumentException("The provided runspace script debugger cannot be null for test.");
}
_runspace = runspace;
_privateFnName = privateFnName;
_runspace.Debugger.DebuggerStop += (sender, args) =>
{
try
{
// Within the debugger stop handler, make sure trusted private functions are not accessible.
string commandText = string.Format(@"Get-Command ""{0}""", _privateFnName);
PSCommand command = new PSCommand();
command.AddCommand(new Command(commandText, true));
PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
_runspace.Debugger.ProcessCommand(command, output);
if ((output.Count > 0) && (output[0].BaseObject is CommandInfo))
{
TestResult |= TestResults.PrivateFnFound;
}
}
catch (Exception e)
{
ScriptException = e;
System.Console.WriteLine(e.Message);
}
TestResult |= TestResults.DebuggerStopHandled;
};
}
}
}
'@
$modulePath = Join-Path $TestDrive Modules
if (Test-Path -Path $modulePath)
{
try { Remove-Item -Path $modulePath -Recurse -Force -ErrorAction SilentlyContinue } catch { }
}
# Trusted module
$trustedModuleName = "TrustedModule_System32"
$trustedModuleDirectory = Join-Path $modulePath $trustedModuleName
New-Item -ItemType Directory -Path $trustedModuleDirectory -Force -ErrorAction SilentlyContinue
$trustedModuleFilePath = Join-Path $trustedModuleDirectory "$($trustedModuleName).psm1"
$trustedManifestFilePath = Join-Path $trustedModuleDirectory "$($trustedModuleName).psd1"
@'
function PublicFn {
Write-Output PrivateFn "PublicFn"
}
function PrivateFn {
param ([string] $msg)
Write-Output $msg
}
'@ > $trustedModuleFilePath
$modManifest = "@{ ModuleVersion = '1.0'" + ("; RootModule = '{0}'" -f $trustedModuleFilePath) + "; FunctionsToExport = 'PublicFn' }"
$modManifest > $trustedManifestFilePath
# Create test runspace
[runspace] $runspace = [runspacefactory]::CreateRunspace()
$runspace.Open()
# Create debugger test object
Add-Type -TypeDefinition $debuggerTestTypeDef
}
AfterAll {
if ($runspace -ne $null) { $runspace.Dispose() }
}
It "Verifies that private trusted module function is not available in script debugger" {
# Run debugger access test
$debuggerTester = [TestRunner.DebuggerTester]::new($runspace, "PrivateFn")
# Script to invoke the script debugger so that $debuggerTester can handle
# the debugger stop event and test for access of private functions within the
# script debugger command processor.
$script = @'
Import-Module -Name HelpersSecurity
Import-Module -Name {0} -Force
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
Import-Module -Name {1} -Force
Set-PSBreakpoint -Command PublicFn
PublicFn
'@ -f "$languageModuleDirectory\TestCmdletForConstrainedLanguage.dll", $trustedManifestFilePath
[powershell] $ps = [powershell]::Create()
$ps.Runspace = $runspace
try
{
$ps.AddScript($script).BeginInvoke()
# Wait for debugger test result for up to ten seconds
$count = 0
while (($debuggerTester.TestResult -eq 0) -and ($count++ -lt 40))
{
Start-Sleep -Milliseconds 250
}
}
finally
{
# Revert lockdown
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
# Verify that PrivateFn function name is not accessible
$debuggerTester.TestResult | Should -Match "DebuggerStopHandled"
$debuggerTester.TestResult | Should -Not -Match "PrivateFnFound"
$debuggerTester.ScriptException | Should -BeNullOrEmpty
}
}
Describe "Cross language debugger get-item commands should not have access to FullLanguage trusted functions through provider" -Tags 'Feature','RequireAdminOnWindows' {
BeforeAll {
# Trusted module that will always run in FullLanguage mode
$scriptModuleName = "ImportTrustedModuleForTestA_System32"
$moduleFilePath = Join-Path $TestDrive ($scriptModuleName + ".psm1")
$script = @'
function PublicFn { "PublicFn"; PrivateFn }
function PrivateFn { "PrivateFn" }
Export-ModuleMember -Function PublicFn
'@
$script > $moduleFilePath
# Import and run module function script
$scriptIM = @'
Import-Module -Name {0} -Force
$null = Set-PSBreakpoint -command PublicFn
PublicFn
'@ -f $moduleFilePath
# Debugger stop event handler object.
$type = @'
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public class DebuggerStopEventHandler
{
private Runspace _runspace;
public object GetItemResult
{
get;
internal set;
}
public object GetChildItemResult
{
get;
internal set;
}
public object CopyItemResult
{
get;
internal set;
}
public object FunctionVariableResult
{
get;
internal set;
}
public object RenameItemResult
{
get;
internal set;
}
public DebuggerStopEventHandler(Runspace runspace)
{
_runspace = runspace;
_runspace.Debugger.DebuggerStop += (sender, args) =>
{
var debugger = sender as Debugger;
PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
PSCommand command = new PSCommand();
command.AddScript(@"Get-Item -Path function:\PrivateFn 2>&1");
debugger.ProcessCommand(command, output);
GetItemResult = (output.Count > 0) ? (output[0].BaseObject) : null;
command.Clear();
output.Clear();
command.AddScript(@"Get-ChildItem -Path function:\PrivateFn 2>&1");
debugger.ProcessCommand(command, output);
GetChildItemResult = (output.Count > 0) ? (output[0].BaseObject) : null;
command.Clear();
output.Clear();
command.AddScript(@"Copy-Item -Path function:\PrivateFn -Destination function:\MyPrivateFn 2>&1");
debugger.ProcessCommand(command, output);
CopyItemResult = (output.Count > 0) ? (output[0].BaseObject) : null;
command.Clear();
output.Clear();
command.AddScript(@"${function:\PrivateFn}");
debugger.ProcessCommand(command, output);
FunctionVariableResult = (output.Count > 0) ? (output[0].BaseObject): null;
command.Clear();
output.Clear();
command.AddScript(@"Rename-Item -Path function:\PrivateFn -NewName function:\MyPrivateFn -Passthru 2>&1");
debugger.ProcessCommand(command, output);
RenameItemResult = (output.Count > 0) ? (output[0].BaseObject) : null;
};
}
public void Reset() { GetItemResult = null; GetChildItemResult = null; CopyItemResult = null; }
}
'@
try { Add-Type -TypeDefinition $type } catch { }
# Create runspace and debugger event handler
[runspace] $rs = [runspacefactory]::CreateRunspace($host)
$rs.Open()
$rs.Debugger.SetDebugMode(@('LocalScript','RemoteScript'))
$debuggerStopHandler = [DebuggerStopEventHandler]::New($rs)
# Create PowerShell to run module script
[powershell] $ps = [powershell]::Create()
$ps.Runspace = $rs
$ps.AddScript($scriptIM)
}
AfterAll {
if ($rs -ne $null) { $rs.Dispose() }
if ($ps -ne $null) { $ps.Dispose() }
}
It "Verifies that same language mode trusted public functions *are* accessible from debugger through Get-Item, Get-ChildItem, Copy-Item, Rename-Item, Variable" {
# Test
$results = $ps.Invoke()
# Results. Only PublicFn is returned since PrivateFn is renamed.
$results[0] | Should Be "PublicFn"
# Expected Get-Item function:\PrivateFn returns FunctionInfo object
($debuggerStopHandler.GetItemResult -is [System.Management.Automation.FunctionInfo]) | Should Be $true
# Expected Get-ChildItem function:\PrivateFn returns FunctionInfo object
($debuggerStopHandler.GetChildItemResult -is [System.Management.Automation.FunctionInfo]) | Should Be $true
# Expected Copy-Item function:\PrivateFn succeeds with no error output
$debuggerStopHandler.CopyItemResult | Should Be $null
# Expected function variable succeeds
($debuggerStopHandler.FunctionVariableResult -is [scriptblock]) | Should Be $true
# Expected Rename-Item function:\PrivateFn returns FunctionInfo object
($debuggerStopHandler.RenameItemResult -is [System.Management.Automation.FunctionInfo]) | Should Be $true
}
It "Verifies that cross language mode trusted public functions *are not* accessible through Get-Item, Get-ChildItem, Copy-Item, Rename-Item, Variable" {
# Test
$debuggerStopHandler.Reset()
try
{
$rs.LanguageMode = "ConstrainedLanguage"
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$results = $ps.Invoke()
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
# Results
$results[0] | Should Be "PublicFn"
$results[1] | Should Be "PrivateFn"
# Expected Get-Item function:\PrivateFn returns error
$debuggerStopHandler.GetItemResult.FullyQualifiedErrorId | Should Be "PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand"
# Expected Get-ChildItem function:\PrivateFn returns error
$debuggerStopHandler.GetChildItemResult.FullyQualifiedErrorId | Should Be "PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand"
# Expected Copy-Item fails with error output
$debuggerStopHandler.CopyItemResult.FullyQualifiedErrorId | Should Be "PathNotFound,Microsoft.PowerShell.Commands.CopyItemCommand"
# Expected function variable fails
$debuggerStopHandler.FunctionVariableResult | Should Be $null
# Expected Rename-Item function:\PrivateFn fails with error
$debuggerStopHandler.RenameItemResult.FullyQualifiedErrorId | Should Be "PathNotFound,Microsoft.PowerShell.Commands.RenameItemCommand"
}
}
Describe "Cross language debugger Action scripts should not have access to FullLanguage trusted functions through provider" -Tags 'Feature','RequireAdminOnWindows' {
BeforeAll {
# Trusted script that will always run in FullLanguage mode
$scriptFileName = "TrustedScriptForTestB_System32"
$scriptFilePath = Join-Path $TestDrive ($scriptFileName + ".ps1")
$script = @'
function PublicFn { PrivateFn -typeDef 'public class Hello { public new static void ToString() { System.Console.WriteLine("Hello!"); } }'; [Hello]::ToString(); }
function PrivateFn { param ([string]$typeDef) Add-Type -TypeDefinition $typeDef }
PublicFn
"Complete"
'@
$script > $scriptFilePath
}
AfterAll {
Get-PSBreakpoint | Remove-PSBreakpoint
}
It "Verifies that debugger stop Action scriptblock cannot access PrivateFn" {
try
{
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
# Set breakpoint on script
Set-PSBreakpoint -Script $scriptFilePath -Line 4 -Action {
& (Get-Item -Path function:\PrivateFn) -typeDef @'
public class Foo {
public new static void ToString() {
System.Console.WriteLine("pwnd!");
}
}
'@
}
# Run script
& $scriptFilePath
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
try
{
# Verify that Action scriptblock did not create Foo type using PrivateFn
[Foo]::ToString()
throw "No Exception!"
}
catch
{
$_.FullyQualifiedErrorId | Should Be "TypeNotFound"
}
}
}
}
finally
{
if ($defaultParamValues -ne $null)
{
$Global:PSDefaultParameterValues = $defaultParamValues
}
}

View file

@ -11,96 +11,156 @@
## Pester AfterEach, AfterAll is not reliable when the session is constrained language or locked down.
##
if ($IsWindows)
{
$code = @'
#region Using directives
using System;
using System.Globalization;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Runtime.InteropServices;
using System.Threading;
using System.Management.Automation;
#endregion
/// <summary>Adds a new type to the Application Domain</summary>
[Cmdlet("Invoke", "LanguageModeTestingSupportCmdlet")]
public sealed class InvokeLanguageModeTestingSupportCmdlet : PSCmdlet
{
[Parameter()]
public SwitchParameter EnableFullLanguageMode
{
get { return enableFullLanguageMode; }
set { enableFullLanguageMode = value; }
}
private SwitchParameter enableFullLanguageMode;
[Parameter()]
public SwitchParameter SetLockdownMode
{
get { return setLockdownMode; }
set { setLockdownMode = value; }
}
private SwitchParameter setLockdownMode;
[Parameter()]
public SwitchParameter RevertLockdownMode
{
get { return revertLockdownMode; }
set { revertLockdownMode = value; }
}
private SwitchParameter revertLockdownMode;
protected override void BeginProcessing()
{
if (enableFullLanguageMode)
{
SessionState.LanguageMode = PSLanguageMode.FullLanguage;
}
if (setLockdownMode)
{
Environment.SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", EnvironmentVariableTarget.Machine);
}
if (revertLockdownMode)
{
Environment.SetEnvironmentVariable("__PSLockdownPolicy", null, EnvironmentVariableTarget.Machine);
}
}
}
'@
if (-not (Get-Command Invoke-LanguageModeTestingSupportCmdlet -ErrorAction Ignore))
{
$moduleName = Get-RandomFileName
$moduleDirectory = join-path $TestDrive\Modules $moduleName
if (-not (Test-Path $moduleDirectory))
{
$null = New-Item -ItemType Directory $moduleDirectory -Force
}
try
{
Add-Type -TypeDefinition $code -OutputAssembly $moduleDirectory\TestCmdletForConstrainedLanguage.dll -ErrorAction Ignore
} catch {}
Import-Module -Name $moduleDirectory\TestCmdletForConstrainedLanguage.dll
}
} # end if ($IsWindows)
Import-Module HelpersSecurity
try
{
$defaultParamValues = $PSDefaultParameterValues.Clone()
$PSDefaultParameterValues["it:Skip"] = !$IsWindows
Describe "Help built-in function should not expose nested module private functions when run on locked down systems" -Tags 'Feature','RequireAdminOnWindows' {
BeforeAll {
$restorePSModulePath = $env:PSModulePath
$env:PSModulePath += ";$TestDrive"
$trustedModuleName1 = "TrustedModule$(Get-Random -Max 999)_System32"
$trustedModulePath1 = Join-Path $TestDrive $trustedModuleName1
mkdir $trustedModulePath1
$trustedModuleFilePath1 = Join-Path $trustedModulePath1 ($trustedModuleName1 + ".psm1")
$trustedModuleManifestPath1 = Join-Path $trustedModulePath1 ($trustedModuleName1 + ".psd1")
$trustedModuleName2 = "TrustedModule$(Get-Random -Max 999)_System32"
$trustedModulePath2 = Join-Path $TestDrive $trustedModuleName2
mkdir $trustedModulePath2
$trustedModuleFilePath2 = Join-Path $trustedModulePath2 ($trustedModuleName2 + ".psm1")
$trustedModuleScript1 = @'
function PublicFn1
{
NestedFn1
PrivateFn1
}
function PrivateFn1
{
"PrivateFn1"
}
'@
$trustedModuleScript1 | Out-File -FilePath $trustedModuleFilePath1
'@{{ FunctionsToExport = "PublicFn1"; ModuleVersion = "1.0"; RootModule = "{0}"; NestedModules = "{1}" }}' -f @($trustedModuleFilePath1,$trustedModuleName2) | Out-File -FilePath $trustedModuleManifestPath1
$trustedModuleScript2 = @'
function NestedFn1
{
"NestedFn1"
"Language mode is $($ExecutionContext.SessionState.LanguageMode)"
}
'@
$trustedModuleScript2 | Out-File -FilePath $trustedModuleFilePath2
}
AfterAll {
$env:PSModulePath = $restorePSModulePath
if ($trustedModuleName1 -ne $null) { Remove-Module -Name $trustedModuleName1 -Force -ErrorAction Ignore }
if ($trustedModuleName2 -ne $null) { Remove-Module -Name $trustedModuleName2 -Force -ErrorAction Ignore }
}
It "Verifies that private functions in trusted nested modules are not globally accessible after running the help function" {
$isCommandAccessible = "False"
try
{
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
$command = @"
Import-Module -Name $trustedModuleName1 -Force -ErrorAction Stop;
"@
$command += @'
$null = help NestedFn1 2>$null;
$result = Get-Command NestedFn1 2>$null;
return ($result -ne $null)
'@
$isCommandAccessible = powershell.exe -noprofile -nologo -c $command
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
# Verify that nested function NestedFn1 was not accessible
$isCommandAccessible | Should -BeExactly "False"
}
}
Describe "NoLanguage runspace pool session should remain in NoLanguage mode when created on a system-locked down machine" -Tags 'Feature','RequireAdminOnWindows' {
BeforeAll {
$configFileName = "RestrictedSessionConfig.pssc"
$configFilePath = Join-Path $TestDrive $configFileName
'@{ SchemaVersion = "2.0.0.0"; SessionType = "RestrictedRemoteServer"}' > $configFilePath
$scriptModuleName = "ImportTrustedModuleForTest_System32"
$moduleFilePath = Join-Path $TestDrive ($scriptModuleName + ".psm1")
$template = @'
function TestRestrictedSession
{{
$iss = [initialsessionstate]::CreateFromSessionConfigurationFile("{0}")
$rsp = [runspacefactory]::CreateRunspacePool($iss)
$rsp.Open()
$ps = [powershell]::Create()
$ps.RunspacePool = $rsp
$null = $ps.AddScript("Hello")
try
{{
$ps.Invoke()
}}
finally
{{
$ps.Dispose()
$rsp.Dispose()
}}
}}
Export-ModuleMember -Function TestRestrictedSession
'@
$template -f $configFilePath > $moduleFilePath
}
It "Verifies that a NoLanguage runspace pool throws the expected 'script not allowed' error" {
try
{
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
$mod = Import-Module -Name $moduleFilePath -Force -PassThru
# Running module function TestRestrictedSession should throw a 'script not allowed' error
# because it runs in a 'no language' session.
try
{
& "$scriptModuleName\TestRestrictedSession"
throw "No Exception!"
}
catch
{
$expectedError = $_
}
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "ScriptsNotAllowed"
}
}
Describe "Built-ins work within constrained language" -Tags 'Feature','RequireAdminOnWindows' {
BeforeAll {
@ -148,17 +208,30 @@ try
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$expectedErrorId | Should BeExactly "MethodInvocationNotSupportedInConstrainedLanguage"
$expectedErrorId | Should -BeExactly "MethodInvocationNotSupportedInConstrainedLanguage"
}
}
Context "Background jobs within inconsistent mode" {
It "Verifies that background job is denied when mode is inconsistent" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ Start-Job { [object]::Equals("A", "B") } } | Should -Throw -ErrorId "CannotStartJobInconsistentLanguageMode"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
Start-Job { [object]::Equals("A", "B") }
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CannotStartJobInconsistentLanguageMode,Microsoft.PowerShell.Commands.StartJobCommand"
}
}
}
@ -166,15 +239,28 @@ try
Describe "Add-Type in constrained language" -Tags 'Feature','RequireAdminOnWindows' {
It "Verifies Add-Type fails in constrained language mode" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ Add-Type -TypeDefinition 'public class ConstrainedLanguageTest { public static string Hello = "HelloConstrained"; }' } |
Should -Throw -ErrorId "CannotDefineNewType"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
Add-Type -TypeDefinition 'public class ConstrainedLanguageTest { public static string Hello = "HelloConstrained"; }'
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CannotDefineNewType,Microsoft.PowerShell.Commands.AddTypeCommand"
}
It "Verifies Add-Type works back in full language mode again" {
Add-Type -TypeDefinition 'public class AfterFullLanguageTest { public static string Hello = "HelloAfter"; }'
[AfterFullLanguageTest]::Hello | Should -Be "HelloAfter"
[AfterFullLanguageTest]::Hello | Should -BeExactly "HelloAfter"
}
}
@ -199,10 +285,23 @@ try
}
It "Verifies New-Object throws error in constrained language for disallowed IntPtr type" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ New-Object System.IntPtr 1234 } | Should -Throw -ErrorId "CannotCreateTypeConstrainedLanguage"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
New-Object System.IntPtr 1234
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CannotCreateTypeConstrainedLanguage,Microsoft.PowerShell.Commands.NewObjectCommand"
}
It "Verifies New-Object works for IntPtr type back in full language mode again" {
@ -214,12 +313,25 @@ try
Context "New-Object with COM types" {
It "Verifies New-Object with COM types is disallowed in system lock down" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
{ New-Object -Com ADODB.Parameter } | Should -Throw -ErrorId "CannotCreateComTypeConstrainedLanguage"
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
New-Object -Com ADODB.Parameter
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CannotCreateComTypeConstrainedLanguage,Microsoft.PowerShell.Commands.NewObjectCommand"
}
It "Verifies New-Object with COM types works back in full language mode again" {
@ -233,22 +345,48 @@ try
Describe "New-Item command on function drive in constrained language" -Tags 'Feature','RequireAdminOnWindows' {
It "Verifies New-Item directory on function drive is not allowed in constrained language mode" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ $null = New-Item -Path function:\SomeEvilFunction -ItemType Directory -Value SomeBadScriptBlock -ErrorAction Stop } |
Should -Throw -ErrorId "NotSupported"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
$null = New-Item -Path function:\SomeEvilFunction -ItemType Directory -Value SomeBadScriptBlock -ErrorAction Stop
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "NotSupported,Microsoft.PowerShell.Commands.NewItemCommand"
}
}
Describe "Script debugging in constrained language" -Tags 'Feature','RequireAdminOnWindows' {
It "Verifies that a debugging breakpoint cannot be set in constrained language and no system lockdown" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
function MyDebuggerFunction {}
{ Set-PSBreakpoint -Command MyDebuggerFunction } | Should -Throw -ErrorId "CannotSetBreakpointInconsistentLanguageMode"
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
function MyDebuggerFunction {}
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
Set-PSBreakpoint -Command MyDebuggerFunction
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CannotSetBreakpointInconsistentLanguageMode,Microsoft.PowerShell.Commands.SetPSBreakpointCommand"
}
It "Verifies that a debugging breakpoint can be set in constrained language with system lockdown" {
@ -268,23 +406,36 @@ try
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$Global:DebuggingOk | Should -Be "DebuggingOk"
$Global:DebuggingOk | Should -BeExactly "DebuggingOk"
}
It "Verifies that debugger commands do not run in full language mode when system is locked down" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
function MyDebuggerFunction3 {}
try
{
$null = Set-PSBreakpoint -Command MyDebuggerFunction3 -Action { $Global:dbgResult = [object]::Equals("A", "B") }
$restoreEAPreference = $ErrorActionPreference
$ErrorActionPreference = "Stop"
MyDebuggerFunction3
} | Should -Throw -ErrorId "CannotSetBreakpointInconsistentLanguageMode"
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
if ($restoreEAPreference -ne $null) { $ErrorActionPreference = $restoreEAPreference }
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
function MyDebuggerFunction3 {}
& {
$null = Set-PSBreakpoint -Command MyDebuggerFunction3 -Action { $Global:dbgResult = [object]::Equals("A", "B") }
$restoreEAPreference = $ErrorActionPreference
$ErrorActionPreference = "Stop"
MyDebuggerFunction3
}
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
if ($restoreEAPreference -ne $null) { $ErrorActionPreference = $restoreEAPreference }
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CannotSetBreakpointInconsistentLanguageMode,Microsoft.PowerShell.Commands.SetPSBreakpointCommand"
}
It "Verifies that debugger command injection is blocked in system lock down" {
@ -352,10 +503,21 @@ try
Import-Module PSDiagnostics
$module = Get-Module PSDiagnostics
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ & $module { [object]::Equals("A", "B") } } | Should -Throw -ErrorId "MethodInvocationNotSupportedInConstrainedLanguage"
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
& $module { [object]::Equals("A", "B") }
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CantInvokeCallOperatorAcrossLanguageBoundaries"
}
}
@ -427,35 +589,73 @@ try
param ($scriptblock)
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ & $scriptblock } | Should -Throw -ErrorId "MethodInvocationNotSupportedInConstrainedLanguage"
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
& $scriptblock
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
$expectedError.FullyQualifiedErrorId | Should -BeExactly "MethodInvocationNotSupportedInConstrainedLanguage,Microsoft.PowerShell.Commands.InvokeExpressionCommand"
}
}
Describe "Dynamic method invocation in constrained language mode" -Tags 'Feature','RequireAdminOnWindows' {
It "Verifies dynamic method invocation does not bypass constrained language mode" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{
$type = [IO.Path]
$method = "GetRandomFileName"
$type::$method()
} | Should -Throw -ErrorId "MethodInvocationNotSupportedInConstrainedLanguage"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
& {
$type = [IO.Path]
$method = "GetRandomFileName"
$type::$method()
}
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "MethodInvocationNotSupportedInConstrainedLanguage"
}
It "Verifies dynamic methods invocation does not bypass constrained language mode" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{
$type = [IO.Path]
$methods = "GetRandomFileName","GetTempPath"
$type::($methods[0])()
} | Should -Throw -ErrorId "MethodInvocationNotSupportedInConstrainedLanguage"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
& {
$type = [IO.Path]
$methods = "GetRandomFileName","GetTempPath"
$type::($methods[0])()
}
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "MethodInvocationNotSupportedInConstrainedLanguage"
}
}
@ -481,19 +681,43 @@ try
Describe "Variable AllScope in constrained language mode" -Tags 'Feature','RequireAdminOnWindows' {
It "Verifies Set-Variable cannot create AllScope in constrained language" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ Set-Variable -Name SetVariableAllScopeNotSupported -Value bar -Option AllScope } |
Should -Throw -ErrorId "NotSupported"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
Set-Variable -Name SetVariableAllScopeNotSupported -Value bar -Option AllScope
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "NotSupported,Microsoft.PowerShell.Commands.SetVariableCommand"
}
It "Verifies New-Variable cannot create AllScope in constrained language" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ New-Variable -Name NewVarialbeAllScopeNotSupported -Value bar -Option AllScope } |
Should -Throw -ErrorId "NotSupported"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
New-Variable -Name NewVarialbeAllScopeNotSupported -Value bar -Option AllScope
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "NotSupported,Microsoft.PowerShell.Commands.NewVariableCommand"
}
}
@ -501,9 +725,15 @@ try
function InvokeDataSectionConstrained
{
$e = { Invoke-Expression 'data foo -SupportedCommand Add-Type { Add-Type }' } | Should -Throw -PassThru
return $e
try
{
Invoke-Expression 'data foo -SupportedCommand Add-Type { Add-Type }'
throw "No Exception!"
}
catch
{
return $_
}
}
It "Verifies data section Add-Type additional command is disallowed in constrained language" {
@ -526,26 +756,50 @@ try
}
It "Verifies data section with no-constant expression Add-Type additional command is disallowed in constrained language" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
$addedCommand = "Add-Type"
{ Invoke-Expression 'data foo -SupportedCommand $addedCommand { Add-Type }' } |
Should -Throw -ErrorId "DataSectionAllowedCommandDisallowed"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
$addedCommand = "Add-Type"
Invoke-Expression 'data foo -SupportedCommand $addedCommand { Add-Type }'
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "DataSectionAllowedCommandDisallowed,Microsoft.PowerShell.Commands.InvokeExpressionCommand"
}
}
Describe "Import-LocalizedData additional commands in constrained language" -Tags 'Feature','RequireAdminOnWindows' {
It "Verifies Import-LocalizedData disallows Add-Type in constrained language" {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{
$localizedDataFileName = Join-Path $TestDrive ImportLocalizedDataAdditionalCommandsNotSupported.psd1
$null = New-Item -ItemType File -Path $localizedDataFileName -Force
Import-LocalizedData -SupportedCommand Add-Type -BaseDirectory $TestDrive -FileName ImportLocalizedDataAdditionalCommandsNotSupported
} | Should -Throw -ErrorId "CannotDefineSupportedCommand"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
& {
$localizedDataFileName = Join-Path $TestDrive ImportLocalizedDataAdditionalCommandsNotSupported.psd1
$null = New-Item -ItemType File -Path $localizedDataFileName -Force
Import-LocalizedData -SupportedCommand Add-Type -BaseDirectory $TestDrive -FileName ImportLocalizedDataAdditionalCommandsNotSupported
}
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "CannotDefineSupportedCommand,Microsoft.PowerShell.Commands.ImportLocalizedData"
}
}
@ -634,14 +888,27 @@ try
param (
[string] $script
)
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{
# Scriptblock must be created inside constrained language.
$sb = [scriptblock]::Create($script)
& sb
} | Should -Throw -ErrorId "MethodInvocationNotSupportedInConstrainedLanguage"
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
& {
# Scriptblock must be created inside constrained language.
$sb = [scriptblock]::Create($script)
& sb
}
throw "No Exception!"
}
catch
{
$expectedError = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$expectedError.FullyQualifiedErrorId | Should -BeExactly "MethodInvocationNotSupportedInConstrainedLanguage"
}
}
@ -660,12 +927,13 @@ try
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$results = Start-ThreadJob -ScriptBlock { $ExecutionContext.SessionState.LanguageMode } | Wait-Job | Receive-Job
$results | Should BeExactly "ConstrainedLanguage"
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$results | Should -BeExactly "ConstrainedLanguage"
}
It "ThreadJob script block using variable must run in ConstrainedLanguage mode with system lock down" {
@ -676,12 +944,13 @@ try
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$results = Start-ThreadJob -ScriptBlock { & $using:sb } | Wait-Job | Receive-Job
$results | Should BeExactly "ConstrainedLanguage"
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$results | Should -BeExactly "ConstrainedLanguage"
}
It "ThreadJob script block argument variable must run in ConstrainedLanguage mode with system lock down" {
@ -692,12 +961,13 @@ try
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$results = Start-ThreadJob -ScriptBlock { param ($sb) & $sb } -ArgumentList $sb | Wait-Job | Receive-Job
$results | Should BeExactly "ConstrainedLanguage"
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$results | Should -BeExactly "ConstrainedLanguage"
}
It "ThreadJob script block piped variable must run in ConstrainedLanguage mode with system lock down" {
@ -708,12 +978,13 @@ try
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$results = $sb | Start-ThreadJob -ScriptBlock { $input | foreach { & $_ } } | Wait-Job | Receive-Job
$results | Should BeExactly "ConstrainedLanguage"
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
$results | Should -BeExactly "ConstrainedLanguage"
}
}

View file

@ -0,0 +1,65 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
##
## ----------
## Test Note:
## ----------
## Since these tests change session and system state (constrained language and system lockdown)
## they will all use try/finally blocks instead of Pester AfterEach/AfterAll to ensure session
## and system state is restored.
## Pester AfterEach, AfterAll is not reliable when the session is constrained language or locked down.
##
Import-Module HelpersSecurity
try
{
$defaultParamValues = $PSDefaultParameterValues.Clone()
$PSDefaultParameterValues["it:Skip"] = !$IsWindows
Describe "Importing PowerShell script files are not allowed in ConstrainedLanguage" -Tags 'CI','RequireAdminOnWindows' {
BeforeAll {
$scriptFileName = (Get-RandomFileName) + ".ps1"
$scriptFilePath = Join-Path $TestDrive $scriptFileName
'"Hello!"' > $scriptFilePath
}
It "Verifies that ps1 script file cannot be imported in ConstrainedLanguage mode" {
$err = $null
try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
Import-Module -Name $scriptFilePath
throw "No Exception!"
}
catch
{
$err = $_
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
$err.FullyQualifiedErrorId | Should -BeExactly "Modules_ImportPSFileNotAllowedInConstrainedLanguage,Microsoft.PowerShell.Commands.ImportModuleCommand"
}
It "Verifies that ps1 script file can be imported in FullLangauge mode" {
{ Import-Module -Name $scriptFilePath } | Should -Not -Throw
}
}
# End Describe blocks
}
finally
{
if ($defaultParamValues -ne $null)
{
$Global:PSDefaultParameterValues = $defaultParamValues
}
}

View file

@ -0,0 +1,71 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Import-Module HelpersRemoting
Describe "Remote runspace pool should expose commands in endpoint configuration" -Tags 'Feature','RequireAdminOnWindows' {
BeforeAll {
if ($isWindows)
{
$configName = "restrictedV"
$configPath = Join-Path $TestDrive ($configName + ".pssc")
New-PSSessionConfigurationFile -Path $configPath -SessionType RestrictedRemoteServer -VisibleCmdlets 'Get-CimInstance'
$null = Register-PSSessionConfiguration -Name $configName -Path $configPath -Force -ErrorAction SilentlyContinue
$remoteRunspacePool = New-RemoteRunspacePool -ConfigurationName $configName
}
}
AfterAll {
if ($IsWindows)
{
if ($remoteRunspacePool -ne $null)
{
$remoteRunspacePool.Dispose()
}
Unregister-PSSessionConfiguration -Name $configName -Force -ErrorAction SilentlyContinue
}
}
It "Verifies that the configured endpoint cmdlet is available in all runspace pool instances" -Skip:(! $IsWindows) {
[powershell] $ps1 = [powershell]::Create()
$ps1.RunspacePool = $remoteRunspacePool
$null = $ps1.AddCommand('Get-Command').AddParameter('Name','Get-CimInstance')
[powershell] $ps2 = [powershell]::Create()
$ps2.RunspacePool = $remoteRunspacePool
$null = $ps2.AddCommand('Get-Command').AddParameter('Name','Get-CimInstance')
[powershell] $ps3 = [powershell]::Create()
$ps3.RunspacePool = $remoteRunspacePool
$null = $ps3.AddCommand('Get-Command').AddParameter('Name','Get-CimInstance')
[powershell] $ps4 = [powershell]::Create()
$ps4.RunspacePool = $remoteRunspacePool
$null = $ps4.AddCommand('Get-Command').AddParameter('Name','Get-CimInstance')
# Invoke all four simultaneously
$a1 = $ps1.BeginInvoke()
$a2 = $ps2.BeginInvoke()
$a3 = $ps3.BeginInvoke()
$a4 = $ps4.BeginInvoke()
# Wait for completion
$r1 = $ps1.EndInvoke($a1)
$r2 = $ps2.EndInvoke($a2)
$r3 = $ps3.EndInvoke($a3)
$r4 = $ps4.EndInvoke($a4)
$r1.Name | Should -BeExactly 'Get-CimInstance'
$r2.Name | Should -BeExactly 'Get-CimInstance'
$r3.Name | Should -BeExactly 'Get-CimInstance'
$r4.Name | Should -BeExactly 'Get-CimInstance'
}
}

View file

@ -0,0 +1,560 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe "UntrustedDataMode tests for variable assignments" -Tags 'CI' {
BeforeAll {
$testModule = Join-Path $TestDrive "UntrustedDataModeTest.psm1"
Set-Content -Path $testModule -Value @'
$scriptVar = 15
$Global:globalVar = "Hello"
## A script cmdlet, it goes through CmdletParameterBinderController
function Test-Untrusted
{
[CmdletBinding()]
param(
[Parameter()]
[ValidateTrustedData()]
$Argument
)
Write-Output $Argument
}
## A simple function, it goes through ScriptParameterBinderController
function Test-SimpleUntrusted
{
param(
[ValidateTrustedData()]
$Argument
)
Write-Output $Argument
}
## A script cmdlet that tests other parameter types
function Test-OtherParameterType
{
[CmdletBinding()]
param(
[Parameter()]
[ValidateTrustedData()]
[string[]] $Name,
[Parameter()]
[ValidateTrustedData()]
[DateTime] $Date,
[Parameter()]
[ValidateTrustedData()]
[System.IO.FileInfo] $File,
[Parameter()]
[ValidateTrustedData()]
[System.Diagnostics.ProcessStartInfo] $StartInfo
)
throw "No Validation Exception!"
}
function Test-WithScriptVar { Test-Untrusted -Argument $scriptVar }
function Test-WithGlobalVar { Test-Untrusted -Argument $Global:globalVar }
function Test-SplatScriptVar { Test-Untrusted @scriptVar }
function Test-SplatGlobalVar { Test-Untrusted @Global:globalVar }
function Get-ScriptVar { $scriptVar }
function Get-GlobalVar { $Global:globalVar }
function Set-ScriptVar { $Script:scriptVar = "Trusted-Script" }
function Set-GlobalVar { $Global:globalVar = "Trusted-Global" }
##
## ValidateTrustedData attribute is applied to some powershell cmdlets
## and the functions below are for testing them in FullLanguage
##
function Test-AddType { Add-Type -TypeDefinition $args[0] }
function Test-InvokeExpression { Invoke-Expression -Command $args[0] }
function Test-NewObject { New-Object -TypeName $args[0] }
function Test-ForeachObject { Get-Date | Foreach-Object -MemberName $args[0] }
function Test-ImportModule { Import-Module -Name $args[0] }
function Test-StartJob { Start-Job -ScriptBlock $args[0] }
'@
## Use a different runspace
$ps = [powershell]::Create()
## Helper function to execute script
function Execute-Script
{
param([string]$Script)
$ps.Commands.Clear()
$ps.Streams.ClearStreams()
$ps.AddScript($Script).Invoke()
}
## Import the module and verify the original behavior of functions
## exposed from UntrustedDataModeTest in FullLanguage
Execute-Script -Script "Import-Module $testModule"
## Set the LanguageMode to be 'ConstrainedLanguage'
Execute-Script -Script '$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"'
## Assign the ModuleInfo object to $mo
Execute-Script -Script '$mo = Get-Module UntrustedDataModeTest'
}
AfterAll {
## Clean up the powershell object
$ps.Dispose()
## Set the LanguageMode to force rebuilding the type conversion cache.
## This is needed because type conversions that happen in the new powershell runspace with 'ConstrainedLanguage' mode
## will be put in the type conversion cache, and that may affect the default session.
$ExecutionContext.SessionState.LanguageMode = "FullLanguage"
}
It "verify the initial state of the test module 'UntrustedDataModeTest'" {
$result = Execute-Script -Script "Test-WithScriptVar"
$result | Should Be 15
$result = Execute-Script -Script "Test-WithGlobalVar"
$result | Should Be "Hello"
$result = Execute-Script -Script "Get-ScriptVar"
$result | Should Be 15
$result = Execute-Script -Script "Get-GlobalVar"
$result | Should Be "Hello"
$result = Execute-Script -Script '$ExecutionContext.SessionState.LanguageMode'
$result | Should Be "ConstrainedLanguage"
}
Context "Set global variable value in top-level session state" {
BeforeAll {
$testScript = @'
Get-GlobalVar
try { Test-WithGlobalVar } catch { $_.FullyQualifiedErrorId }
'@
$testCases = @(
## Assignment in language
@{
Name = 'language in global scope'
SetupScript = '$globalVar = "language in global scope"'
ExpectedOutput = "language in global scope;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'language in sub scope'
SetupScript = '& { $Global:globalVar = "language in sub scope" }'
ExpectedOutput = "language in sub scope;ParameterArgumentValidationError,Test-Untrusted"
},
## New-Variable
@{
Name = 'New-Variable in global scope'
SetupScript = 'New-Variable globalVar -Value "New-Variable in global scope" -Force'
ExpectedOutput = "New-Variable in global scope;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = "New-Variable in sub scope with [-Scope Global]"
SetupScript = '& { New-Variable globalVar -Value "New-Variable in sub scope with [-Scope Global]" -Force -Scope Global }'
ExpectedOutput = "New-Variable in sub scope with [-Scope Global];ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = "New-Variable in sub scope with [-Scope 1]"
SetupScript = '& { New-Variable globalVar -Value "New-Variable in sub scope with [-Scope 1]" -Force -Scope 1 }'
ExpectedOutput = "New-Variable in sub scope with [-Scope 1];ParameterArgumentValidationError,Test-Untrusted"
},
## Set-Variable
@{
Name = 'Set-Variable in global scope'
SetupScript = 'Set-Variable globalVar -Value "Set-Variable in global scope" -Force'
ExpectedOutput = "Set-Variable in global scope;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'Set-Variable in sub scope with [-Scope Global]'
SetupScript = '& { Set-Variable globalVar -Value "Set-Variable in sub scope with [-Scope Global]" -Scope Global }'
ExpectedOutput = "Set-Variable in sub scope with [-Scope Global];ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'Set-Variable in sub scope with [-Scope 1]'
SetupScript = '& { Set-Variable globalVar -Value "Set-Variable in sub scope with [-Scope 1]" -Scope 1 }'
ExpectedOutput = "Set-Variable in sub scope with [-Scope 1];ParameterArgumentValidationError,Test-Untrusted"
},
## New-Item
@{
Name = 'New-Item in global scope'
SetupScript = 'New-Item variable:\globalVar -Value "New-Item in global scope" -Force'
ExpectedOutput = "New-Item in global scope;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'New-Item in sub scope'
## New-Item in sub scope won't affect global variable
SetupScript = 'Set-GlobalVar; & { New-Item variable:\globalVar -Value "New-Item in sub scope" -Force }'
ExpectedOutput = "Trusted-Global;Trusted-Global"
},
## Set-Item
@{
Name = 'Set-Item in global scope'
SetupScript = 'Set-Item variable:\globalVar -Value "Set-Item in global scope" -Force'
ExpectedOutput = "Set-Item in global scope;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'Set-Item in sub scope'
## Set-Item in sub scope won't affect global variable
SetupScript = 'Set-GlobalVar; & { Set-Item variable:\globalVar -Value "Set-Item in sub scope" -Force }'
ExpectedOutput = "Trusted-Global;Trusted-Global"
},
## Error Variable
@{
Name = 'ErrorVariable in global scope'
SetupScript = 'Write-Error "Error" -ErrorAction SilentlyContinue -ErrorVariable globalVar'
ExpectedOutput = "Error;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'ErrorVariable in sub scope'
SetupScript = '& { Write-Error "Error-in-Sub" -ErrorAction SilentlyContinue -ErrorVariable global:globalVar }'
ExpectedOutput = "Error-in-Sub;ParameterArgumentValidationError,Test-Untrusted"
},
## Out Variable
@{
Name = 'OutVariable in global scope'
SetupScript = 'Write-Output "Out" -OutVariable globalVar'
ExpectedOutput = "Out;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'OutVariable in sub scope'
SetupScript = '& { Write-Output "Out-in-Sub" -OutVariable global:globalVar }'
ExpectedOutput = "Out-in-Sub;ParameterArgumentValidationError,Test-Untrusted"
},
## Warning Variable
@{
Name = 'WarningVariable in global scope'
SetupScript = 'Write-Warning "Warning" -WarningAction SilentlyContinue -WarningVariable globalVar'
ExpectedOutput = "Warning;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'WarningVariable in sub scope'
SetupScript = '& { Write-Warning "Warning-in-Sub" -WarningAction SilentlyContinue -WarningVariable global:globalVar }'
ExpectedOutput = "Warning-in-Sub;ParameterArgumentValidationError,Test-Untrusted"
},
## Information Variable
@{
Name = 'InformationVariable in global scope'
SetupScript = 'Write-Information "Information" -InformationAction SilentlyContinue -InformationVariable globalVar'
ExpectedOutput = "Information;ParameterArgumentValidationError,Test-Untrusted"
},
@{
Name = 'InformationVariable in sub scope'
SetupScript = '& { Write-Information "Information-in-Sub" -InformationAction SilentlyContinue -InformationVariable global:globalVar }'
ExpectedOutput = "Information-in-Sub;ParameterArgumentValidationError,Test-Untrusted"
},
## Data Section
<# @{
Name = 'Data Section - "data global:var"'
## 'data global:var { }' syntax is not supported today. If it's added someday, this test should be enabled
SetupScript = '& { data global:globalVar { "data section - [global:]" } }'
ExpectedOutput = "data section - [global:];ParameterArgumentValidationError,Test-Untrusted"
}, #>
@{
Name = 'Data Section'
SetupScript = 'data globalVar { "data section" }'
ExpectedOutput = "data section;ParameterArgumentValidationError,Test-Untrusted"
}
)
}
It "<Name>" -TestCases $testCases {
param ($SetupScript, $ExpectedOutput)
Execute-Script -Script $SetupScript > $null
$result = Execute-Script -Script $testScript
$result -join ";" | Should Be $ExpectedOutput
}
It "Enable 'data global:var' test if the syntax is supported" {
try {
[scriptblock]::Create('data global:var { "data section" }')
throw "No Exception!"
} catch {
## Syntax 'data global:var { }' is not supported at the time writting the tests here
## If this test fail, then maybe this syntax is supported now, and in that case, please
## enable the test 'Data Section - "data global:var"' in $testCases above
$_.FullyQualifiedErrorId | Should Be "ParseException"
}
}
}
Context "Set variable in Import-LocalizedData" {
BeforeAll {
$localData = Join-Path $TestDrive "local.psd1"
Set-Content $localData -Value '"Localized-Data"'
}
It "test global variable set by Import-LocalizedData" {
$testScript = @'
Get-GlobalVar
try { Test-WithGlobalVar } catch { $_.FullyQualifiedErrorId }
'@
Execute-Script -Script "Import-LocalizedData -BindingVariable globalVar -BaseDirectory $TestDrive -FileName local.psd1"
$result = Execute-Script -Script $testScript
$result -join ";" | Should Be "Localized-Data;ParameterArgumentValidationError,Test-Untrusted"
}
}
Context "Exported variables by module loading" {
BeforeAll {
## Create a module that exposes two variables
$VarModule = Join-Path $TestDrive "Var.psm1"
Set-Content $VarModule -Value @'
$globalVar = "global-from-module"
$scriptVar = "script-from-module"
Export-ModuleMember -Variable globalVar, scriptVar
'@
$testScript = @'
Get-GlobalVar
try { Test-WithGlobalVar } catch { $_.FullyQualifiedErrorId }
Get-ScriptVar
try { Test-WithScriptVar } catch { $_.FullyQualifiedErrorId }
'@
}
BeforeEach {
## Set both the global and script vars to default value
Execute-Script -Script "Set-ScriptVar; Set-GlobalVar"
}
It "test global variable set by exported variable" {
try {
## Import the module in the global scope of the runspace, so only the
## global variable is affected, the module script variable is not.
Execute-Script -Script "Import-Module $VarModule"
$result = Execute-Script -Script $testScript
$result -join ";" | Should Be "global-from-module;ParameterArgumentValidationError,Test-Untrusted;Trusted-Script;Trusted-Script"
} finally {
Execute-Script -Script "Remove-Module Var -Force"
}
}
}
Context "Splatting of untrusted value" {
It "test splatting global variable" {
$testScript = @'
Get-GlobalVar
try { Test-SplatGlobalVar } catch { $_.FullyQualifiedErrorId }
'@
Execute-Script -Script '$globalVar = @{ Argument = "global-splatting" }'
$result = Execute-Script -Script $testScript
$result -join ";" | Should Be "System.Collections.Hashtable;ParameterArgumentValidationError,Test-Untrusted"
}
}
Context "ValidateTrustedDataAttribute takes NO effect in non-FullLanguage" {
It "test 'ValidateTrustedDataAttribute' NOT take effect in non-FullLanguage [Add-Type]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
$globalVar = "C# Code"
Add-Type -TypeDefinition $globalVar
throw "Expected 'CannotDefineNewType' error was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "CannotDefineNewType,Microsoft.PowerShell.Commands.AddTypeCommand"
}
It "test 'ValidateTrustedDataAttribute' NOT take effect in non-FullLanguage [Invoke-Expression]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
$globalVar = "gps -id $PID"
Invoke-Expression -Command $globalVar | ForEach-Object Id
'@
$result | Should Be $PID
}
It "test 'ValidateTrustedDataAttribute' NOT take effect in non-FullLanguage [New-Object]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
$globalVar = "uri"
New-Object -TypeName $globalVar -ArgumentList 'http://www.bing.com'
'@
$result | Should Not BeNullOrEmpty
}
It "test 'ValidateTrustedDataAttribute' NOT take effect in non-FullLanguage [Foreach-Object]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
$globalVar = "Year"
Get-Date | Foreach-Object -MemberName $globalVar
'@
$result | Should Not BeNullOrEmpty
}
It "test 'ValidateTrustedDataAttribute' NOT take effect in non-FullLanguage [Import-Module]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
$globalVar = "NonExistModule"
Import-Module -Name $globalVar -ErrorAction SilentlyContinue -ErrorVariable ev; $ev
'@
$result | Should Not BeNullOrEmpty
$result.FullyQualifiedErrorId | Should Be "Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand"
}
It "test 'ValidateTrustedDataAttribute' NOT take effect in non-FullLanguage [Start-Job]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
$globalVar = {1+1}
Start-Job -ScriptBlock $globalVar
throw "Expected 'CannotStartJob' error was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "CannotStartJobInconsistentLanguageMode,Microsoft.PowerShell.Commands.StartJobCommand"
}
}
Context "ValidateTrustedDataAttribute takes effect in FullLanguage" {
It "test 'ValidateTrustedDataAttribute' take effect when calling from 'Constrained' to 'Full' [Script Cmdlet]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
$globalVar = "C# Code"
Test-Untrusted -Argument $globalVar
throw "Expected 'ParameterArgumentValidationError' was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "ParameterArgumentValidationError,Test-Untrusted"
}
It "test 'ValidateTrustedDataAttribute' take effect when calling from 'Constrained' to 'Full' [Simple function]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
$globalVar = "C# Code"
Test-SimpleUntrusted -Argument $globalVar
throw "Expected 'ParameterArgumentValidationError' was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "ParameterArgumentValidationError,Test-SimpleUntrusted"
}
It "test 'ValidateTrustedDataAttribute' with param type conversion [string -> string[]]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
$globalVar = "John"
Test-OtherParameterType -Name $globalVar
throw "Expected 'ParameterArgumentValidationError' was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "ParameterArgumentValidationError,Test-OtherParameterType"
}
It "test 'ValidateTrustedDataAttribute' with value type param [DateTime]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
$globalVar = Get-Date
Test-OtherParameterType -Date $globalVar
throw "Expected 'ParameterArgumentValidationError' was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "ParameterArgumentValidationError,Test-OtherParameterType"
}
It "test 'ValidateTrustedDataAttribute' with param type conversion [string -> FileInfo]" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
$globalVar = "FakeFile"
Test-OtherParameterType -File $globalVar
throw "Expected 'ParameterArgumentValidationError' was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "ParameterArgumentValidationError,Test-OtherParameterType"
}
It "test type property conversion to [ProcessStartInfo] should fail during Lang-Mode transition" {
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$result = Execute-Script -Script @'
try {
Test-OtherParameterType -StartInfo @{ FileName = "File" }
throw "Expected conversion error was not thrown"
} catch {
$_.FullyQualifiedErrorId
}
'@
$result | Should Be "ParameterArgumentTransformationError,Test-OtherParameterType"
}
}
Context "Validate trusted data for parameters of some built-in powershell cmdlets" {
BeforeAll {
$ScriptTemplate = @'
try {{
{0}
throw "Expected 'ParameterArgumentValidationError' was not thrown"
}} catch {{
$_.FullyQualifiedErrorId
}}
'@
$testCases = @(
@{ Name = "test 'ValidateTrustedDataAttribute' on [Add-Type]"; Argument = '$globalVar = "Global-Value"; Test-AddType $globalVar'; ExpectedErrorId = "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.AddTypeCommand" }
@{ Name = "test 'ValidateTrustedDataAttribute' on [Invoke-Expression]"; Argument = '$globalVar = "Global-Value"; Test-InvokeExpression $globalVar'; ExpectedErrorId = "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeExpressionCommand" }
@{ Name = "test 'ValidateTrustedDataAttribute' on [New-Object]"; Argument = '$globalVar = "Global-Value"; Test-NewObject $globalVar'; ExpectedErrorId = "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewObjectCommand" }
@{ Name = "test 'ValidateTrustedDataAttribute' on [Foreach-Object]"; Argument = '$globalVar = "Global-Value"; Test-ForeachObject $globalVar'; ExpectedErrorId = "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.ForeachObjectCommand" }
@{ Name = "test 'ValidateTrustedDataAttribute' on [Import-Module]"; Argument = '$globalVar = "Global-Value"; Test-ImportModule $globalVar'; ExpectedErrorId = "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.ImportModuleCommand" }
@{ Name = "test 'ValidateTrustedDataAttribute' on [Start-Job]"; Argument = '$globalVar = {1+1}; Test-StartJob $globalVar'; ExpectedErrorId = "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.StartJobCommand" }
)
}
It "<Name>" -TestCases $testCases {
param ($Argument, $ExpectedErrorId)
## Run this in the global scope, so value of $globalVar will be marked as untrusted
$testScript = $ScriptTemplate -f $Argument
$result = Execute-Script -Script $testScript
$result | Should Be $ExpectedErrorId
}
}
}

View file

@ -16,6 +16,6 @@ Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.'
Description = 'Temporary module for remoting tests'
FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession', 'Enter-RemoteSession', 'Invoke-RemoteCommand', 'Connect-RemoteSession'
FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession', 'Enter-RemoteSession', 'Invoke-RemoteCommand', 'Connect-RemoteSession', 'New-RemoteRunspacePool'
}

View file

@ -75,6 +75,39 @@ function New-RemoteRunspace
return $remoteRunspace
}
function New-RemoteRunspacePool
{
param (
[int] $MinRunspace = 1,
[int] $MaxRunspace = 6,
[string] $ConfigurationName
)
$wsmanConnection = [System.Management.Automation.Runspaces.WSManConnectionInfo]::new()
if ($ConfigurationName -ne $null)
{
$wsmanConnection.ShellUri = "http://schemas.microsoft.com/powershell/$ConfigurationName"
}
if ($Script:AppVeyorRemoteCred)
{
Write-Verbose "Using Global AppVeyor Credential" -Verbose
$wsmanConnection.Credential = $Script:AppVeyorRemoteCred
}
else
{
Write-Verbose "Using Implicit Credential" -Verbose
}
[System.Management.Automation.Runspaces.RunspacePool] $remoteRunspacePool = [runspacefactory]::CreateRunspacePool($MinRunspace, $MaxRunspace, $wsmanConnection)
$remoteRunspacePool.Open()
return $remoteRunspacePool
}
function CreateParameters
{
param (

View file

@ -0,0 +1,13 @@
#
# Manifest for 'HelpersSecurity' module
#
@{
RootModule = 'HelpersSecurity.psm1'
ModuleVersion = '1.0'
GUID = '544d00d4-e3b7-46e2-a6a1-8bbf53980e5d'
CompanyName = 'Microsoft Corporation'
Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.'
Description = 'Security tests helper functions'
FunctionsToExport = @()
}

View file

@ -0,0 +1,89 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
if ($IsWindows)
{
Import-Module HelpersCommon
$code = @'
#region Using directives
using System;
using System.Globalization;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Runtime.InteropServices;
using System.Threading;
using System.Management.Automation;
#endregion
/// <summary>Adds a new type to the Application Domain</summary>
[Cmdlet("Invoke", "LanguageModeTestingSupportCmdlet")]
public sealed class InvokeLanguageModeTestingSupportCmdlet : PSCmdlet
{
[Parameter()]
public SwitchParameter EnableFullLanguageMode
{
get { return enableFullLanguageMode; }
set { enableFullLanguageMode = value; }
}
private SwitchParameter enableFullLanguageMode;
[Parameter()]
public SwitchParameter SetLockdownMode
{
get { return setLockdownMode; }
set { setLockdownMode = value; }
}
private SwitchParameter setLockdownMode;
[Parameter()]
public SwitchParameter RevertLockdownMode
{
get { return revertLockdownMode; }
set { revertLockdownMode = value; }
}
private SwitchParameter revertLockdownMode;
protected override void BeginProcessing()
{
if (enableFullLanguageMode)
{
SessionState.LanguageMode = PSLanguageMode.FullLanguage;
}
if (setLockdownMode)
{
Environment.SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", EnvironmentVariableTarget.Machine);
}
if (revertLockdownMode)
{
Environment.SetEnvironmentVariable("__PSLockdownPolicy", null, EnvironmentVariableTarget.Machine);
}
}
}
'@
if (-not (Get-Command Invoke-LanguageModeTestingSupportCmdlet -ErrorAction Ignore))
{
$moduleName = Get-RandomFileName
$moduleDirectory = join-path $TestDrive\Modules $moduleName
if (-not (Test-Path $moduleDirectory))
{
$null = New-Item -ItemType Directory $moduleDirectory -Force
}
try
{
Add-Type -TypeDefinition $code -OutputAssembly $moduleDirectory\TestCmdletForConstrainedLanguage.dll -ErrorAction Ignore
} catch {}
Import-Module -Name $moduleDirectory\TestCmdletForConstrainedLanguage.dll
}
}