Search code examples
c#.netwinapiloadlibrary

Does 'System.Reflection.Assembly.LoadFrom' modify the native DLL search order?


I have the following situation:

base\C_sharp.exe -> base\sub\CPPCLI.dll -> base\sub\CPPnative.dll

where I have a C# executable, loading a C++/CLI (.NET) dll via System.Reflection.Assembly.LoadFrom that is statically linked to CPPnative.dll.

Now, the Standard Dll Search Order on Windows is:

  1. The directory from which the application loaded.
  2. The system directory. Use the GetSystemDirectory function to get the path of this directory.
  3. The 16-bit system directory. (...)
  4. The Windows directory. (...)
  5. The current directory.
  6. The directories that are listed in the PATH environment variable.

Obviously, if I call

System.Reflection.Assembly.LoadFrom("D:\....\base\sub\cppcli.dll");

the .NET assembly will be found, but what then happens is that the transitive load of the cppnative.dll succeeds!

1618:3088 @ 1557218328 - LdrLoadDll - ENTER: DLL name: D:\....\base\sub\cppcli.dll DLL path: D:\....\base\sub\;;C:\Windows\system32;...
...
1618:3088 @ 1557218328 - LdrpHandleOneOldFormatImportDescriptor - INFO: DLL "D:\....\base\sub\cppcli.dll" imports "cppnative.dll"
...
1618:3088 @ 1557218328 - LdrpLoadImportModule - ENTER: DLL name: cppnative.dll DLL path: D:\....\base\sub;;C:\Windows\system32;

TL;DR: How does the native NT loader suddenly start looking in the sub directory?

Normal LoadLibrary behaviour would suggest that the executable directory is searched first.


Solution

  • There is the possibility that the .NET framework is using LOAD_WITH_ALTERED_SEARCH_PATH internally when eventually doing the LoadLibrary call to the C++/CLI assembly.

    At least, I can clearly see in my call stacks that the framework is using LoadLibraryEx internally to load the mixed mode cppcli.dll into the process.

    First:

    >   cppnative1.dll!DllMain(HINSTANCE__ * hModule, unsigned long ul_reason_for_call, void * lpReserved) Line 13  C++ Symbols loaded.
        cppnative1.dll!dllmain_dispatch(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 199   C++ Symbols loaded.
        ntdll.dll!LdrpRunInitializeRoutines()  Unknown Symbols loaded.
        ntdll.dll!LdrpLoadDll()    Unknown Symbols loaded.
        ntdll.dll!LdrLoadDll() Unknown Symbols loaded.
        KernelBase.dll!LoadLibraryExW()    Unknown Symbols loaded.
        [Managed to Native Transition]      Annotated Frame
        mscorlib.dll!System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, System.Reflection.RuntimeAssembly reqAssembly, ref System.Threading.StackCrawlMark stackMark, System.IntPtr pPrivHostBinder, bool throwOnFileNotFound, bool forIntrospection, bool suppressSecurityChecks)    Unknown No symbols loaded.
    

    Then:

    >   cppcli.dll!DllMain(HINSTANCE__ * hModule, unsigned long ul_reason_for_call, void * lpReserved) Line 17  C++ Symbols loaded.
        cppcli.dll!dllmain_dispatch(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 199   C++ Symbols loaded.
        mscoreei.dll!000007fef3ec0e64() Unknown No symbols loaded.
        mscoree.dll!000007fef5fd3281()  Unknown No symbols loaded.
        mscoree.dll!000007fef5fd32cf()  Unknown No symbols loaded.
        ntdll.dll!LdrpRunInitializeRoutines()  Unknown Symbols loaded.
        ntdll.dll!LdrpLoadDll()    Unknown Symbols loaded.
        ntdll.dll!LdrLoadDll() Unknown Symbols loaded.
        KernelBase.dll!LoadLibraryExW()    Unknown Symbols loaded.
        [Managed to Native Transition]      Annotated Frame
        mscorlib.dll!System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, System.Reflection.RuntimeAssembly reqAssembly, ref System.Threading.StackCrawlMark stackMark, System.IntPtr pPrivHostBinder, bool throwOnFileNotFound, bool forIntrospection, bool suppressSecurityChecks)    Unknown No symbols loaded.
    

    I can also observe, that the executable path is no longer searched when resolving the transitive (native) module dependencies during this LoadLibraryEx call.

    That is, all transitive (native) dependencies MUST reside in the directory of the LoadFrom call, not in the application directory.

    (Incidentally the CWD "." is searched, so that can add to the confusion as well.)