2
votes

I have an old 32-bit installer program that installs some 32-bit dependency DLLs into the Windows system folder. I have discovered that it's failing to install some of the 32-bit DLLs on a 64-bit system because the SysWOW redirection is doing something that I don't understand.

The installer program has relied on Windows API function GetFileVersionInfo to indicate if the DLL is already present with a newer version number. However, I am now seeing a case where file MSVCR100.DLL is already present in the System32 folder, but not in the SysWOW64 folder. When GetFileVersionInfo is used to test C:\Windows\System32\MSVCR100.DLL, I expect it to redirect to C:\Windows\SysWOW64\MSVCR100.DLL. It seems that if the file doesn't exist in SysWOW64, then it looks in System32 as a fallback. Thus, the installer thinks MSVCR100.DLL is already there and fails to install it.

I created a C++ Win32 Console app to test this. The entirety of the code is:

int _tmain(int argc, _TCHAR* argv[])
{
    char sysDirName[64], sysWow64DirName[64];
    char fileName[256];

    strcpy_s(fileName, argv[1]);
    GetSystemDirectory((LPSTR)sysDirName, 256);
    GetSystemWow64Directory((LPSTR)sysWow64DirName, 256);

    test_file(sysDirName, fileName);
    test_file(sysWow64DirName, fileName);

    return 0;
}

void test_file(char *dir, char* fileName)
{
    char filePath[256];
    DWORD verInfoSize, tempDWORD;
    BOOL found;
    byte buff[8192];

    PathCombine((LPSTR)filePath, (LPSTR)dir, (LPSTR)fileName);
    verInfoSize = GetFileVersionInfoSize((LPSTR)filePath, &tempDWORD);
    found = GetFileVersionInfo((LPSTR)filePath, 0, verInfoSize, buff);
    if (found)
        printf("%s   --found\n", filePath);
    else
        printf("%s   --NOT found\n", filePath);
}

I tested it on 3 different 64-bit computers, including Win 10, Win 7, and I get the same results.

If MSVCR100.DLL is in SysWOW64 but not in System32, then my test shows redirection working as expected:

>testSysFile msvcr100.dll
C:\WINDOWS\system32\msvcr100.dll   --found
C:\WINDOWS\SysWOW64\msvcr100.dll   --found

If MSVCR100.DLL is in neither System32 nor SysWOW64, then the result is expected:

>testSysFile msvcr100.dll
C:\WINDOWS\system32\msvcr100.dll   --NOT found
C:\WINDOWS\SysWOW64\msvcr100.dll   --NOT found

If MSVCR100.DLL is in System32 but not SysWOW64, then the result shows something unexpected and unhelpful:

>testSysFile msvcr100.dll
C:\WINDOWS\system32\msvcr100.dll   --found
C:\WINDOWS\SysWOW64\msvcr100.dll   --NOT found

Web searching has shown me lots of information about the SysWOW redirection, but I couldn't find any documentation or discussion of this behavior. Is this really what I should expect? My tests are also showing that if I use API function GetSystemWow64Directory I can have a file path that doesn't rely on redirection. Would it be safe to just copy DLLs and register them at that path instead?

1
Are you or the installer doing anything to disable the FS redirection at any point? Put a breakpoint on those functions and see. - Anders
Well, that's the entire code of the test program. I've heard of function Wow64DisableWow64FsRedirection, and I'm certainly not calling it. Is there anything else I could be doing to disable redirection? Sorry, I don't understand your suggestion about the breakpoint. - Tony Pulokas
"I have an old 32-bit installer program that installs some 32-bit dependency DLLs into the Windows system folder." - Surely, that is the bug you need to address. Your installer has no business to write to the system folder. Keep your dependencies in the application folder. Unless you are prepared to answer the unanswerable question: "What should your uninstaller do?" - IInspectable

1 Answers

2
votes

GetFileVersionInfo* uses LoadLibraryEx to do its work by loading the file as a data file.

For some reason KERNELBASE!BasepLoadLibraryAsDataFile inside LoadLibraryW calls ntdll!RtlWow64EnableFsRedirectionEx to disable the redirection and if the requested file is inside "%WinDir%\System32" then it tries to load the file again, this time from the "real" system32 directory.

This is clearly by design and I can't think of a way around it that is not a giant hack. I assume they do this for compatibility reasons.

You can however detect it with something like this:

bool validFile = !(GetFileAttributes(filePath) & FILE_ATTRIBUTE_DIRECTORY);
bool falsePositive = gotversioninfo && !validFile;