Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/System.Management.Automation/engine/parser/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ internal static class CachedReflectionInfo
internal static readonly MethodInfo PSSetMemberBinder_SetAdaptedValue =
typeof(PSSetMemberBinder).GetMethod(nameof(PSSetMemberBinder.SetAdaptedValue), StaticFlags);

internal static readonly MethodInfo PSSetMemberBinder_SetInstanceMemberValue =
typeof(PSSetMemberBinder).GetMethod(nameof(PSSetMemberBinder.SetInstanceMemberValue), StaticFlags);

internal static readonly MethodInfo PSTraceSource_WriteLine =
typeof(PSTraceSource).GetMethod(nameof(PSTraceSource.WriteLine), InstanceFlags, new[] { typeof(string), typeof(object) });

Expand Down
103 changes: 87 additions & 16 deletions src/System.Management.Automation/engine/runtime/Binding/Binders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6112,9 +6112,10 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy
// If there is an instance member, we generate (roughly) the following:
// PSMemberInfo memberInfo;
// if (PSGetMemberBinder.TryGetInstanceMember(target.Value, Name, out memberInfo))
// memberInfo.Value = value;
// PSSetMemberBinder.SetInstanceMemberValue(memberInfo, target.Value, value);
// else
// update the site
//
// We use a generic method like this because:
// * If one object has an instance property with a given name, it's like many others do as well
// * We want to avoid generating new sites for every object with an instance member
Expand All @@ -6129,19 +6130,28 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy
// but we add the check because the DLR requires a non-empty check when the target implements IDynamicMetaObjectProvider,
// which PSObject does. The version check is also marginally useful if we knew we'd never see another
// instance member with this member name, but we're not tracking things to make that a useful test.
ParameterExpression memberInfoVar = Expression.Variable(typeof(PSMemberInfo));

var memberInfoVar = Expression.Variable(typeof(PSMemberInfo));
var temp = Expression.Variable(typeof(object));
var expr = Expression.Condition(
Expression.Call(CachedReflectionInfo.PSGetMemberBinder_TryGetInstanceMember,
target.Expression.Cast(typeof(object)), Expression.Constant(Name), memberInfoVar),
Expression.Assign(Expression.Property(memberInfoVar, "Value"), value.Expression.Cast(typeof(object))),
ConditionalExpression expr = Expression.Condition(
Expression.Call(
CachedReflectionInfo.PSGetMemberBinder_TryGetInstanceMember,
target.Expression.Cast(typeof(object)),
Expression.Constant(Name),
memberInfoVar),
Expression.Call(
CachedReflectionInfo.PSSetMemberBinder_SetInstanceMemberValue,
memberInfoVar,
target.Expression.Cast(typeof(object)),
value.Expression.Cast(typeof(object))),
this.GetUpdateExpression(typeof(object)));
var bindingRestrictions = BinderUtils.GetVersionCheck(_getMemberBinder, _getMemberBinder._version)

BindingRestrictions bindingRestrictions = BinderUtils.GetVersionCheck(_getMemberBinder, _getMemberBinder._version)
.Merge(value.PSGetTypeRestriction());

return (new DynamicMetaObject(Expression.Block(new[] { memberInfoVar, temp }, expr),
bindingRestrictions)).WriteToDebugLog(this);
return new DynamicMetaObject(
Expression.Block(new[] { memberInfoVar }, expr),
bindingRestrictions)
.WriteToDebugLog(this);
}

if (targetValue is IDictionary)
Expand Down Expand Up @@ -6194,13 +6204,35 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy
{
restrictions = restrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());

