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