8000 Add `LoadAssemblyFromNativeMemory` function to load assemblies in memory from a native PowerShell host by awakecoding · Pull Request #14652 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

Add LoadAssemblyFromNativeMemory function to load assemblies in memory from a native PowerShell host #14652

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

Merged
merged 10 commits into from
Apr 28, 2021

Conversation

awakecoding
Copy link
Contributor

PR Summary

Add a new NativeHost class in Microsoft.PowerShell.ConsoleHost.dll with a LoadAssemblyData function callable from unmanaged code to load assemblies in memory. This fixes #14641 and dotnet/runtime#46652 for PowerShell. This modification has been confirmed to work in a proof-of-concept PowerShell native host.

PR Context

Long story short, the ultimate goal is to make it possible for any native language such as C/C++/Rust to find, load, and host PowerShell 7 entirely at runtime, using nothing but the existing PowerShell installation. This is possible by loading the hostfxr library found alongside PowerShell dynamically to then load the PowerShell runtime config. The last step is to load bindings to make the bridge between unmanaged to managed code possible, and wrap functions from Microsoft.PowerShell.SDK.

Loading the precompiled bindings is currently only possible from a file with the hostfxr APIs, which is why I originally approached the .NET runtime team first (dotnet/runtime#46652) but eventually exhausted all options. I then opened an issue with PowerShell (#14641) to see if this could be fixed outside of the .NET runtime, after all, we only need one helper function exported by one of the PowerShell assemblies.

PR Checklist

This is my first contribution to the core PowerShell project, and I perfectly understand that questions are going to be asked: why is this necessary, why a new class, why this function, etc. Please just work with me to find the best place to put this single helper function meant to facilitate loading PowerShell inside a native host. I deliberately used just one function and didn't try exporting more functions because it is a small change that will require no further changes inside PowerShell. I placed it inside its own class inside Microsoft.PowerShell.ConsoleHost.dll, but I'm open to suggestions.

In other words: if I can load my assembly in memory, then I can load all the bindings I need for the native language of my choosing. It also becomes a lot easier to choose exactly how those bindings will handle marshaling, and which functions they expose to unmanaged callers. This is a one-time thing that unlocks extensibility forever because you can load anything into the assembly context to fit your needs.

The helper function always loads the assembly into the default AssemblyLoadContext. My thinking is that if I ever need to deal with non-default ALCs, I can still add more functions callable from native code to deal with non-default ALCs, even if those helper functions are first loaded in the default ALCs. However, if there are easier ways to extend the NativeHost class, I'm open to suggestions, but I'd rather keep changes as small as possible to increase the chances of this PR being accepted.

@ghost
Copy link
ghost commented Jan 22, 2021

CLA assistant check
All CLA requirements met.

@awakecoding
Copy link
Contributor Author

@SteveL-MSFT @joeyaiello @iSazonov this is the only thing required to make PowerShell native hosts possible from any native language using nothing but the existing PowerShell files, as seen on twitter!

Help me get this in an acceptable state and I promise you'll see a special Rust library published soon making it possible to load and host PowerShell 7 with zero link-time dependencies, and zero installation required other than PowerShell. If you have "pwsh", then you now have it usable inside your native host process as well.

@SteveL-MSFT SteveL-MSFT requested a review from rjmholt January 23, 2021 18:07
@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Jan 24, 2021
Copy link
Collaborator
@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @PaulHigin @TravisEz13 Please review a security of the new public API.

/// <summary>
/// Provides helper functions to faciliate calling managed code from a native PowerShell host.
/// </summary>
public static class NativeHost
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My post for discussion from original issue: PowerShellAssemblyLoadContext is internal :-( Utils.cs too. I suggest to start the prototype with public sealed class UnmanagedPSEntry. Alternative is to put the public class in SMA.

@TravisEz13
Copy link
Member

Security Working group reviewed, and we don't find a security issue in the public API.

@awakecoding
Copy link
Contributor Author

@TravisEz13 thanks!

Anybody knows what is causing some checks to fail? It appears unrelated to the changes in this PR:

WinRM based remoting session abrupt disconnect.Verifies that an abruptly disconnected Invoke-Command session produces a valid disconnected job needed for reconnect
Expected exactly 'Disconnected', but got Disconnecting.

@iSazonov
Copy link
Collaborator

Anybody knows what is causing some checks to fail? It appears unrelated to the changes in this PR:

I restarted CI-Windows.

@awakecoding
Copy link
Contributor Author

@anmenaga @daxian-dbw I see you've been assigned as reviewers, what would be the next step? It's a small change, but there are things that are open to debate, such as:

  • in which class to put the function, we only have one for now, but we leave space for adding more if required in the future.
  • naming of the class and, I named it 'NativeHost' but it could very well be named 'UnmanagedHost' if this is preferred
  • the LoadAssemblyData() function and how it uses the Default ALC, maybe there is a nice way to support non-default ALCs

I'm not very familiar with ALCs, but loading my bindings to the PowerShell class inside the default ALC works just fine for me. If someone can foresee a potential limitation with ALCs and knows more about them than I do, it would be work pointing it out now.

@ghost ghost added the Review - Needed The PR is being reviewed label Feb 3, 2021
@ghost
Copy link
ghost commented Feb 3, 2021

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

@awakecoding
Copy link
Contributor Author

I am waiting on one of the reviewers to tell me what the next step is :)

@TravisEz13
Copy link
Member

Somehow we need to show that the failures are not related to your change.

@ghost ghost removed the Review - Needed The PR is being reviewed label Feb 3, 2021
@ghost ghost added the Review - Needed The PR is being reviewed label Feb 11, 2021
@ghost
Copy link
ghost commented Feb 11, 2021

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

/// <summary>
/// Provides helper functions to faciliate calling managed code from a native PowerShell host.
/// </summary>
public static class NativeHost
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this to be a public API, I think a more specific name (and one we're likely not to want to use for a different purpose) would be ideal.

But also it feels like this should probably be another method on either UnmanagedPSEntry or ManagedPSEntry, since those classes exist for this exact purpose

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to rename "NativeHost" to "UnmanagedHost", "UnmanagedPSHost" or "UnmanagedPSHostHelper"? name your pick, I'll update the PR accordingly. If you feel like this should be moved to UnmanagedPSEntry I can put it in there, it just felt a bit out of place since there was pretty much a single entry point function in that class.

However, I'll go with whatever you think feels right, I don't really care how it is named and where it is placed in the end, as long as it is included somewhere :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally I'd like to find consensus with @iSazonov and @daxian-dbw on what the best course of action is. I think my own preference is to put it on UnmanagedPSEntry, but want to discuss it briefly first just so we're comfortable with the API.

Also, I'm not sure what the overhead on testing this would be, but without one I imagine there's an increased likelihood of regression

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjmholt it may be a difficult one to test automatically, because it's meant to be called from an unmanaged host. However, we could make it callable from managed code, and maybe use a test similar to whatever is currently used to load assemblies in memory?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally I'd like to find consensus with @iSazonov and @daxian-dbw on what the best course of action is. I think my own preference is to put it on UnmanagedPSEntry, but want to discuss it briefly first just so we're comfortable with the API.

Perhaps UnmanagedPSEntry is too specific that coming from Windows PowerShell. Do we really need always ConsoleHost dll in custom projects? If no it is better to place the new API in SMA.

@awakecoding
Copy link
Contributor Author

@SteveL-MSFT @rjmholt @iSazonov @daxian-dbw @PaulHigin @TravisEz13 @anmenaga I would like to get this PR unstuck from the mud. It's unclear from the previous discussion what change would make it finally clean enough for a merge. There's a lot of "this might be better, maybe we should move it here" but nothing firm from one of the 4 assigned reviewers yet?

I really do not care what the class name and function name is, I just want to know what needs to be changed to make it good for a merge, especially before 7.2 becomes final? I would hate to have to wait for 7.3 just for 5 lines of code, but I would be fine with requiring 7.2 as a baseline just for this.

Please tell me exactly what to fix, I'll do it once, and then we can move on. So, I'm asking, what changes need to be done to make the current PR acceptable, please? :)

@iSazonov
Copy link
Collaborator
iSazonov commented Mar 26, 2021

I reread all related links to refresh my memory.

The main fact is that .Net team has no plans now and near future to implement an solution for supporting native host scenarios and recommends to use custom wrap managed dll which can be called from native code.
Example of such dll https://github.com/awakecoding/pwsh-native-host/blob/master/NativeHost/Bindings.cs


Based on the fact I see two way we have.

  1. If MSFT PowerShell team does not want to spend even 1 cent, then we continue this PR given that the security team did not find any problems in this.
    Given the specifics of this API the status of this API should be "not public, not supported, only for external partner, can be removed in any time".
  2. Second option is to implement the wrap dll as shared solution.
    Considering that the project is open source, it would be more correct design to implement this wrap dll in PowerShell / NativeSDK repository so that other native host projects can benefit from this.
    Since PowerShell managed SDK is very stable the Native SDK will be stable too and shouldn't take a lot of effort to maintain.
    That we need to do:
    • wrap most of useful PowerShell public API in the dll and cover most of native host scenarios.
    • publish as nuget signed by Microsoft
    • distribute with PowerShell (reference the nuget in Microsoft.PowerShell.SDK.csproj)
      • as standalone file (a native host must ensure that this dll is loaded)
      • or with an API hook to load always or on demand (preffered I believe) from a native host

/cc @SteveL-MSFT @JamesWTruher @daxian-dbw who could weight and select a direction we will follow.

GitHub
pwsh native host experiment. Contribute to awakecoding/pwsh-native-host development by creating an account on GitHub.

@ghost ghost removed the Review - Needed The PR is being reviewed label Mar 26, 2021
@awakecoding
Copy link
Contributor Author

@iSazonov thank you for the detailed follow up. I noticed that you pointed to my larger set of bindings, and I just want to clarify that my PR is to add a single function that makes it possible to load any set of bindings not distributed with PowerShell. In other words, a single native function export to load your own bindings, and not the whole set of bindings, making it a "bring your own bindings" type of solution, but no additional file or installation required to load the bindings.

Your first suggestion is still what I'd like: just include this single function somewhere into PowerShell and we can take it from there without further modifications to PowerShell. My very first use case for this is a Rust crate that will enable loading and using PowerShell entirely at runtime without redistributing PowerShell, and without link time dependencies. This is what I could validate with my proof-of-concept in C, and since then I've been working very hard to find a solution to the last problem of requiring at last one additional file on disk distributed separately.

The second suggestion of making a wrapper DLL as a shared solution would unfortunately greatly increase the scope of the project and take it in a completely new direction. Because it's very hard to know how foreign language XYZ would deal with the bindings, I wouldn't even consider trying to do it that way. This being said, nothing prevents implementing the first suggestion (single native function export + bring your own bindings) and then working on a useful set of bindings that most people would be fine with. However, that would be a totally different story and out-of-scope for the current work.

Since the security team did not find any objections to the current PR, maybe it's already good enough for a possible merge, unless someone wants to request modifications? At this point any change request would likely be non-functional in nature (name and location in the code), but there seems to be no objection on the actual code change made here.

So, who can give their seal of approval on this one? @SteveL-MSFT ? :)

