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:000> k 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:
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.
Oh, thanks. We can probably find creative ways to use it.
Thanks for finding that out. It helped to resolve a Python bug report, see the thread at http://mail.python.org/pipermail/python-dev/2010-November/105866.html
The thing I find most shocking about that is that no one on the Windows dev team noticed they hadn’t changed the reg key type to REG_EXPAND_SZ lol I’ve never actually tried using an environmental variable in a standard REG_SZ registry value as I just kind of assumed it would screw up whatever you were trying to do with that value…