The hidden danger of forgetting to specify %SystemRoot% in a custom environment block

When spawning a process using CreateProcess and friends, the child process usually inherits the environment (i.e. all environment variables) of the spawning process. Of course, this behavior can be overridden by creating a custom environment block and passing it to the lpEnvironment parameter of CreateProcess.

While the MSDN documentation on CreateProcess does contain a remark saying that current directory information (=C: and friends) should be included in such a custom environment block, it does not mention the importance of SystemRoot.

The SystemRoot environment variable usually contains the path c:\windows – the path that is also accessible using the GetWindowsDirectory function. This environment variable, as it turns out, is not only handy for scripting purposes – it is, in fact, essential for the proper operation of many libraries.

For very simple programs, forgetting to include SystemRoot in a custom environment block usually goes unnoticed – even an empty environment block works just fine. In case of more complex applications, however, the omission of this variable can quickly lead to errors – on Vista, the most common error that can be tracked back to a missing SystemRoot variable is SXS failing to find/load basic system libraries.

Now that we have Windows 7, SystemRoot seems to have become even more important: Now it is not only SXS that requires SystemRoot to be specified properly, but also CryptoAPI.

In my particular case, I was experiencing a 0x80090006 (“Invalid Signature”, NTE_BAD_SIGNATURE) error whenever the child process attempted to call CoGetObject to retrieve a pointer to a DCOM object. While this error occured on Windows 7, the same code worked fine on Windows Vista and XP.

Given this more than general error message, it seemed anything but clear to me what the problem was, so I attached a debugger to the child process (using gflags/Image File Execution Options). Once I did that, I got the following messages in my debug output output:

CryptAcquireContext:  CheckSignatureInFile failed at cryptapi.c line 5198
CryptAcquireContext:  Failed to read registry signature value at cryptapi.c line 873

I set a breakpoint on CryptAcquireContextW and looked at the stack trace:

    
    0:000k
    ChildEBP RetAddr  
    0008f8a4 75760a4f ole32!CRandomNumberGenerator::Initialize+0x2e
    0008f8b0 75760769 ole32!CRandomNumberGenerator::GenerateRandomNumber+0xd
    0008f8e8 757609cf ole32!CStdMarshal::AddIPIDEntry+0x48
    0008f93c 75766aae ole32!CStdMarshal::MarshalServerIPID+0x5a
    0008f994 75767519 ole32!CStdMarshal::MarshalObjRef+0xb9
    0008f9c8 7576778e ole32!MarshalInternalObjRef+0x8c
    0008fa4c 757676ba ole32!CRemoteUnknown::CRemoteUnknown+0x3b
    0008fa8c 7576754a ole32!CComApartment::InitRemoting+0x19c
    0008fa98 7586d83e ole32!CComApartment::StartServer+0x13
    0008faa8 757652b3 ole32!InitChannelIfNecessary+0x1e
    0008fb20 757fc046 ole32!CoUnmarshalInterface+0x38
    0008fb34 757fd3d5 ole32!CObjrefMoniker::Load+0x26
    0008fb70 7573cb7f ole32!CObjrefMonikerFactory::ParseDisplayName+0x16f
    0008fbbc 7573caae ole32!FindClassMoniker+0x8b
    0008fbf4 75789dc7 ole32!MkParseDisplayName+0xbb
    0008fc3c 6954ce84 ole32!CoGetObject+0x82
    ...
    

Quite obviously, COM, trying to unmarshal an interface, needed a random number and attempted to use CryptoAPI for this purpose. Looking at the paramters of CryptAcquireContext, I saw that the Microsoft Strong Cryptographic Provider was attempted to be loaded – one of the standard Windows CSPs – so everything seemed normal.

Guided by the message Failed to read registry signature, I switched to Process Monitor to see which registry key was being queried: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Cryptography\Defaults\Provider\Microsoft Strong Cryptographic Provider.

Taking a look at this key in regedit, it did not take long before spotting SystemRoot as the culprit:

Looking at file system activity in Process Monitor proved this:

Process Monitor

Interestingly, on Windows Vista, all Image Path values in the CSP registry keys do not use SystemRoot – they just contain the file name and rely on the library path in order to locate the libraries at runtime. This explains why my code worked fine on Vista.

(While making this change, the developer seemed to forget to change the value’s type from REG_SZ to REG_EXPAND_SZ though :) )

Bottom Line 1: Always, always include SystemRoot when passing a custom environment block to CreateProcess.

Bottom Line 2: The case also shows how a seemingly trivial change in Windows (using an absolute path rather then just a file name in the CSP registry key) can lead to an application incompatibility.

« Back to home