@iSazonov
Copy link
Collaborator

Since the security team did not find any objections to the current PR, maybe it's already good enough for a possible merge, unless someone wants to request modifications?

I have reviewed the existing API and believe that it has nothing to do with the new API. The existing API comes from Windows PowerShell and .Net Framework - it can not be used with .Net Core and we could remove it at all. (Or is it used on Windows Nano?)

So I think we should put the new API in SMA dll as new standalone static class (with a comment about limited support as I mentioned above).

@awakecoding
Copy link
Contributor Author

@iSazonov thank you, the new API has nothing to do with the old one, so I do agree it has to be in its own standalone static class (which is currently the case). I'm no expert in PowerShell internals, can you tell me what SMA stands for and where it is?

In this case, all I'd need is to move the class (currently named NativeHost in NativeHost.cs) to the SMA DLL, if you could just point me to where that is :)

@awakecoding
Copy link
Contributor Author

We can still make it a int return type, but only return 0 for success and 1 for failure. So, even if we have to improve it for some reason in future to return a list of error codes, it won't break the ABI.

If this sounds fine to you, can you please update the code accordingly?

That's even better, my primary concern was ABI stability just because of the return type, if you make it an int with a known success/failure return value (without necessarily defining error codes and an enum) that would be absolutely perfect!

