Windows 2008 RenderFarm Service: CreateProcessAsUser "Session 0 Isolation" and OpenGL
- by holtavolt
Hello,
I have a legacy Windows server service and (spawned) application that works fine in XP-64 and W2K3, but fails on W2K8. I believe it is because of the new "Session 0 isolation " feature.
(Note: As a StackOverflow newbie I'm being limited to one link in this post, so you'll need to scroll to bottom to lookup the links for '' items)*
Consequently, I'm looking for code samples/security settings mojo that let you create a new process from a windows service for Windows 2008 Server such that I can restore (and possibly surpass) the previous behavior. I need a solution that:
Creates the new process in a non-zero session to get around session-0 isolation restrictions (no access to graphics hardware from session 0) - the official MS line on this is:
Because Session 0 is no longer a user
session, services that are running in
Session 0 do not have access to the
video driver. This means that any
attempt that a service makes to render
graphics fails. Querying the display
resolution and color depth in Session
0 reports the correct results for the
system up to a maximum of 1920x1200 at
32 bits per pixel.
The new process gets a windows station/desktop (e.g. winsta0/default) that can be used to create windows DCs. I've found a solution (that launches OK in an interactive session) for this here: *(Starting an Interactive Client Process in C++ - 2)
The windows DC, when used as the basis for an *(OpenGL DescribePixelFormat enumeration - 3), is able to find and use the hardware-accelerated format (on a system appropriately equipped with OpenGL hardware.) Note that our current solution works OK on XP-64 and W2K3, except if a terminal services session is running (VNC works fine.) A solution that also allowed the process to work (i.e. run with OpenGL hardware acceleration even when a terminal services session is open) would be fanastic, although not required.
I'm stuck at item #1 currently, and although there are some similar postings that discuss this (like *(this -4), and *(this - 5) - they are not suitable solutions, as there is no guarantee of a user session logged in already to "take" a session id from, nor am I running from a LocalSystem account (I'm running from a domain account for the service, for which I can adjust the privileges of, within reason, although I'd prefer to not have to escalate priorities to include SeTcbPrivileges.)
For instance - here's a stub that I think should work, but always returns an error 1314 on the SetTokenInformation call (even though the AdjustTokenPrivileges returned no errors) I've used some alternate strategies involving "LogonUser" as well (instead of opening the existing process token), but I can't seem to swap out the session id.
I'm also dubious about using the WTSActiveConsoleSessionId in all cases (for instance, if no interactive user is logged in) - although a quick test of the service running with no sessions logged in seemed to return a reasonable session value (1).
I’ve removed error handling for ease of reading (still a bit messy - apologies)
//Also tried using LogonUser(..) here
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY
| TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_SESSIONID
| TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY
| TOKEN_DUPLICATE, &hToken)
GetTokenInformation( hToken, TokenSessionId, &logonSessionId, sizeof(DWORD), &dwTokenLength )
DWORD consoleSessionId = WTSGetActiveConsoleSessionId();
/* Can't use this - requires very elevated privileges (LOCAL only, SeTcbPrivileges as well)
if( !WTSQueryUserToken(consoleSessionId, &hToken))
...
*/
DuplicateTokenEx(hToken, (TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_SESSIONID | TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE), NULL, SecurityIdentification, TokenPrimary, &hDupToken))
// Look up the LUID for the TCB Name privilege.
LookupPrivilegeValue(NULL, SE_TCB_NAME, &tp.Privileges[0].Luid))
// Enable the TCB Name privilege in the token.
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hDupToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, 0))
{
DisplayError("AdjustTokenPrivileges");
...
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
DEBUG( "Token does not have the necessary privilege.\n");
} else {
DEBUG( "No error reported from AdjustTokenPrivileges!\n");
} // Never errors here
DEBUG(LM_INFO, "Attempting setting of sessionId to: %d\n", consoleSessionId );
if (!SetTokenInformation(hDupToken, TokenSessionId, &consoleSessionId, sizeof(DWORD)))
*** ALWAYS FAILS WITH 1314 HERE ***
All the debug output looks fine up until the SetTokenInformation call - I see session 0 is my current process session, and in my case, it's trying to set session 1 (the result of the WTSGetActiveConsoleSessionId). (Note that I'm logged into the W2K8 box via VNC, not RDC)
So - a the questions:
Is this approach valid, or are all service-initiated processes restricted to session 0 intentionally?
Is there a better approach (short of "Launch on logon" and auto-logon for the servers?)
Is there something wrong with this code, or a different way to create a process token where I can swap out the session id to indicate I want to spawn the process in a new session? I did try using LogonUser instead of OpenProcessToken, but that didn't work either. (I don't care if all spawned processes share the same non-zero session or not at this point.)
Any help much appreciated - thanks!
(You need to replace the 'zttp' with 'http' - StackOverflow restriction on one link in my newbie post)
2: http://msdn.microsoft.com/en-us/library/aa379608(VS.85).aspx
3: http://www.opengl.org/resources/faq/technical/mswindows.htm
4: http://stackoverflow.com/questions/2237696/creating-a-process-in-a-non-zero-session-from-a-service-in-windows-2008-server
5: http://stackoverflow.com/questions/1602996/how-can-i-lauch-a-process-which-has-a-ui-from-windows-service