Windows Vista for Developers – Part 4 – User Account Control

Since the release of Windows 2000, the developers working on Windows have been trying to create an environment where users can work safely and securely. Windows 2000 introduced techniques for creating restricted tokens that can effectively limit the permissions and privileges afforded to an application. Windows XP introduced further improvements but it has simply not been pervasive enough to make any real difference for the average user... until now. Whatever your initial reaction, User Account Control (UAC) is here to stay and really isn’t as bad as critics make it out to be. As developers we have a responsibility to embrace it so that the applications we develop don’t annoy and desensitize our users with needless prompts.

In this part 4 of the Windows Vista for Developers series, we are taking a practical look at UAC and specifically what can be done programmatically with respect to elevation and integrity control.

What is security context?

Security context refers to those things that define and constrain what a process or thread can do in terms of permissions and privileges. A security context on Windows is defined in terms of a logon session and these are manipulated via tokens. As its name suggests, a logon session represents a specific session on a single computer for a given user. Programmers interact with logon sessions by means of tokens. Any number of tokens can be created that refer to the same logon session. These tokens can offer different sets of permissions and privileges based on a subset of those provided by the logon session. This is really the key to how UAC works or at least a big part of it.

So how does UAC work?

On Windows Vista there are two predominant types of user accounts, standard users and administrators. The first user account that you can create will be an administrator at least initially and any subsequent user accounts will be standard users by default. Standard user accounts are for those people who you do not trust with complete control over the computer. Administrator accounts are for those users who also enjoy complete control over the computer. Unlike previous versions of Windows, you don’t have to logon as a standard user to protect yourself from malicious code that may find its way to your computer. The logon sessions created for standard users and administrators are equally capable of protecting from such threats.

When a standard user logs on to a computer a new logon session is created and they are presented with a shell application such as Windows Explorer that was created by the system and associated with the user’s newly created logon session by means of a token. This effectively limits what the user can do since Windows Explorer can only run those applications and access those resources that the user’s logon session permits based on the permissions and privileges specified by the token.

When an administrator logs on to a computer things are a little different and this is where Windows Vista differs dramatically from previous versions. Although the system creates a new logon session, it creates not one but two different tokens representing the same logon session. The first token grants all the permissions and privileges afforded to the administrator while the second token is a restricted token, sometimes called a filtered token, offering far fewer permissions and privileges. This restricted token offers practically the same capabilities and constraints as would be granted to a standard user. The system then creates the shell application using the restricted token. This means that although the user is logged on as an administrator, applications are by default run with limited permissions and privileges.

When the administrator needs to perform some task that requires additional permissions or privileges not granted to the restricted token, he or she can elect to run an application using the full security context provided by the unrestricted token. What protects the administrator from malicious code is that this elevation to the unrestricted token is only allowed after the administrator has confirmed the desire to use the unrestricted token by means of a secure prompt provided by the system. Malicious code cannot suppress this prompt and thereby gain complete control over the computer without the user’s knowledge.

As I hinted at before, restricted tokens are not new Windows Vista but it is in Windows Vista that they are finally being used in an integrated way in the shell to provide a more secure environment for users to work (and play).

Restricted tokens

Although you will typically not have to create restricted tokens yourself, it is useful to understand how it’s done so that you have a better idea of what is being done on your behalf and so that you can have more insight into the environment in which your application will run. As a developer you may also find yourself needing to create an even more restrictive environment than what is provided by UAC in which case knowing how to create restricted tokens is a must.

The aptly named CreateRestrictedToken function creates a new token that is a duplicate of an existing token with certain restrictions. This function can restrict the toke in a number of ways:

•    By specifying deny-only security identifiers (SIDs) that can only be used to deny access to securable resources.
•    By specifying restricting SIDs that will be used as an additional access check.
•    By deleting privileges.

The restricted token used by UAC is created by adding deny-only SIDs and deleting privileges. Restricted SIDs are not used. Let’s walk through a simple example. The first thing we need is a token to duplicate and restrict. Let’s grab the process token:

CHandle processToken;

VERIFY(::OpenProcessToken(::GetCurrentProcess(),
                          TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY,
                          &processToken.m_h));

Next we need an array of SIDs to disable. This ensures that they can never be used to allow access. The following code uses my handy WellKnownSid class to construct the SID for the built-in Administrators group. The WellKnownSid class is available with the download for this article.