@daxian-dbw
Copy link
Member

I believe we still need to declare an experimental feature name in this list, even though we won't really use it for anything. @SteveL-MSFT, any suggestion on the feature name?

@TravisEz13
Copy link
Member

Security working group:
there should be non-repudiation logging, which should include:

  • The user
  • the process
  • a size and hash or the assembly (or something else to identify the assembly)

Once we have code for the logging, the security working group should review the PR

This can be a separate PR

@daxian-dbw
Copy link
Member

I chose the name PSLoadAssemblyFromNativeCode for the experimental feature. @SteveL-MSFT Please let me know if you have a concern.

@daxian-dbw
Copy link
Member
daxian-dbw commented Apr 28, 2021

A new test has been failing consistently in mac CIs for this PR:

    [-] native command should be killed when pipeline is disposed 94ms
      Expected 0, but got 1.
      50:         (Get-Process 'yes' -ErrorAction Ignore).Count | Should -Be $yes
      at <ScriptBlock>, /Users/runner/work/1/s/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1: line 50

Further investigation is needed. /cc @SteveL-MSFT

@SteveL-MSFT
Copy link
Member

The name of the ExperimentalFeature seems fine. I checked out this branch and it doesn't have that test case that's failing which is quite strange as CI is running it? I rebased to pick up the code changes related to that test change.

@daxian-dbw daxian-dbw merged commit 8f8ddc3 into PowerShell:master Apr 28, 2021
@iSazonov
Copy link
Collaborator

@awakecoding Congratulations! 😄

@awakecoding
Copy link
Contributor Author

@awakecoding Congratulations! 😄

Woohoo! Now we can start looking for the next steps, I think there were mentions of many new tickets related to this one?

rkeithhill pushed a commit to rkeithhill/PowerShell that referenced this pull request May 3, 2021
@ghost
Copy link
ghost commented May 27, 2021

🎉v7.2.0-preview.6 has been released which incorporates this pull request.:tada:

Handy links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log Committee-Reviewed PS-Committee has reviewed this and made a decision WG-Security security related areas such as JEA
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Loading and hosting existing PowerShell installation dynamically inside native application
0