Dealing with Administrator and standard user’s context
With introduction of UAC, I often get two questions for Windows Vista and later.
1) How to launch an application in the Administrative context from an application which is running in standard user’s context?
2) How to launch application in standard user’s context from an application which is running in administrative context?
The first question has been discussed in details on multiple places. The following description clearly talks about the methodologies which can be used and their consequences.
Spawning Interactive Child Processes
There is no published API for applications to spawn an arbitrary program as an adminstrator like Windows Explorer’s “Run as Administrator” context menu. The overall UAC design philosophy is that admin and installation applications need to be detected by the program launch mechanism rather than individually started as elevated or not by the calling program. This frees the caller from having to know which processes to run elevated and which to run as standard users.
CreateProcess() and CreateProcessWithLogonW do not have new flags to launch the child process as elevated. Internally, CreateProcess() checks whether the target application requires elevation by looking for a manifest, determining if it is an installer, or if it has an app compat shim. If CreateProcess() determines the target application requires elevation, it simply fails with ERROR_ELEVATION_REQUIRED(740). It will not contact the AIS to perform the elevation prompt or run the app. If CreateProcess() determines the application doesn’t require elevation, it will spawn it as a new process.
Likewise, it would seem reasonable to attempt this with the following code, but it doesn’t work:
OpenProcessToken (GetCurrentProcess, …)
GetTokenInformation (TokenLinkedToken)
CreateProcessWithTokenW(hLinkedToken, …)
To programmatically launch a child process as an elevated process, two things must occur: first, the executable of the child process needs to be identified as needing elevation, and second, the parent process needs to use ShellExecute() or ShellExecuteEx(). Applications that are designed to perform admin tasks should be marked with a signed application manifest that sets the requestedExecutionLevel to “requireAdministrator”. See page 60-71 of “Windows Vista Application Development Requirements for User Account Control Compatibility” in the reading list below. Then, when an application uses ShellExecute(), the following sequence of events occurs:
Parent Process is Standard User
The Windows Vista standard user launch path is similar to the Windows XP launch path, but includes some modifications.
1) Application calls ShellExecute() which calls CreateProcess().
2) CreateProcess() calls AppCompat, Fusion, and Installer Detection to assess if the application requires elevation. The executable is then inspected to determine its requestedExecutionLevel, which is stored in the executable's application manifest. The AppCompat database stores information for an application's application compatibility fix entries. Installer Detection detects setup executables.
3) CreateProcess() returns a Win32 error code stating ERROR_ELEVATION_REQUIRED.
4) ShellExecute() looks specifically for this new error and, upon receiving it, calls across to the Application Information service (AIS) to attempt the elevated launch.
Parent Process is an Elevated User
The Windows Vista elevated launch path is a new Windows launch path.
5) Application calls ShellExecute() which calls the AIS.
6) AIS receives the call from ShellExecute() and re-evaluates the requested execution level and Group Policy to determine if the elevation is allowed and to define the elevation user experience.
7) If the requested execution level requires elevation, the service launches the elevation prompt on the caller’s interactive desktop (based on Group Policy), using the HWND passed in from ShellExecute().
8) After the user has given consent or valid credentials, AIS will retrieve the corresponding access token associated with the appropriate user, if necessary. For example, an application requesting a requestedExecutionLevel of highestAvailable will retrieve different access tokens for a user that is only a member of the Backup Operators group than for a member of the local Administrators group.
9) AIS re-issues a CreateProcessAsUser() call, supplying the administrator access token and specifying the caller’s interactive desktop.
Spawning Non-interactive Child Processes
One cannot launch non-interactive child processes as another user with FULL access token from a parent process that is started with LUA access token. It may seem reasonable to attempt the following code to launch a process with FULL access token from a process started with LUA access token, but, it doesn't work.
LogonUser(member of local Administrators), ...
LOGON32_LOGON_BATCH, .., &hToken)
ImpersonateLoggedOnUser(&hToken)
CreateProcessWithTokenW(hToken, ...)
If one needs to launch non-interactive processes, they can do only from parent processes that are started with FULL access token such as LocalSystem service or service running under an account that is member of Administrators group. Non-interactive software installations as Administrator may use methods like SMS deployment.
For question 2, it becomes difficulty to decrease the privileges of the child process. In earlier case we have found that an application must use manifest to let operating system know the level of execution required. Remember there are three levels which can be defined in an application manifest.
Requested Execution Levels
Possible requested execution level values
Value |
Description |
Comment |
asInvoker |
The application runs with the same access token as the parent process. |
Recommended for standard user applications. Do refractoring with internal elevation points, as per the guidance provided earlier in this document. |
highestAvailable |
The application runs with the highest privileges the current user can obtain. |
Recommended for mixed-mode applications. Plan to refractor the application in a future release. |
requireAdministrator |
The application runs only for administrators and requires that the application be launched with the full access token of an administrator. |
Recommended for administrator only applications. Internal elevation points are not needed. The application is already running elevated. |
If the caller is an Administrator you will not be able to use any of the execution level to ensure that the child application launched uses standard user token.
In this case one reasonable approach would be with following code and it should work.
OpenProcessToken (GetCurrentProcess, …)
GetTokenInformation (TokenLinkedToken)
CreateProcessWithTokenW(hLinkedToken, …)
If you are running as in interactive administrative context you can use following approach.
See the following sample, this sample if launched as an administrator will launch Nodepad.exe in a standard user’ context. To paunch this sample as an administrator you would need to manifest it with requireAdministrator execution level:
#include <Windows.h>
#pragma comment (lib, "advapi32.lib")
void CreateLowProcess( HANDLE hLowPrivToken, WCHAR *wszProcessName); HANDLE GetLinkedToken(HANDLE hToken) ;
int _tmain(int argc, _TCHAR* argv[]) { BOOL fRet; HANDLE hToken = NULL; HANDLE hNewToken = NULL; TOKEN_LINKED_TOKEN tlt = {0};
// Notepad is used as an example fRet = OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY, &hToken);
if (!fRet) { return 0;; }
WCHAR wszProcessName[MAX_PATH] = L"C:\\Windows\\System32\\Notepad.exe"; HANDLE hLowPrivilage = GetLinkedToken(hToken);
if(NULL != hLowPrivilage) { CreateLowProcess(hLowPrivilage,wszProcessName); } return 0; }
HANDLE GetLinkedToken(HANDLE hToken) {
HANDLE hProcessToken = NULL; HANDLE hLinkedToken = NULL; DWORD et = (0); DWORD dwReturnLength; if (0 == GetTokenInformation(hToken,TokenElevationType, &et, sizeof(et), &dwReturnLength )) { // return of zero indicates an error return NULL; } else { // success switch (et) { case TokenElevationTypeDefault: hLinkedToken = NULL; break; case TokenElevationTypeLimited: { hLinkedToken = NULL; break; } case TokenElevationTypeFull: { TOKEN_LINKED_TOKEN linkedToken; DWORD dwLength; linkedToken.LinkedToken = 0; int iRet = GetTokenInformation(hToken, TokenLinkedToken, &linkedToken, sizeof ( linkedToken), &dwLength); if (0 == iRet) { hLinkedToken = NULL; } else { hLinkedToken = linkedToken.LinkedToken; } } } // switch (et) }
if (hProcessToken) { CloseHandle(hProcessToken); } return hLinkedToken; }
void CreateLowProcess( HANDLE hLowPrivToken, WCHAR *wszProcessName) { PROCESS_INFORMATION ProcInfo = {0}; STARTUPINFO StartupInfo = {0}; DWORD dwError = 0; DWORD dwLength = 0; BOOL fRet = 0;
fRet = CreateProcessAsUser(hLowPrivToken, NULL, wszProcessName, NULL, NULL, FALSE, 0, NULL, NULL, &StartupInfo, &ProcInfo);
if (0==fRet) { return; }
WaitForSingleObject(ProcInfo.hProcess,INFINITE);
if (ProcInfo.hProcess != NULL) { CloseHandle(ProcInfo.hProcess); }
if (ProcInfo.hThread != NULL) { CloseHandle(ProcInfo.hThread); } return ; } |
If you are not able to get TokenLinkedToken token, most likely the process is not interactive. Perhaps it has got token from following code sequence
LogonUser(member of local Administrators), ...
LOGON32_LOGON_BATCH, .., &hToken)
ImpersonateLoggedOnUser(&hToken)
CreateProcessWithTokenW(hToken, ...)
In this case you would need to use CreateRestrictedToken to create a low privileged token from the administrative token of the parent. You would need to specifically strip the privileges you don’t need.
-Prateek