WellKnownSid administratorsSid = WellKnownSid::Administrators();

SID_AND_ATTRIBUTES sidsToDisable[] =
{
    &administratorsSid, 0
    // add additional SIDs to disable here
};

Next we need an array of privileges to delete. We first need to look up the privilege’s LUID value:

LUID shutdownPrivilege = { 0 };

VERIFY(::LookupPrivilegeValue(0, // local system
                              SE_SHUTDOWN_NAME,
                              &shutdownPrivilege));

LUID_AND_ATTRIBUTES privilegesToDelete[] =
{
    shutdownPrivilege, 0
    // add additional privileges to delete here
};

Finally, we can call CreateRestrictedToken to create the restricted token:

CHandle restrictedToken;

VERIFY(::CreateRestrictedToken(processToken,
                               0, // flags
                               _countof(sidsToDisable),
                               sidsToDisable,
                               _countof(privilegesToDelete),
                               privilegesToDelete,
                               0, // number of SIDs to restrict,
                               0, // no SIDs to restrict,
                               &restrictedToken.m_h));

The resulting token’s group SIDs will have an entry for the built-in Administrators group with the SE_GROUP_USE_FOR_DENY_ONLY flag making sure that the SID is used to deny access but not allow access. The token will also be stripped of the SeShutdownPrivilege privilege ensuring that the token cannot be used to restart, sleep, or shutdown the computer.

If this sounds interesting you can try a quick experiment. Copy the code above to a console application and then add the following call to the CreateProcessAsUser function, updating the path to the Windows Explorer executable as appropriate:

STARTUPINFO startupInfo = { sizeof (STARTUPINFO) };
ProcessInfo processInfo;

VERIFY(::CreateProcessAsUser(restrictedToken,
                             L"C:\\Windows\\Explorer.exe",
                             0, // cmd line
                             0, // process attributes
                             0, // thread attributes
                             FALSE, // don't inherit handles
                             0, // flags
                             0, // inherit environment
                             0, // inherit current directory
                             &startupInfo,
                             &processInfo));

Now kill any existing Explorer.exe process on your computer and run the code. You should notice that you can no longer use Explorer to restart, sleep, or shutdown the computer. These options in the Start menu should be disabled.

Finally, there is a function called IsTokenRestricted. It does not however tell you whether the token was created using the CreateRestrictedToken function but merely reports on whether the token contains restricted SIDs. So unless you’re using restricted SIDs it is not a very useful function.

Integrity levels

One aspect of UAC that gets very little attention is a concept called Mandatory Integrity Control. This is a new authorization feature added to processes and security descriptors. Securable resources can specify an integrity level in their security descriptors. Each process on the system is also marked with an integrity level which is then validated against the integrity level of the resource as an added authorization step. Despite its relative simplicity, it is a surprisingly useful feature as it allows you to very simply and effectively partition what a particular process can access.

Imagine you are developing an application that must routinely deal with data from untrusted sources such as the Internet. Since it may be possible for malicious code to circumvent any steps you may have taken to protect the computer, it is helpful to add a certain amount of “defense in depth” as an added layer of protection. One extremely effective solution was described in the previous section using restricted tokens. It can however be complicated to figure out exactly what SIDs to allow and what SIDs to deny access to certain resources and specifically what permissions to grant each identity considering that the application itself still needs certain permissions to function. This is where integrity levels come in. Integrity levels are primarily used to prevent write access while allowing read and execute access. By allowing read and execute access you allow applications to continue to perform the majority of their functions but preventing write access limits the harm they can cause by overwriting system files or tainting the user’s search paths as an example. This is exactly what Internet Explorer 7 does. It runs portions of the browser in a separate process with low integrity and there are only a handful of file system locations that provide write access to low integrity applications.

There are four integrity levels available to user-mode processes:

•    Low
•    Medium
•    High
•    System

Standard user tokens as well as restricted (non-elevated) administrator tokens have an integrity level of Medium. Unrestricted (elevated) administrator tokens have an integrity level of High. Processes running under the Local System account have an integrity level of System. The Internet Explorer process token has an integrity level of Low. A simple way to see the integrity levels for various processes is to get the latest version of Process Explorer which includes an optional column to display the integrity level of each process.

