-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Turn off AMSI member invocation on *nix release builds #24451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
|
||
| internal static void LogMemberInvocation(string targetName, string name, object[] args) | ||
| { | ||
| #if UNIX && !DEBUG |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this, but, given that there seems to be only one call to this method (in Binders.cs), wouldn't it be better for performance reasons to conditionally exclude that call instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah the JIT should optimize it out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should have, but ... does it and at what point (interpreter)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taking a closer look at the call site, it seems that a (cached) reference to the method is being passed as an argument to another method, and it's not clear to me how that could be optimized away (but I'm not familiar with the subtleties of JIT compilation), especially since it seems desirable to prevent that other method call altogether.
PowerShell/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
Lines 6946 to 6958 in 623c451
| // Expression block runs two expressions in order: | |
| // - Log method invocation to AMSI Notifications (can throw PSSecurityException) | |
| // - Invoke method | |
| string targetName = methodInfo.ReflectedType?.FullName ?? string.Empty; | |
| expr = Expression.Block( | |
| Expression.Call( | |
| CachedReflectionInfo.MemberInvocationLoggingOps_LogMemberInvocation, | |
| Expression.Constant(targetName), | |
| Expression.Constant(name), | |
| Expression.NewArrayInit( | |
| typeof(object), | |
| args.Select(static e => e.Expression.Cast(typeof(object))))), | |
| expr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mklement0 The code you referenced does only one thing - call LogMemberInvocation method. This is the code that should have been excluded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, of course, thanks. I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why I put the change here is in case the method is called in multiple places in the future. I can certainly change it so the expression tree doesn't call it but I'm not sure if there is any actual overhead to care about with just calling a method that is ultimately a no-op. Will defer to the pwsh team to see what they decide here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, Jordan. I suppose a pragmatic way to handle this would be to to use #if UNIX && !DEBUG around both the currently known only call site as well as inside the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the more I think about this the more I think it would be better to make a new method in the compiler to conditionally add this expression. The JIT should optimize the call, but it won't optimize the array creation for the argument and extra boxing involved. Should be very minimal, but might as well get rid of it if we can.
Something like this:
private Expression MaybeAddMemberInvocationLogging(Expression expr, Expression[] args, ...otherNeededState)
{
#if UNIX && !DEBUG
return expr;
#else
return Expression.Block(
Expression.Call(
CachedReflectionInfo.MemberInvocationLoggingOps_LogMemberInvocation,
Expression.Constant(targetName),
Expression.Constant(name),
Expression.NewArrayInit(
typeof(object),
args.Select(static e => e.Expression.Cast(typeof(object))))),
expr);
#endif
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to follow this pattern.
SeeminglyScience
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to ping me on discord when the changes are made (or you've otherwise responded) and I'll get this merged. Thanks Jordan! ❤️
Turn off the AMSI member invocation logging for non-Windows release builds. This is slightly more efficient as there's no extra work to create the AMSI string.
SeeminglyScience
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thank you Jordan!
|
📣 Hey @jborean93, how did we do? We would love to hear your feedback with the link below! 🗣️ 🔗 https://aka.ms/PSRepoFeedback |
| typeof(object), | ||
| args.Select(static e => e.Expression.Cast(typeof(object))))), | ||
| expr); | ||
| MaybeAddMemberInvocationLogging(expr, targetName, name, args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be missing something, but it looks like the return value from this method call is never used; shouldn't that be expr = MaybeAddMemberInvocationLogging(expr, targetName, name, args);?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good pickup, sorry for missing that. @SeeminglyScience will be submitting a new PR to fix that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That says there are no tests.
And many would say thank you for such an omission 😄
PR Summary
Turn off the AMSI member invocation logging for non-Windows release builds. This is slightly more efficient as there's no extra work to create the AMSI string.
Fixes: #21492
PR Context
#21492 (comment)
It doesn't make sense for this feature to be on for a proper release build as writing to stdout will affect anything calling PowerShell. The env var name also indicates this is an internal optional and not a publicly supported feature so shouldn't be under any API contract.
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.- [ ] Issue filed:
(which runs in a different PS Host).