-
Notifications
You must be signed in to change notification settings - Fork 58
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
Use high-level library load API #217
base: main
Are you sure you want to change the base?
Use high-level library load API #217
Conversation
b369101
to
0708d44
Compare
0708d44
to
d765df3
Compare
Interesting to see that the CI fails, will mark as draft and look into it, @jasongin thanks for the pipeline run.
|
Thanks for the PR, but I have a couple concerns:
|
@jasongin As for your first concern, this is still possible when using the "better" |
if (Current != null) | ||
{ | ||
throw new InvalidOperationException( | ||
"Only one Node.js platform instance per process is allowed."); | ||
} | ||
|
||
nint libnodeHandle = NativeLibrary.Load(libnodePath); | ||
#if NET7_0_OR_GREATER |
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.
Can this be !NETFRAMEWORK
? The 3-parameter overload of NativeLibrary.Load()
is present on .NET 6.
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.
This code was removed in the newest revision.
|
||
nint libnodeHandle = | ||
entryAssembly != null | ||
? NativeLibrary.Load(libnode, entryAssembly, null) |
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'm a little concerned about the lack of functional parity on .NET Framework 4.x. Would it be feasible to add code in this repo's NativeLibrary.cs
to at least replicate the logic for loading from the same directory as the assembly?
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've implemented a partially-featured replica of the original method, but I can't test it because I don't have access to a Windows machine.
if (Current != null) | ||
{ | ||
throw new InvalidOperationException( | ||
"Only one Node.js platform instance per process is allowed."); | ||
} | ||
|
||
nint libnodeHandle = NativeLibrary.Load(libnodePath); | ||
#if NET7_0_OR_GREATER | ||
var entryAssembly = System.Reflection.Assembly.GetEntryAssembly(); |
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.
Did you consider using DllImportSearchPath.AssemblyDirectory
instead? I think technically that would use the current assembly rather than the entry assembly, but that might actually be better.
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.
Even using AssemblyDirectory, you still need to pass an assembly. I'd rather use the system default but I can change the assembly to get the current assembly instead of the entry.
I see the build issues are fixed, thanks. @samardzicneo, I'm curious what is your motivation for submitting this change? I see you're aware in #218 of the current unfinished state of libnode support. |
@jasongin Thanks for the review, I'll have to look at your comments tomorrow. |
# Conflicts: # src/NodeApi/Runtime/NodejsPlatform.cs
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.
Sorry this got more complicated than you probably anticipated, but we have to consider security.
|
||
nint libnodeHandle = | ||
entryAssembly != null | ||
? NativeLibrary.Load(libnode, entryAssembly!, null) |
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 think we need to specify DllImportSearchPath.SafeDirectories
here, because the default search behavior is considered unsafe.
If your goal was to load libnode
from the same directory as the application, I think that will work, though the documentation for SafeDirectories
is not entirely clear.
In that case the back-compat code in NativeLibrary
should search the application directory, which is not necessarily the same as the assembly directory.
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.
SafeDirectories won't use the assembly directory, so I'm ORing SafeDirectories
and AssemblyDirectory
now.
src/NodeApi/Runtime/NativeLibrary.cs
Outdated
string?[] tryDirectories = | ||
[ | ||
Path.GetDirectoryName(assembly.Location), | ||
Environment.CurrentDirectory, |
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.
Do not search the current directory; that is very unsafe.
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.
Removed.
src/NodeApi/Runtime/NativeLibrary.cs
Outdated
/// <returns>Library path if found, otherwise false.</returns> | ||
private static string? FindLibrary(string libraryName, Assembly assembly) | ||
{ | ||
if (File.Exists(libraryName)) |
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.
If libraryName
is not an absolute path this searches the current directory (or a relative path from it), which is unsafe.
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.
Added a check to ensure it's an absolute path.
@jasongin Thanks for the review. I wasn't aware that loading from cwd is unsafe. Side note, this is my first time contributing to open source so I came into it fully expecting a load of comments. Especially if it concerns security, I'd rather have more than less comments. |
? NativeLibrary.Load( | ||
libnode, | ||
entryAssembly!, | ||
DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories |
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.
AssemblyDirectory
is listed as one of the unsafe options, so this may get flagged by a security scan. (I admit I do not understand why that location should be considered unsafe.)
I think it would be better to allow the application to optionally specify the search path:
public NodejsPlatform(string libnode, string[]? args = null, DllImportSearchPath? searchPath = null)
Then the application developer can decide whether some search paths are unsafe or not, depending on the application threat model.
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 can do that, but after some research it looks like CA5393 might have some problems in it: It looks like "SafeDirectories" includes the application directory, but then "ApplicationDirectory" is considered unsafe. Maybe this rule hasn't been updated in a while, or maybe it was made with only Windows in mind? On UNIX it's quite common to load libraries from the assembly directory and if it really was insecure, wouldn't that mean that .NETs own approach of putting managed dependency DLLs next to the assembly is also insecure?
If you want I can still change this but to me, the rule looks a little weird.
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 learned today that the DllImportSearchPath
is entirely ignored on non-Windows platforms, and that is not documented: dotnet/dotnet-api-docs#3205
In that case should we use your FindLibrary()
code on those platforms? Or if not, will it even search in the right place(s)? It probably requires testing.
Also I found that CA5393 is now disabled by default since it doesn't apply cross-platform, so maybe we don't need to worry about that. (I recalled dealing with this warning in other projects several years ago.)
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.
At least for the near future, loading libnode
from a system directory is rarely going to be the right choice. The Node.js project does not publish official libnode shared-library binaries, and I believe most applications that do use it now are static-linked. Also, applications are likely to be sensitive to the major version of node. So any apps that do use a libnode shared-library will distribute it with the application.
For this project, our plan is to publish libnode binaries via NuGet packages. Then when a .NET application that consumes the package is published, the libnode binary for the target runtime-identifier would get copied to the publish output directory. That would be the assembly directory, or at least relative to it. So I'm thinking the best default should probably be just DllImportSearchPath.AssemblyDirectory
, and we can let the application override that with an optional parameter as I suggested above.
We are working on setting up that infrastructure for building libnode, packaging multiple platforms in nuget packages, and publishing them. When that is further along, it might be necessary to make adjustments to the loading code here anyway.
$"{libraryName}.{libraryExtension}", | ||
$"lib{libraryName}.{libraryExtension}" | ||
]; | ||
} |
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.
This else block is not needed. This method is only used with .NET Framework 4.x, which only runs on Windows, so there is no need to consider other operating systems.
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 was thinking about Mono here - not sure if it's a target you might want to support though.
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.
We're not attempting to support Mono. There are several places in code already that assume anything within #if NETFRAMEWORK
is Windows-only.
.NET provides multiple APIs for loading libraries. One of them is
NativeLibrary.Load(string)
, which imports the library directly from the specified path. However, this requires the caller to search for the path and specify the proper extension for the OS.NativeLibrary.Load(string,Assembly,DllImportSearchPath)
searches for the library in the usual library paths, like/lib
, and adds extensions automatically.