Search code examples
windowswinapidllsetdlldirectory

SetDllDirectory does not cascade, so dependency DLLs cannot be loaded


I execute an exe from a directory, say, "C:/test"
The DLLs are in the directory "C:/test/dlls" so, in this exe, I call:

SetDllDirectory("C:/test/dlls");

Then I call

lib1 = LoadLibrary("lib1.dll)

and

ptrType pr = (ptrType) ::GetProcAddress(lib1, "test")

lib1.dll needs other DLLs that are in the directory "C:/test/dlls", but when I execute pr(...) got from GetProcAddress, I get an error:

"The program can't start because lib2.dll is missing from your computer. Try reinstalling the program to fix this problem."

If I move lib2.dll to "C:/test", it is found. That means that SetDllDirectory() is valid only for the loading of the first DLL.

Does anyone know why and how to fix it?


Solution

  • This should work. SetDllDirectory does "cascade" and thus will affect how dependent DLLs are loaded. Basically, SetDllDirectory allows you to modify the process's default search order for DLLs, and whatever changes are made to this affect the entire process, including any DLLs that may be loaded into that process. This search order is also used by Windows when it implicitly loads dependencies for child processes.

    However, there is a fatal flaw in SetDllDirectory, as called out in the documentation:

    Each time the SetDllDirectory function is called, it replaces the directory specified in the previous SetDllDirectory call.

    If some other code in your process is calling SetDllDirectory, then it is undoing your first call. If it is code in lib1.dll that does this before it attempts to load lib2.dll, then the attempt to load lib2.dll will fail.

    Now, granted, this is bad behavior on the part of lib1.dll. DLLs are guests in the application process and therefore should not go changing the carpet. But not all code out there is well-behaved, which makes this a fragile strategy.

    Another way this could go wrong would be if lib1.dll loads lib2.dll by calling LoadLibraryEx and passing one of the many flags that override the default search order, like LOAD_LIBRARY_SEARCH_APPLICATION_DIR.

    The best solution is to put all of the DLLs that you depend on in the application directory. On Windows, the application's directory is the application bundle, so this is the best place to put them. Users should never be digging through this directory, so "cluttering it up" is not a concern. It solves all sorts of problems, including the dependency-injection attack where someone puts a DLL of the same name in the current directory. (You can prevent this type of attack by calling SetDllDirectory and passing an empty string to remove the current directory from the search order, but now we're back to problem #1. If you have a guest in your house that's modifying the DLL search order, this isn't a reliable fix for the security issue. If you put the DLLs in the application directory, they will be found first before the current directory is ever searched, closing the attack vector before it can ever be exploited.)

    If you have a really good reason for putting the DLLs in a different directory, then you have a limited number of options.

    You can't use AddDllDirectory as the above-quoted documentation goes on to advise, because you don't control the code in lib1.dll and therefore can't modify it to call LoadLibraryEx with the LOAD_LIBRARY_SEARCH_USER_DIRS flag.

    Loading lib1.dll by specifying the full path won't work, because "If a DLL has dependencies, the system searches for the dependent DLLs as if they were loaded with just their module names. This is true even if the first DLL was loaded by specifying a full path."

    This leaves you with the options of either using DLL redirection, or adding information about your dependencies in your application's manifest.