// Validate that this is allowed in the current language mode.
DynamicMetaObject runtimeError = PSGetMemberBinder.EnsureAllowedInLanguageMode(
target, targetValue, Name, _static, new[] { value }, restrictions,
"PropertySetterNotSupportedInConstrainedLanguage", ParserStrings.PropertySetConstrainedLanguage);
if (runtimeError != null)
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
if (context is { LanguageMode: PSLanguageMode.ConstrainedLanguage })
{
return runtimeError.WriteToDebugLog(this);
Type constrainedTargetType = targetValue as Type;
if ((!_static) || (constrainedTargetType == null))
{
constrainedTargetType = targetValue.GetType();
}

if (!CoreTypes.Contains(constrainedTargetType))
{
if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit)
{
return target.ThrowRuntimeError(
new[] { value },
restrictions,
"PropertySetterNotSupportedInConstrainedLanguage",
ParserStrings.PropertySetConstrainedLanguage)
.WriteToDebugLog(this);
}

string targetName = constrainedTargetType.FullName ?? string.Empty;
SystemPolicy.LogWDACAuditMessage(
context: context,
title: ParameterBinderStrings.WDACBinderInvocationLogTitle,
message: StringUtil.Format(ParameterBinderStrings.WDACBinderInvocationLogMessage, Name, targetName),
fqid: "MethodOrPropertyInvocationNotAllowed",
dropIntoDebugger: true);
}
}
}

Expand Down Expand Up @@ -6448,6 +6480,45 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy
restrictions).WriteToDebugLog(this);
}

internal static object SetInstanceMemberValue(PSMemberInfo memberInfo, object target, object value)
{
if (ExecutionContext.HasEverUsedConstrainedLanguage)
{
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
// Explicit `GetType() ==` instead of `is` as we only want to allow `PSNoteProperty`,
// classes that inherits from it should be excluded.
if ((context != null) && (context.LanguageMode == PSLanguageMode.ConstrainedLanguage) && (memberInfo.GetType() != typeof(PSNoteProperty)))
{
object baseObject = PSObject.Base(target);
if ((baseObject == null) || !CoreTypes.Contains(baseObject.GetType()))
{
if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit)
{
throw InterpreterError.NewInterpreterException(
target,
typeof(PSInvalidOperationException),
null,
"PropertySetterNotSupportedInConstrainedLanguage",
ParserStrings.PropertySetConstrainedLanguage);
}

SystemPolicy.LogWDACAuditMessage(
context: context,
title: ParameterBinderStrings.WDACBinderInvocationLogTitle,
message: StringUtil.Format(
ParameterBinderStrings.WDACBinderInvocationLogMessage,
memberInfo.Name ?? string.Empty,
baseObject?.GetType().FullName ?? target.GetType().FullName ?? string.Empty),
fqid: "MethodOrPropertyInvocationNotAllowed",
dropIntoDebugger: true);
}
}
}

memberInfo.Value = value;
return value;
}

private DynamicMetaObject GeneratePropertyAssignmentException(BindingRestrictions restrictions)
{
Expression innerException = Expression.New(CachedReflectionInfo.SetValueException_ctor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,41 @@ try
$defaultParamValues = $PSDefaultParameterValues.Clone()
$PSDefaultParameterValues["it:Skip"] = !$IsWindows

Describe "Property setting in constrained language" -Tags 'Feature' {

It "rejects setting an instance script property" {
$obj = [System.Text.StringBuilder]::new()
$obj | Add-Member -MemberType NoteProperty -Name Value -Value 0
$obj | Add-Member -MemberType ScriptProperty -Name Test -Value { $this.Value } -SecondValue { param($value) $this.Value = $value }

try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ $obj.Test = 1 } | Should -Throw -ErrorId "PropertySetterNotSupportedInConstrainedLanguage"
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
}

It "rejects setting an alias property that references a note property" {
$obj = [System.Text.StringBuilder]::new()
$obj | Add-Member -MemberType NoteProperty -Name Value -Value 0
$obj | Add-Member -MemberType AliasProperty -Name Alias -Value Value

try
{
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
{ $obj.Alias = 1 } | Should -Throw -ErrorId "PropertySetterNotSupportedInConstrainedLanguage"
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -EnableFullLanguageMode
}
}
}

Describe "Help built-in function should not expose nested module private functions when run on locked down systems" -Tags 'Feature','RequireAdminOnWindows' {

BeforeAll {
Expand Down
Loading