This is the English translation of the already published German article:
BUG: Schwarzer Patchday für alle OS XP und später 3. – MFC 8.0 (VC-2005) oder MFC 9.0 (VC-2008) die dynamisch gelinkt wurden finden die MFC Sprach-DLLs evtl. nicht mehr nach Installation der Sicherheitspatches vom 12.04.2011

Affected are:

  • All programs created with MFC 8.0 and MFC 9.0 that link dynamically to the MFC DLLs .
  • All operating systems from Windows XP and later. 32bit as 64bit
  • Al programs that do not use an application local installation (program directory, see note at the bottom of the article). So all programs that use and depend on WinSxS and VCRedist_x86.exe ( VCRedist_x64.exe).
  • All programs that are localized and use the MFC90xxx.DLL or. MFC80xxx.DLL language-DLLs and the OS system language is not set to English.

It is affected due to the security fixes offered April 12th, 2011:

For VS-2005 SP1 http://support.microsoft.com/kb/2465367 and http://support.microsoft.com/kb/2467175
For VS-2008 SP1 http://support.microsoft.com/kb/2465361 and http://support.microsoft.com/kb/2467174

Failure description:

The MFC language DLLs (satellite DLLs) are not loaded any longer. Parts of the application appear in English and not the selected language from the OS.

Background:

To prevent loading of wrong satellite DLLs (Binary Planting), an internal function in appcore.cpp named _AfxLoadLangDLL was changed. It checks if an activation context is active or not and if the DLLs should be loaded using this context. If there is an activation context active it is safe to load the satellite DLLs(MFCDEUxxx.DLL etc.) without defining a full path. If no activation context is active the path of the current application is used to load and find the satellite DLLs. The DLLs are loaded with a call to LoadLibrary.

The code used looks like this (empty lines removed):

...
TCHAR *pszFilename = ::PathFindFileName(szLangDLL);
ACTCTX_SECTION_KEYED_DATA data;
if (FindActCtxSectionString(
    FIND_ACTCTX_SECTION_KEY_RETURN_HACTCTX,
    NULL,
    ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION,
    pszFilename,
    &data) )
{
    // Load using the dll name only...
    hInstance = ::LoadLibraryEx(pszFilename, NULL, 0);
}
else
{
    // Load using the full path...
    hInstance = ::LoadLibraryEx(szLangDLL, NULL, 0);
}
...

The code looks OK.  And it is conform to the documentation of FindActCtxSectionString where the last parameter is defined as __out.

BOOL FindActCtxSectionString(
  __in   DWORD dwFlags,
  __in   const GUID *lpExtensionGuid,
  __in   ULONG ulSectionId,
  __in   LPCTSTR lpStringToFind,
  __out  PACTCTX_SECTION_KEYED_DATA ReturnedData
);

But the documentation of ACTCTX_SECTION_KEYED_DATA tells a different story:

Callers should initialize the ACTCTX_SECTION_KEYED_DATA structure as such:
“ACTCTX_SECTION_KEYED_DATA askd = { sizeof(askd) };”
which initializes all members to zero/null except the size field which is set correctly.

(BTW: In my eyes a documentation failure)

So what we see is that the code misses this: data.cbSize isn’t initialized:!:
Now we have 3 possible scenarios what can happen with a  randomly initialized data.cbSize field:

  1. data.cbSize is larger than sizeof(ACTCTX_SECTION_KEYED_DATA):
    In this case the activation context is correctly detected. The program executes normal.  With an activation context no full path is needed. The MFC90xxx.DLL will be loaded from the WinSxS (Side by Side) or found over the common search path.
  2. data.cbSize is less than  sizeof(ACTCTX_SECTION_KEYED_DATA):
    In this case FindActCtxSectionString returns with an error. The DLL is now loaded with a full path name constructed from the application directory to prevent Binary Planting. Butthe problemis that with a normal installation the searched files are all in WinSxS, and the application directory has no such data. The DLL is not loaded.
    If the application local assemblies are used and placed in sub directories they aren’t found either.
  3. A future problem.
    If an OS will use a larger ACTCTX_SECTION_KEYED_DATA and data.cbSize has a greater value than the corresponding sizeof(…):
    We have a buffer-overrun!

I always recommend to use private and application local assemblies for the CRT and MFC DLLs. And to install all this files local to the application.
Years ago I wrote an article for this scenario that was published on CodeProject and a hotfix for VS-2008 is also available :!:
Create projects easily with private MFC, ATL and CRT assemblies
Hotfix für UseMSPrivateAssemblies.h und VC-2008

What to do?

Uninstall all of the mentioned security fixes with the specified article IDs.
Runtime-2005: KB2467175, Runtime-2008: KB2467174
VS-2007 SP1: KB2465367, VS-2008 SP1: KB2465361).

Further notes:

The affected C/C++ Runtimes of Visual Studio have the following version numbers:
– VC-2005 8.0.50727.5592 (KB2467175)
– VC-2008 9.0.30729.5570 (KB2467174)

My comment to tis issue:
It was easier to live with the DLL-hell. :(

Many thanks to my Co-MVP Mike Ryan who helped me to discover this problems with the latest security patches:!:

What Do I mean with “application local”?
Some people ship the MFC files in the application directory. In such a case this DLLs are not loaded if a newer version can be found in the WinSxS directory. This is not application local for me!
So if the manifest file in the program directory still have a publicKey entry, the local files will be used  in case of the here described bug. Even if the activation context was not detected, so the local files are a kind of fallback and help prevent get around the problem.
My articles describe how to make your application really application local in removingthe publicKey tokens from the manifest files. Such programs will never fail on such broken security patches. (Just read my article at Codeproject). (Thanks for Co-MVP David Ching who asked me for a clarification)