A child process will by default inherit the integrity level of the parent. You can change the integrity level when a process is created but not once it has been created. Further, you cannot raise the integrity level of a child process to anything higher than that of the parent. This avoids code with lower integrity from gaining higher integrity.

Let’s first take a look at how to query and set the integrity level for a process and then we’ll examine how to set the integrity level for securable resources.

Process integrity levels

You can determine the integrity level for a process by examining the process token. The GetTokenInformation function returns different classes of information. For example, to determine the user account represented by the token you specify the TokenUser class. GetTokenInformation will then populate a TOKEN_USER structure based on the information in the token. Similarly the TokenIntegrityLevel class can be specified to query the integrity level of the process and a TOKEN_MANDATORY_LABEL structure will be produced. Most of the structures populated by GetTokenInformation are variable length structures and since only GetTokenInformation knows how much space is required you need to follow a rigid pattern for calling this function. Since most of the low-level security functions use LocalAlloc and LocalFree to allocate and release memory, I use a LocalMemory class template along with a GetTokenInformation function template to simplify matters dramatically. You can find these templates in the download that accompanies this article. Here we will simply focus on the subject at hand:

CHandle processToken;

VERIFY(::OpenProcessToken(::GetCurrentProcess(),
                          TOKEN_QUERY,
                          &processToken.m_h));

LocalMemory<PTOKEN_MANDATORY_LABEL>info;

COM_VERIFY(GetTokenInformation(processToken,
                               TokenIntegrityLevel,
                               info));

SID* sid = static_cast<SID*>(info->Label.Sid);
DWORD rid = sid->SubAuthority[0];

switch (rid)
{
    case SECURITY_MANDATORY_LOW_RID:
    {
        // Low integrity process
        break;
    }
    case SECURITY_MANDATORY_MEDIUM_RID:
    {
        // Medium integrity process
        break;
    }
    case SECURITY_MANDATORY_HIGH_RID:
    {
        // High integrity process
        break;
    }
    case SECURITY_MANDATORY_SYSTEM_RID:
    {
        // System integrity level
        break;
    }
    default:
    {
        ASSERT(false);
    }
}

Here OpenProcessToken is used again to get the process token to query. My GetTokenInformation function template is then called with the appropriate information class and LocalMemory class template specifying the information type. The populated TOKEN_MANDATORY_LABEL structure contains a SID that represents the integrity level. Dissecting it will give you the relative identifier (RID) representing the integrity level that you program against.

To set the integrity level for a child process is pretty straightforward. Start by creating a duplicate of the parent process token. Then simply set the integrity level using the same information class and data structure used to query the integrity level in the previous example. The SetTokenInformation function comes in handy for this. Finally you can call CreateProcessAsUser to create the child process using the modified token. Here’s an example:

CHandle processToken;

VERIFY(::OpenProcessToken(::GetCurrentProcess(),
                          TOKEN_DUPLICATE,
                          &processToken.m_h));

CHandle duplicateToken;

VERIFY(::DuplicateTokenEx(processToken,
                          MAXIMUM_ALLOWED,
                          0, // token attributes
                          SecurityAnonymous,
                          TokenPrimary,
                          &duplicateToken.m_h));

WellKnownSid integrityLevelSid(WellKnownSid::MandatoryLabelAuthority,
                               SECURITY_MANDATORY_LOW_RID);

TOKEN_MANDATORY_LABEL tokenIntegrityLevel = { 0 };
tokenIntegrityLevel.Label.Attributes = SE_GROUP_INTEGRITY;
tokenIntegrityLevel.Label.Sid = &integrityLevelSid;

VERIFY(::SetTokenInformation(duplicateToken,
                             TokenIntegrityLevel,
                             &tokenIntegrityLevel,
                             sizeof (TOKEN_MANDATORY_LABEL) + ::GetLengthSid(&integrityLevelSid)));

STARTUPINFO startupInfo = { sizeof (STARTUPINFO) };
ProcessInfo processInfo;

VERIFY(::CreateProcessAsUser(duplicateToken,
                             L"C:\\Windows\\Notepad.exe",
                             0, // cmd line
                             0, // process attributes
                             0, // thread attributes
                             FALSE, // don't inherit handles
                             0, // flags
                             0, // inherit environment
                             0, // inherit current directory
                             &startupInfo,
                             &processInfo));

