Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a call to Assembly.Load(byte[]) raise the AppDomain.AssemblyResolve event?

Suppose I have a handler for AppDomain.AssemblyResolve event, and in the handler I construct a byte array and invoke the method Assembly.Load(byte[]). Can this method itself cause the AssemblyResolve event to be raised again, and cause my handler to be re-entered?

My question is not restricted only to assemblies that can be generated using C# compiler, they can contain abritrary metadata and executable code supported by the CLR.

I did some experiments and haven't find any cases when it happens. I tried to load assemblies that require additional references, tried to add CAS attributes to the loaded assembly whose decoding would require another assembly, tried to load an assembly with a module initializer (global .cctor method). In no case I observed the AssemblyResolve event to be raised from inside the Assembly.Load(byte[]) method, it only happened if some code later tried to access types, methods or attributes in the loaded assembly. But I can be missing something here.

like image 852
Vladimir Reshetnikov Avatar asked Jul 13 '14 02:07

Vladimir Reshetnikov


4 Answers

A module initializer is the only trouble-maker I can think of. A simple example of one in C++/CLI:

#include "stdafx.h"
#include <msclr\gcroot.h>

using namespace msclr;
using namespace ClassLibrary10;

class Init {
    gcroot<ClassLibrary1::Class1^> managedObject;
public:
    Init() {
        managedObject = gcnew ClassLibrary1::Class1;
    }
} Initializer;

The Init() constructor is invoked when the module is loaded through the module initializer, right after it initializes the C runtime. You are off the hook on this kind of code though in your specific case, Assembly.Load(byte[]) is not capable of loading mixed-mode assemblies.

That is not otherwise a restriction induced by module initializers. They were added in CLR v2.0 with the specific intention to a similar jobs like this, getting a language runtime to initialize itself before it starts executing any managed code. The odds that you run into such code should be very, very low. You'll know it when you see it :)

like image 120
Hans Passant Avatar answered Sep 17 '22 15:09

Hans Passant


You mentioned -

In no case I observed the AssemblyResolve event to be raised from inside the Assembly.Load(byte[]) method, it only happened if some code later tried to access types, methods or attributes in the loaded assembly. But I can be missing something here.

The points to note here -

  1. While executing code, if a type is referenced in code and the CLR detects that the assembly containing the type is not loaded, then it will load the assembly. Your observation is correct here.

  2. AssemblyResolve is an event defined in AppDomain type. So this event cannot be raised from inside the Assembly.Load(byte[])

Hence if you have already registered with AssemblyResolve event on the running appdomain and call Assembly.Load(byte[]) it loads the assembly in the current domain.

Now when any type from this loaded assembly is invoked which lets say happens to be calling another type defined in some other assembly, the AppDomain will call the AssemblyResolve event to try and load that assembly.

like image 44
Dinesh Avatar answered Sep 17 '22 15:09

Dinesh


To my knowledge Assembly.Load or loading assembly by other means does not execute any constructors that can be generated by the C# compiler (including static constructors). As result you're not going to get reentrancy to AssemblyResolve on commonly found assemblies.

As you've mentioned in the question, module initializers are not executed during the Load call. Covered in list of guarantees in CLI spec - excerpt can be found in Module Initializers by Junfeng Zhang.

B. The module’s initializer method is executed at, or sometime before, first access to any types, methods, or data defined in the module

There are related SO questions usually discussing "run code before any type constructors" like Initialize library on Assembly load. Note that .Net: Running code when assembly is loaded has an answer by Marc Gravell that states it may not be possible due to security constraints.

like image 32
Alexei Levenkov Avatar answered Sep 19 '22 15:09

Alexei Levenkov


The MSDN Documentation states:

How the AssemblyResolve Event Works:

When you register a handler for the AssemblyResolve event, the handler is invoked whenever the runtime fails to bind to an assembly by name. For example, calling the following methods from user code can cause the AssemblyResolve event to be raised:

  1. An AppDomain.Load method overload or Assembly.Load method overload whose first argument is a string that represents the display name of the assembly to load (that is, the string returned by the Assembly.FullName property).

  2. An AppDomain.Load method overload or Assembly.Load method overload whose first argument is an AssemblyName object that identifies the assembly to load.

It doesn't mention the overload receiving a byte[]. I looked up in the reference source and it seems that the Load which accepts a string overload internally calls a method named InternalLoad, which before invoking the native LoadImage calls CreateAssemblyName and its documentation states:

Creates AssemblyName. Fills assembly if AssemblyResolve event has been raised.

internal static AssemblyName CreateAssemblyName(
           String assemblyString, 
           bool forIntrospection, 
           out RuntimeAssembly   assemblyFromResolveEvent)
{
        if (assemblyString == null)
           throw new ArgumentNullException("assemblyString");
        Contract.EndContractBlock();

        if ((assemblyString.Length == 0) ||
            (assemblyString[0] == '\0'))
            throw new ArgumentException(Environment.GetResourceString("Format_StringZeroLength"));

        if (forIntrospection)
            AppDomain.CheckReflectionOnlyLoadSupported();

        AssemblyName an = new AssemblyName();

        an.Name = assemblyString;
        an.nInit(out assemblyFromResolveEvent, forIntrospection, true); // This method may internally invoke AssemblyResolve event.
        
        return an;

The byte[] overload doesn't have this, it simply calls the native nLoadImage inside QCall.dll. This may explain why ResolveEvent isn't invoked.

like image 39
Yuval Itzchakov Avatar answered Sep 16 '22 15:09

Yuval Itzchakov