Let’s say we want to create a new Window Station, a new Desktop and create a process that should be associated with those objects. Sounds simple, right?
int _tmain(int argc, TCHAR* argv)
HWINSTA winsta, original;
winsta = CreateWindowStation(L"omeg-winsta", 0, MAXIMUM_ALLOWED, NULL);
printf("CreateWindowStation failed: %d\n", GetLastError());
original = GetProcessWindowStation();
// we need to switch to the new window station, because CreateDesktop uses a current one
printf("SetProcessWindowStation(new) failed: %d\n", GetLastError()
desk = CreateDesktop(L"omeg-desk", NULL, NULL, 0, MAXIMUM_ALLOWED, NULL);
printf("CreateDesktop failed: %d\n", GetLastError()
si.cb = sizeof(si);
si.lpDesktop = L"omeg-winsta\\omeg-desk";
CreateProcess(0, L"calc.exe", 0, 0, 0, 0, 0, 0, &si, &pi);
Let’s try that!
calc.exe - Application Error
The application was unable to start correctly (0xc0000142). Click OK to close the application.
So what’s happening? At first I thought that maybe that’s supposed to happen — MSDN says that the only interactive window station is WinSta0, a predefined one created with the interactive user session. But why give the ability to create new ones then? System services run in separate sessions and have separate window stations/desktops. In the past (pre-Vista) they all ran in the user’s session, but that was changed to mitigate Shatter attacks.
Anyway, services used to be able to display GUI even on their separate window stations in Vista (with the help of a helper service). Apparently that should still be possible on Windows 7 (where I run my tests). Why didn’t it work then?
I was even more confused when calc.exe appeared to work when I launched my test program from the Visual Studio debugger (but still refused to work if I launched it from a command shell). Also if there was one instance of calc.exe already running in my new desktop, test also worked when launched from command prompt, explorer or wherever. What is this, black magic?
And then it struck me. When running under debugger, I had a breakpoint set just after the CreateProcess call. That means the child process was created, but handles to the new window station and desktop were not immediately closed (because of debugger pause). If running standalone, on the other hand, handles were immediately closed. Why is that significant?
Window stations and desktops are both Windows kernel objects under the hood. And all kernel objects are reference-counted. That means the Windows Object Manager can (and will) destroy any kernel object that is no longer referenced. The example program above contains a race condition — by closing all handles to the newly-created objects we indirectly destroy them, usually before the child process can initialize properly. How to fix that? Add a call to WaitForInputIdle after CreateProcess. That way we wait for the child process to finish its initialization before closing handles. The new desktop and window station won’t be destroyed because now the child keeps their handles.
Moral of the story: don’t close your object handles until you know you really don’t need to keep them.