This example launches Notepad. Give it a try. You should notice that although Notepad can open text files in most locations, it cannot save to any location that is not marked with a low integrity level.

Finally, you can get a display name for integrity levels using the LookupAccountSid function, but they’re not all that user friendly so you’re better off using a string table with values like “Low”, “Medium”, “High”, and “System”.

The token created by the system for standard users has an integrity level of Medium. The restricted, or filtered, token created by the system for administrators also has an integrity level of Medium, but the unrestricted administrator token has an integrity level of High.

Now let’s examine how to set the integrity level for a particular resource.

Resource integrity levels

The integrity level for a resource is stored in a special access control entry (ACE) in the system access control list (SACL) of the resource’s security descriptor. The simplest way to set or update this value for system resources is using the SetNamedSecurityInfo function. AddMandatoryAce is a new function provided by Windows Vista to add the special ACE, known as a mandatory ACE, to an ACL. Remember, security is all about confusing people with acronyms…

Seriously, the code is quite simple if you’re familiar with programming security descriptors. Start off by preparing an ACL pointing to a buffer that is just large enough to hold a single ACE. This is achieved using the InitializeAcl function. Next the SID representing the integrity level is created and added to the ACL using the AddMandatoryAce function. Finally the integrity level is updated using the SetNamedSecurityInfo function. Notice the new LABEL_SECURITY_INFORMATION flag being used in the sample below:

LocalMemory<PACL> acl;

const DWORD bufferSize = 64;
COM_VERIFY(acl.Allocate(bufferSize));

VERIFY(::InitializeAcl(acl.m_p,
                       bufferSize,
                       ACL_REVISION));

WellKnownSid sid(WellKnownSid::MandatoryLabelAuthority,
                 SECURITY_MANDATORY_LOW_RID);

COM_VERIFY(Kerr::AddMandatoryAce(acl.m_p,
                                 &sid));

CString path = L"C:\\SampleFolder";

DWORD result = ::SetNamedSecurityInfo(const_cast<PWSTR>(path.GetString()),
                                      SE_FILE_OBJECT,
                                      LABEL_SECURITY_INFORMATION,
                                      0, // owner
                                      0, // group
                                      0, // dacl
                                      acl.m_p); // sacl

ASSERT(ERROR_SUCCESS == result);

Retrieving the integrity level of a resource is equally simple provided you realize that most resources won’t have an integrity level set explicitly. Rather the system interprets the absence of an integrity label as if the resource were marked with “Medium” integrity. Start by calling GetNamedSecurityInfo with the same security information flag namely LABEL_SECURITY_INFORMATION. If a valid ACL pointer is returned and contains an ACE then you know the integrity level is set explicitly. Now simply call the GetAce function to retrieve a pointer to the ACE storing the integrity level SID and read the RID value to determine the integrity level. Here is an example:

CString path = L"C:\\SampleFolder";
LocalMemory<PSECURITY_DESCRIPTOR>descriptor;
PACL acl = 0;

DWORD result = ::GetNamedSecurityInfo(const_cast<PWSTR>(path.GetString()),
                                      SE_FILE_OBJECT,
                                      LABEL_SECURITY_INFORMATION,
                                      0,
                                      0,
                                      0,
                                      &acl,
                                      &descriptor.m_p);

ASSERT(ERROR_SUCCESS == result);

DWORD integrityLevel = SECURITY_MANDATORY_MEDIUM_RID;

if (0 != acl && 0 < acl->AceCount)
{
    ASSERT(1 == acl->AceCount);

    SYSTEM_MANDATORY_LABEL_ACE* ace = 0;

    VERIFY(::GetAce(acl,
                    0,
                    reinterpret_cast<void**>(&ace)));

    ASSERT(0 != ace);

    SID* sid = reinterpret_cast<SID*>(&ace->SidStart);

    integrityLevel = sid->SubAuthority[0];
}

ASSERT(SECURITY_MANDATORY_LOW_RID == integrityLevel);

Run as administrator

