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:
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.