2

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?

Asesh
  • 3,186
  • 2
  • 21
  • 31
Tony Pulokas
  • 465
  • 5
  • 12
  • Are you or the installer doing anything to disable the FS redirection at any point? Put a breakpoint on those functions and see. – Anders Jan 12 '18 at 02:44
  • 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 Jan 12 '18 at 03:34
  • 2
    *"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 Jan 12 '18 at 10:29

1 Answers1

2

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;
Anders
  • 97,548
  • 12
  • 110
  • 164
  • yes, exactly. strange that this behavior of `LoadLibraryEx` not documented. - https://prnt.sc/hzb9s7 – RbMm Jan 12 '18 at 05:49
  • Ah thanks. That seems to seal the deal on GetFileVersionInfo. However, I have just modified my test program to apply found = (GetFileAttributes((LPSTR)filePath) != 0xFFFFFFFF); instead of calling GetFileAttributes, and that seems to show the expected & desired behavior when the target file is in System32 but not SysWOW64. My thinking is that it in my installer program, it would be simplest to just call GetFileAttributes to determine existence of the file before calling GetFileVersionInfo. Do you see any problem with that plan? – Tony Pulokas Jan 12 '18 at 05:54
  • @TonyPulokas - call `GetFileAttributes` is good way for check are file present. no any problems here – RbMm Jan 12 '18 at 05:56
  • GetFileAttributes is probably fine, I used CreateFile because I did not want to request any type of access rights. GetFileAttributes could be tricked by a directory with the name but that is unlikely. – Anders Jan 12 '18 at 06:05
  • even if exist directory with same name - subsequent call to `GetFileVersionInfo*` will return error - in this case it will not try disable redirection (it called only in case if previous call return `STATUS_NO_SUCH_FILE` which already will be not true). and `GetFileAttributes` also not require any access on file – RbMm Jan 12 '18 at 06:43
  • @RbMm I did not test but I would imagine that if "%windir%\syswow64\foo.dll" is a directory then LoadLibraryEx will fail on it and it will retry with the real system32 directory where foo.dll might be a file and the false positive check is broken but if you actually checked then I guess you are correct. Either way, the workaround is easy, just make sure the directory bit is not set in the return value from GetFileAttributes. – Anders Jan 12 '18 at 06:49
  • no, I test - `LoadLibraryEx` not retry in this case. retry will be if file not found (`STATUS_NO_SUCH_FILE ` returned ). in case another error - not retry. – RbMm Jan 12 '18 at 06:52
  • "I used CreateFile because I did not want to request any type of access rights". FYI, `CreateFile` always requests at least `SYNCHRONIZE | FILE_READ_ATTRIBUTES` access. Opening with truly no access requires opening in asynchronous mode with `NtCreateFile` or `NtOpenFile`. – Eryk Sun Jan 12 '18 at 16:03
  • [Why is GetFileAttributes the way old-timers test file existence?](https://blogs.msdn.microsoft.com/oldnewthing/20071023-00/?p=24713) – Remy Lebeau Jan 12 '18 at 17:56