-
Notifications
You must be signed in to change notification settings - Fork 239
Token Helpers
WIL Windows Security Token methods make using thread and process tokens easier.
The token helpers can be used by including the correct header file:
#include <wil/token_helpers.h>
A thread running with a different token than the token of its parent process is said to be impersonating. In most cases, security decisions should be made using the current access token - the thread token if set, the process token otherwise. This method returns a handle to that effective token, checking first the thread and then the process.
unique_handle open_current_access_token(
unsigned long access = TOKEN_QUERY,
OpenThreadTokenAs openAs = OpenThreadTokenAs::Current);
By default the token is opened with TOKEN_QUERY
access
which allows inspection of the token but nothing else. This parameter is
passed along to OpenThreadToken
and OpenProcessToken
.
By default, the access check is made against the current security
context of the calling thread (OpenThreadTokenAs::Current
).
Passing OpenThreadTokenAs::Self
performs the access check
against the process security context instead. This is useful
when the current thread is a low-rights token and the desired access
is available to the process but not to the thread token.
The returned token handle is a regular token handle and can be used with any other token-accepting API.
Example:
auto check_token = wil::open_current_access_token(TOKEN_QUERY);
auto check_token = wil::open_current_access_token(
TOKEN_IMPERSONATE | TOKEN_QUERY, OpenThreadTokenAs::Self);
unique_handle open_current_access_token_failfast(
ULONG access = TOKEN_QUERY,
OpenThreadTokenAs openAs = OpenThreadTokenAs::Current);
The failfast
version fails fast if the token cannot be opened.
HRESULT open_current_access_token_nothrow(
HANDLE* token,
ULONG access = TOKEN_QUERY,
OpenThreadTokenAs openAs = OpenThreadTokenAs::Current);
The nothrow
version clears the output token parameter on failure
and returns GetLastError
as an HRESULT
.
Windows pseudo tokens can be used with many token-accepting APIs. These pseudo-handles are context-sensitive but do not need lifecycle management. Examples include:
HANDLE GetCurrentThreadEffectiveTokenWithOverride(HANDLE tokenHandle);
The GetCurrentThreadEffectiveTokenWithOverride
function returns
GetCurrentThreadEffectiveToken()
if the tokenHandle
parameter is null;
otherwise, it returns the tokenHandle
.
The idea is that the function normally returns
GetCurrentThreadEffectiveToken()
,
but passing an explicit non-null tokenHandle
overrides that behavior.
This function is useful when you want to allow nullptr
to mean "the current token",
rather than requiring the caller to call GetCurrentThreadEffectiveToken()
explicitly.
The handle returned by GetCurrentThreadEffectiveTokenWithOverride
should
not be stored in a unique_handle
because its lifetime is controlled by
the input parameter
(and in the case of a pseudo-handle, it may not be an actual token at all).
Examples:
THROW_IF_WIN32_BOOL_FALSE(::AccessCheck(
...,
wil::GetCurrentThreadEffectiveTokenWithOverride(m_customToken.get()),
...));
GetTokenInformation
returns many different kinds of information about a token in a single
method call. Some information is variably-sized, requiring the typical
two-call pattern and management of an allocation. WIL's helper method
simplifies this operation.
The token methods follow these patterns:
// Variably-sized data returned in a unique_ptr<>
template<typename T>
wistd::unique_ptr<T> get_token_information(_In_ HANDLE token = nullptr);
// Statically-sized data returned directly
template<typename T>
T get_token_information(_In_ HANDLE token = nullptr);
-
T
A structure related to a token information class. The structure type is matched to aTOKEN_INFORMATION_CLASS
when callingGetTokenInformation
. When the class data is variably sized, such asTOKEN_USER
, the result is wrapped inside awistd::unique_ptr<T>
.
The following structures and class levels are supported:
TOKEN_INFORMATION_CLASS |
Output Type |
---|---|
TokenAccessInformation |
wistd::unique_ptr<TOKEN_ACCESS_INFORMATION> |
TokenAppContainerSid |
wistd::unique_ptr<TOKEN_APPCONTAINER_INFORMATION> |
TokenDefaultDacl |
wistd::unique_ptr<TOKEN_DEFAULT_DACL> |
TokenGroupsAndPrivileges |
wistd::unique_ptr<TOKEN_GROUPS_AND_PRIVILEGES> |
TokenIntegrityLevel |
wistd::unique_ptr<TOKEN_MANDATORY_LABEL> |
TokenOwner |
wistd::unique_ptr<TOKEN_OWNER> |
TokenPrimaryGroup |
wistd::unique_ptr<TOKEN_PRIMARY_GROUP> |
TokenPrivileges |
wistd::unique_ptr<TOKEN_PRIVILEGES> |
TokenUser |
wistd::unique_ptr<TOKEN_USER> |
TokenElevationType |
TOKEN_ELEVATION_TYPE |
TokenMandatoryPolicy |
TOKEN_MANDATORY_POLICY |
TokenOrigin |
TOKEN_ORIGIN |
TokenSource |
TOKEN_SOURCE |
TokenStatistics |
TOKEN_STATISTICS |
TokenType |
TOKEN_TYPE |
TokenImpersonationLevel |
SECURITY_IMPERSONATION_LEVEL |
TokenElevation |
TOKEN_ELEVATION |
-
token
When passed, this is the token whose information is to be queried. Both real and pseudo-tokens are valid parameters. Real tokens must have been opened withTOKEN_QUERY
access. When defaulted (or explicitly passed asnullptr
) the information is queried from the effective thread token.
// Reset the owner of the key to the effective user
THROW_IF_WIN32_ERROR(::SetNamedSecurityInfo(
keyPath,
SE_REGISTRY_KEY,
OWNER_SECURITY_INFORMATION,
wil::get_token_information<TOKEN_USER>()->User.Sid,
nullptr, nullptr, nullptr));
// See if the passed-in token can be impersonated
if (wil::get_token_information<TOKEN_TYPE>() == TokenImpersonation)
{
// ...
}
Nonthrowing variants emit their results through their first parameter. When
the information query fails, GetLastError
is returned inside an HRESULT
.
template<typename T>
HRESULT get_token_information_nothrow(wistd::unique_ptr<T>& tokenInfo, _In_ HANDLE token = nullptr);
template<typename T>
HRESULT get_token_information_nothrow(T& tokenInfo, _In_ HANDLE token = nullptr);
Failfast variants fail-fast when the query operation fails.
// Variably-sized data emitted in a unique_ptr<>
template<typename T>
wistd::unique_ptr<T> get_token_information_failfast(_In_ HANDLE token = nullptr);
// Statically-sized data emitted in a unique_ptr<>
template<typename T>
T get_token_information_failfast(_In_ HANDLE token = nullptr);
Impersonation changes the current thread token to another token so the
thread can access resources available to the impersonated token. The key
method is SetThreadToken
which replaces the current thread token with a new token. It is common to
change the thread token temporarily, perform some operations,
and then restore the original thread token.
unique_token_reverter impersonate_token(_In_ HANDLE token);
This method calls SetThreadToken
with its parameter and returns a scope-
exit object that resets the token back to whatever it had been before.
The passed-in token must have been acquired with TOKEN_IMPERSONATE
access.
Note that passing nullptr
as the token causes the thread
to stop impersonating.
If impersonation fails, GetLastError
is placed in an HRESULT
and thrown inside a wil::ResultException
.
Impersonation is reverted when the unique_token_reverter
object is destructed or
.reset()
.
// Impersonate the caller when opening the caller's log file.
auto runAs = wil::impersonate_token(m_callerToken.get());
wil::unique_hfile log { ::CreateFile( ..., FILE_READ, ... ) };
// Restore the original token.
// The log file handle still has 'read' access.
runAs.reset();
::ReadFile( log.get(), ... );
A nonthrowing version takes the reverter object as a parameter and
initializes it during the call. Errors during impersonation are
emitted as GetLastError
wrapped in an HRESULT
.
HRESULT impersonate_token_nothrow(HANDLE token, unique_token_reverter& reverter);
A failfast variant returns the reverter object but fails-fast when impersonation fails.
unique_token_reverter impersonate_token_failfast(HANDLE token);
A common operation in a higher-privileged server is to "run as itself" when accessing
resources unavailable to its caller. This operation is a wrapper around calling
wil::impersonate_token
with nullptr
- "no token." As the thread is no longer
impersonating anyone, access checks are performed under the process token instead.
unique_token_reverter run_as_self();
HRESULT run_as_self_nothrow(unique_token_reverter&);
unique_token_reverter run_as_self_failfast();
Example:
void WriteAccessEntry()
{
// This serivce immediately impersonates its RPC client for the duration of an
// operation, elevating back to 'self' only as necessary. Here it captures the SID
// of the caller, opens its log file for write, saves the SID, and continues.
auto userId = wil::get_token_information<TOKEN_USER>();
// Switch to running as the service
auto runAsSelf = wil::run_as_self();
wil::unique_hfile log { ::CreateFile( ..., FILE_WRITE, ...) };
DWORD wrote{};
THROW_IF_WIN32_BOOL_FALSE(::WriteFile(log.get(), userId->User.Sid,
GetLengthSid(userId->User.Sid), &wrote));
} // impersonation reverts here, back to the RPC caller's token
This method returns an initialized object structured like a
SID
but without requiring an allocation or call to AllocateAndInitializeSid
.
It's convenient when the SID is fixed or known at compilation time.
template<typename... Ts>
constexpr auto make_static_sid(const SID_IDENTIFIER_AUTHORITY&, Ts&&... subAuthorities);
The returned type is an instance of wil::details::sid_t<>
physically laid out like
a _SID
structure. It contains precisely sizeof...(Ts)
subauthorities.
Example:
auto systemSid = wil::make_static_sid(
SECURITY_NT_AUTHORITY,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS);
RETURN_IF_WIN32_ERROR(::SetNamedSecurity(..., &systemSid, ...));
Functions that accept PSID
will accept &sid_t<N>
as PSID
is an alias of PVOID
. For
explicit conversion, use the .get()
method which returns PSID
.
As the SECURITY_NT_AUTHORITY
is common on Windows a convenience wrapper is provided
that only takes the subauthorities list:
auto sid = wil::make_static_nt_sid(SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
This method accepts a SID definition, constructs a sid_t
around it, and calls the
platform API CheckTokenMembership
.
When the SID has an enabled entry in the token this method returns
true
.
template<typename... Ts>
bool test_token_membership(_In_ HANDLE token, const SID_IDENTIFIER_AUTHORITY& authority, Ts&&... subAuthorities);
This sample function checks whether the caller is a member of the "domain guests" group:
bool IsGuest()
{
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_GUESTS);
}
-
token
The token to use when looking for membership. Passingnullptr
uses the effective thread token instead. -
authority
ASID_IDENTIFIER_AUTHORITY
used in relationship to the subauthorities. MSDN has a list of know authorities. -
subAuthorities
Zero or more subauthorities may be passed in.
A nonthrowing variant places the result of testing membership in an out-parameter
and returns any errors during the check as GetLastError
wrapped in an HRESULT
.
template<typename... Ts>
HRESULT test_token_membership_nothrow(_Out_ bool* result, _In_ HANDLE token,
const SID_IDENTIFIER_AUTHORITY& authority,
Ts&&... subAuthorities);
A failfast variant returns the result of the test on success and fails-fast otherwise.
template<typename... Ts>
bool test_token_membership_failfast(_In_ HANDLE token, const SID_IDENTIFIER_AUTHORITY& authority, Ts&&... subAuthorities);
This method is similar to wil::get_token_information
purpose-built for querying
the TokenIsAppcontainer
information level. See GetTokenInformation
for a special note on using this information level.
bool get_token_is_appcontainer(_In_ HANDLE token = nullptr);
HRESULT get_token_is_appcontainer_nothrow(_Out_ bool* isAppcontainer, _In_ HANDLE token = nullptr);
bool get_token_is_appcontainer_failfast(_In_ HANDLE token = nullptr);