dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.25k stars 4.73k forks source link

Strict assembly loading policy #34562

Open zvrba opened 4 years ago

zvrba commented 4 years ago

Hi,

my project consists of a number of strongly-named assemblies. The documentation on how CoreCLR handles dependencies implies that the loading policy is rather lax and will load the required assembly version OR a higher version. How can this default be overridden, i.e., I'd like to have a strict loading policy, at least for "my" assemblies (i.e., assemblies that are part of my application, not NuGet dependencies).

ghost commented 4 years ago

Tagging @vitek-karas as an area owner

vitek-karas commented 4 years ago

There's no simple way to do this.

One possible solution would be to load just the entry point assembly and create a new AssemblyLoadContext and then load everything else into it - with that you get to control the AssemblyLoadContext.Load which is called first when resolving assemblies - in it you can hardcode a specific version for each assembly and prohibit loading anything else. But this comes with non-trivial complexity, specially in resolving assembly paths.

If it would be OK to just fail if a higher version gets loaded, you can subscribe to AppDomain.AssemblyLoad event and verify the loaded version.

All that said, .NET Core by default doesn't just go and search for assemblies all over the place. It's very strict in where it looks for the assemblies. Basically just the application folder. So if you're worried about getting assemblies loaded from other places, that should not happen.

You could also make the application self-contained in which case it's even more restrictive in where it loads assemblies from.

In any case, can I ask what is the scenario where you need this?

zvrba commented 4 years ago

If it would be OK to just fail if a higher version gets loaded, you can subscribe to AppDomain.AssemblyLoad event and verify the loaded version.

Just failing is fine, that is actually the intention. Is there a way to find out which version of the assembly was referenced during build/link-time so that it can be compared with the loaded version?

In any case, can I ask what is the scenario where you need this?

An extra insurance layer against human operator error. The application is comprised of finely-grained assemblies, about 20, which means that hotfixes can be very targeted, i.e., a single AnyCPU DLL can be deployed to fix a bug. Multiple versions of the application are installed simultaneously in different folders and dropping a v2 DLL into a v1 app folder could lead to data corruption because v1 and v2 DLLs are incompatible but not incompatible enough, i.e., v2 DLL might run for a while in a v1 app, make some (incorrect) data changes to the database and then eventually crash. Therefore the application should fail as soon as it detects that an assembly of version other than major.X.Y.Z referenced at build-time was loaded (i.e., only major version number is relevant in this scheme).

joshudson commented 4 years ago

I found it expedient to use a patch manager to solve this problem. The patch manager would track down the installs and update only the targeted ones. You might find the same.

vitek-karas commented 4 years ago

Just failing is fine, that is actually the intention. Is there a way to find out which version of the assembly was referenced during build/link-time so that it can be compared with the loaded version?

Technically this might be written in the .deps.json file... please verify that it does in your case (writing that file is complex and it's hard to say if this works in all cases). Reading it can be done via Microsoft.Extensions.DependencyModel, the RuntimeFile.AssemblyVersion should be that value. I've never tried this combination though - so it's probably worth a small prototype first.

Another solution might be to use the ResolveEventArgs.RequestingAssembly - you can ask that assembly via reflection for its assembly references https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getreferencedassemblies?view=netcore-3.1. That should basically be the version the assembly was built against - and you can compare that to the loaded version.