So far we’ve looked at some of the individual building blocks that make up UAC such as restricted tokens and integrity levels. Let’s now take a look at what it means to “Run as administrator” and how this can be done programmatically. You have probably noticed that you can right-click an application or shortcut and select a context menu option entitled “Run as administrator”. This is available for both standard users and administrators. The concept of running as an administrator is more commonly referred to simply as elevation or creating an elevated process. Standard users are prompted for administrator credentials while administrators are simply prompted for permission to elevate. Either way the end result is a new process running with an unrestricted administrator token and all the permissions and privileges it affords.

The process of elevation is a bit complicated but fortunately much of the complexity is hidden behind an updated ShellExecute(Ex) function. ShellExecute on Windows Vista makes use of the new Application Information (appinfo) service to perform elevation through an undocumented COM interface. ShellExecute first calls CreateProcess to attempt to create the new process. CreateProcess does all the work of checking application compatibility settings, application manifests, runtime loaders, etc. If it determines that the application requires elevation but the calling process is not elevated then CreateProcess fails with ERROR_ELEVATION_REQUIRED. ShellExecute then calls the Application Information service to handle the elevation prompt and creation of the elevated process since the calling process obviously doesn’t have the necessary permissions to perform such a task. The Application Information service ultimately calls CreateProcessAsUser with an unrestricted administrator token.

If on the other hand you want to create an elevated process regardless of what application information is available then you can specify the little-known “runas” verb with ShellExecute. This has the effect of requesting elevation regardless of what an application’s manifest and compatibility information might prescribe. The runas verb is not actually new to Windows Vista. It was available on Windows XP and Windows 2003 and was often used to create a restricted token directly from the shell. This behavior has however changed. Here is a simple example:

::ShellExecute(0, // owner window
               L"runas",
               L"C:\\Windows\\Notepad.exe",
               0, // params
               0, // directory
               SW_SHOWNORMAL);

Considering how much is going on behind the scenes, aren’t you glad this is all the code you need to write? Although creating a new elevated process is reasonable, it’s not always the most appropriate solution if you only need to elevate temporarily. Enter elevated COM objects.

Creating an elevated COM object

If you invested heavily in the COM era you’ll know that COM supports creating COM servers hosted in a surrogate process. Well this technique has been augmented to allow COM servers to be created in an elevated surrogate process. This is extremely useful since you can simply create the COM object on the fly in your application without having to create a whole new process directly.

The hardest part about using this technique is registering the COM server correctly to indicate that it should be loaded in a surrogate process and support elevation since the COM object needs to specifically declare its cooperation.

This first thing to do is update the COM registration to declare that your library (DLL) server can be run in a surrogate process. All you need to do is add the “DllSurrogate” named value under your server’s AppID registry key. In ATL you simply update the project’s main RGS file to look something like this:

HKCR
{
    NoRemove AppID
    {
        '%APPID%' = s 'SampleServer'
        {
            val DllSurrogate = s ''
        }
        'SampleServer.DLL'
        {
            val AppID = s '%APPID%'
        }
    }
}

An empty value for DllSurrogate indicates that the system-supplied surrogate is to be used. The COM client can now specify the CLSCTX_LOCAL_SERVER execution context to create the COM server in the surrogate process:

CComPtr<ISampleServer>server;

COM_VERIFY(server.CoCreateInstance(__uuidof(SampleServer),
                                   0,
                                   CLSCTX_LOCAL_SERVER));

The next step is to enable elevated launch of the COM class. This involves adding entries to the COM class registration script. An elevation key is added to indicate that elevation is supported for this particular COM class and a “LocalizedString” named value is also required and provides the display name used by the UAC prompt. An ATL COM class registration script should look something like this:

HKCR
{
    SampleServer.SampleServer.1 = s 'SampleServer Class'
    {
        CLSID = s '{91C5423A-CF90-4E62-93AD-E5B922AE8681}'
    }
    SampleServer.SampleServer = s 'SampleServer Class'
    {
        CLSID = s '{91C5423A-CF90-4E62-93AD-E5B922AE8681}'
        CurVer = s 'SampleServer.SampleServer.1'
    }
    NoRemove CLSID
    {
        ForceRemove {91C5423A-CF90-4E62-93AD-E5B922AE8681} = s 'SampleServer Class'
        {
            ProgID = s 'SampleServer.SampleServer.1'
            VersionIndependentProgID = s 'SampleServer.SampleServer'
            InprocServer32 = s '%MODULE%'
            {
                val ThreadingModel = s 'Neutral'
            }
            val AppID = s '%APPID%'
            'TypeLib' = s '{A43B074B-0452-4FF4-8308-6B0BF641C3AE}'
            Elevation
            {
                val Enabled = d 1
            }
            val LocalizedString = s '@%MODULE%,-101'
        }
    }
}

Don’t forget to add a string to your string table for the localized name. The COM client can now launch the elevated COM server. As with CreateProcess, the CoCreateInstance function won’t create an elevated COM object directly. Instead you need to use the COM elevation moniker. This is most easily achieved using the CoGetObject function to build the moniker and return a proxy to the object that is ultimately created:

template <typename T>
HRESULT CreateElevatedInstance(HWND window,
                               REFCLSID classId,
                               T** object)
{
    BIND_OPTS3 bindOptions;
    ::ZeroMemory(&bindOptions, sizeof (BIND_OPTS3));
    bindOptions.cbStruct = sizeof (BIND_OPTS3);
    bindOptions.hwnd = window;
    bindOptions.dwClassContext = CLSCTX_LOCAL_SERVER;

    CString string;
    const int guidLength = 39;

    COM_VERIFY(::StringFromGUID2(classId,
                                 string.GetBufferSetLength(guidLength),
                                 guidLength));

    string.ReleaseBuffer();
    string.Insert(0, L"Elevation:Administrator!new:");

    return ::CoGetObject(string,
                         &bindOptions,
                         __uuidof(T),
                         reinterpret_cast<void**>(object));
}

Using the function template is just as simple as calling CoCreateInstance:

CComPtr<ISampleServer>server;

COM_VERIFY(CreateElevatedInstance(0, // window
                                  __uuidof(SampleServer),
                                  &server);

As with ShellExecute, the window handle is used by UAC to determine whether the prompt will steal the focus or whether it will be waiting for the user in the background.

Using application manifests

Remember when I said that CreateProcess checks application compatibility settings and application manifests among other things? As it turns out, Windows Vista goes to great lengths to ensure that legacy 32-bit applications run properly. This involves a tremendous amount of plumbing to virtualize areas of the file system and registry that applications traditionally had full access to but no longer do under the more restrictive environment imposed by UAC. As impressive as all this virtualization is, the key is to avoid it if at all possible. It’s meant for legacy applications. If you’re writing an application today make sure to provide an application manifest which is the surest way to make sure that virtualization is not applied to your application. Indeed Microsoft is planning on removing the virtualization capabilities in a future version of Windows.

The application manifest schemas have received an upgrade in Windows Vista to allow them to express the application’s requested security context. What makes it a little more complicated is that Visual C++ by default automatically generates an application manifest. This is actually a very good thing. The linker is keenly aware of the dependencies of your application and the manifest is used to define dependencies on side-by-side assemblies. Fortunately it also provides an option for merging in additional manifest files into the manifest that is ultimately embedded into the application executable. The Visual C++ project’s “Additional Manifest Files” setting is just the thing. Here’s a simple manifest file that declares a dependency on Common Controls 6.0 and specifies a desired security context:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        processorArchitecture="*"
        publicKeyToken="6595b64144ccf1df"
        language="*"
        />
    </dependentAssembly>
  </dependency>
  <v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
    <v3:security>
      <v3:requestedPrivileges>
        <!-- level can be "asInvoker", "highestAvailable", or "requireAdministrator" -->
        <v3:requestedExecutionLevel level="highestAvailable" />
      </v3:requestedPrivileges>
    </v3:security>
  </v3:trustInfo>
</assembly>

The requested execution level can specify one of three values. “asInvoker” is the default and a new process will simply inherit the token of the parent process. “highestAvailable” indicates that the application is requesting the most unrestictive security context available to the user. For standard users this is the same as “asInvoker” but for administrators this is a demand for the unrestricted token. “requireAdministrator” indicates that a unrestricted administrator token is needed. Standard users will be prompted for administrator credentials and administrators will be prompted for consent to elevate.

Remember, CreateProcess will check the manifest and fail to create the process if the requested execution level does not match that of the parent process. Only ShellExecute makes use of the Application Information service to perform elevation.

Am I already elevated?

Finally, if all you need is to determine whether or not you are currently elevated you can simply call the IsUserAnAdmin function. If you need more precision you can also use GetTokenInformation but in most cases that is overkill.

Conclusion

That’s it for UAC. I hope you’ve found this helpful. Very little of what I’ve described is currently documented. Hopefully that will change.

I should add that all the samples I present typically use assertions and similar macros to check for errors. I do this to identify where error handling is necessary. If you use some of the code in your own applications, be sure to replace these macros with whatever error handling strategy you use, whether its exceptions, HRESULTs or something else.

The download for this article include some helpful class templates and functions that you may find helpful.

Read part 5 now: Getting Started With Server Core

 

Have you tried Window Clippings yet?


© 2006 Kenny Kerr

 

24 Comments

  • Sorry, my fault - someone changed the proxy security settings and disabled the download... sigh.

    Ch.

  • Hi,

    How do I call the ShellExecute runas verb via managed code, i.e. c#?

    Do I p/invoke under .NET 2.0?

  • Oh, apologies, the property .Verb on ProcessInfo does the trick - sorry!

  • David:
    using (Process process = new Process())
    {
    &nbsp;&nbsp; process.StartInfo.FileName = // app path
    &nbsp;&nbsp; process.StartInfo.Verb = &quot;runas&quot;;
    &nbsp;&nbsp; process.Start();
    }

  • quote from article:
    "As developers we have a responsibility to embrace it so that the applications we develop don’t annoy and desensitize our users with needless prompts."

    As implementers of new functionality, if their new functionality is going to cause so many "needless prompts" then they should have some sort of legacy support in place. It's ridiculous to expect everyone to re-write all their apps. Most importantly, any company that does a re-write will likely put the new functionality in a new version and require users to buy a new version. Now, what happens to all those people with existing versions? They still get those "needless prompts" don't they?

    Please not, I am not arguing pro or con about UAC. I've specifically left out my personal opinion about it because that's not my point. I am specifically commenting on how implementing a new feature in such a way to cause "needless prompts" and then pushing the responsibility onto other developers is not a good thing. What about the responsibility of the UAC developers? If we have a "responsibility" to "avoid needless prompt" what is the responsibility of the UAC developers? Shouldn't it be similar?

  • Peter: thanks for sharing your thoughts. To be clear, legacy apps will not necessarily have any elevation prompts. If they rely on functionality that requires the full administrator token, then they must run as an administrator requiring one prompt to launch after which there will be no further prompts since the app is running with an unrestricted administrator token. The UAC virtualization layer also goes to great lengths to ensure that many legacy apps will continue to work without prompts and without requiring elevation. This obviously doesn’t work 100% of the time. As far as legacy apps go, I personally think the “UAC developers” have done a lot of work to helper application developers. I did not however focus on all the features of UAC that are provided for legacy apps as the focus of my series is on development for Vista, whether new apps or new versions of existing applications.

    At the end of the day, the same guidelines apply as for older versions of Windows. If you have operations in your application that require greater than normal permissions they should typically be partitioned in a separate process, typically running as a service, while the majority of your application can continue to run as a normal user. Another common strategy is to gracefully fail or limit the operations of an application when it is not run as an administrator. None of this is new to Vista. Elevation through UAC is of course now an alternative but one that should be used with care as with any security feature.

  • Hi,

    i tried to build my application in VS2005 on Vista, it doesn't work well (it doesn't creates the specified output file in the program files folder) but if you give some other directory name or if i switch off UAC then it ll work fine.

    What changes i need to do now?

  • Nanu: not knowing much about your app it’s hard to say specifically but one thing’s for sure: don’t write to the Program Files directory. You should never assume you have write access to this location unless you’re an installer.

    The next article (part 6) in the series deals with known “special” folders and should help in figuring out where to store app-specific data.

  • This is a "must" for us. (This is chicken and egg problem... we cannot write HKCU)

  • I have a C# application that may be run as an administrator and if that application creates a new process the process inherits the elevation. I need to run the new process as user; is there a simple way to do this under .NET?

  • Hi, I have an application to create namespace extension under my computer and which is running well under XP,

    but if i try the same in Vista its not working properly, no syncronization between treeview and list view. i used the COM objects to view the contents of namespace in Explorer using interfaces like IShellView and IShellFolder etc.

    what do you think, its problem in COM DLL registration(i am not modified any code as you sujjested in this article) or the interface derivation(because as i know there are new interfaces in vista).

    please help in this regard,

    Thanks
    Nanu

  • Hi,

    I need to create an elevated process, but I can't seem to use ShellExecute because I need the CREATE_SUSPENDED flag (create the process suspended). Any clues on how to solve this ?

    Thanks in advance,

    Michiel.

  • Michiel: Normally a parent process has control over a child process but since UAC uses a process broker to create the child process and the new process is running at a higher integrity level it would break the UAC security promise if the logical “parent” process were allowed to control the elevated process, so unfortunately CREATE_SUSPENDED is not a supported option in this scenario. Consider if you were able to create the process suspended: you would have no way of resuming it.

  • Big thanks to Andrei since his method produces really non-elevated process rather then a restricted one. For example, if you run taskmgr.exe as a restricted process, it is unable to re-run itself elevated when you click the "show processes for all users" button.

  • I launch 2 DOS box on Vista, one with "Run as Administrator". The mapped drives (by net use) created on above 2 DOX box can not be shared with each other.
    Now, I have a app which is embeded a manifest file to make it always run with administrator token, I hope it can create a process with "standard user token" to access those mapped drived create by "standard user token", could you please let me know how to create this kind of process?

  • I heard by using the service, we can start an elevated privilage application (runas administrator type) without UAC prompt in the user account session. I am working on it but don't see any light in avoiding the prompt. I could launch the process with Administrator but it is on System session desktop (which is even worse). Am I hitting my head on wall or is there any hope?

  • Kenny,

    Do you know of any way to have Vista run my .NET app under Low integrity by default? It'd be very nice, since my app doesn't need even Medium integrity, so it shouldn't have it.

  • Ben: good question, but this is unnecessary for managed code. Managed apps can take advantage of Code Access Security and partial trust to provide an even stronger sandbox than UAC provides by itself. In this case UAC simply provides an additional layer of defense.

  • Do you have a good link to an article explaining how to use CAS to reduce the privilidges of my app? For example, only allow it to read/write to certain directories and connect to certain websites? All I can ever find is articles on how to demand *more* permissions.

  • Hi,

    I have a legacy application, which creates/writes files in ProgramFiles. To support it on Vista, I am writing a sample dll and test driver that uses low level security API to detect the rights programmatically. I have tried to disable the Virualization for the current process using SetTokenInformation. But still, I am not able to write to the folders. But if I use 'Run as Administrator' on the right-click menu, it works perfectly. So I tried to elevate the current process using SetTokenInformation as
    {
    DWORD dwElevationEnabled = 1;
    SetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)20, (LPVOID)&dwElevationEnabled, 4) ; }

    This call is failing. How can I elevate this process to admin level??
    Can someone help me please?

    Thanks,
    Kumar

  • Kumar: You cannot elevate an existing process. This is not the same as thread impersonation. To elevate you need to launch a new process as I described in the article. Also, virtualization cannot be turned on and off within a process. You need to provide a manifest, again as I described in the article.

    And as I’ve said before, unless you’re writing a setup program you should not be writing to the Program Files directory. If this is something you need to do to support your legacy app then take advantage of virtualization rather than trying to turn it off.

  • The "download" is not AddMandatoryAce fuc....


  • How can I elevate my process to admin level?? and then use CreateProcessAsUser to run a .exe driver application?

  • In windows XP/2003 I used like that:

    if ( CreateProcess( gbAppInCmdLine ? NULL: glpAppPath, // lpszImageName
    glpCmdLine, // lpszCommandLine
    0, // lpsaProcess
    0, // lpsaThread
    FALSE, // fInheritHandles
    DETACHED_PROCESS, // fdwCreate
    0, // lpvEnvironment
    0, // lpszCurDir
    &startupInfo, // lpsiStartupInfo
    &processInfor // lppiProcInfo
    ))
    {

    if(processInfor.hProcess != NULL)
    {
    WaitForSingleObject( processInfor.hProcess, INFINITE);

    // CloseHandle(processInfor.hProcess);
    }
    // GetExitCodeProcess( processInformation.hProcess, &dwExitCode );
    ::PostMessage(hwnd,WM_DRIVER,0,1);
    }
    else
    {
    ::PostMessage(hwnd,WM_DRIVER,-1,1);
    }

    How can I do in vista?

Comments have been disabled for this content.