Skip to content
Raymond Chen edited this page Oct 4, 2019 · 3 revisions

WIL Windows Security Token methods make using thread and process tokens easier.

Usage

The token helpers can be used by including the correct header file:

#include <wil/token_helpers.h>

wil::open_current_access_token

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);

Method Variants

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.

Pseudo-token helpers

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()),
    ...));

wil::get_token_information

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);

Template Parameters

  • T
    A structure related to a token information class. The structure type is matched to a TOKEN_INFORMATION_CLASS when calling GetTokenInformation. When the class data is variably sized, such as TOKEN_USER, the result is wrapped inside a wistd::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 parameter

  • 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 with TOKEN_QUERY access. When defaulted (or explicitly passed as nullptr) the information is queried from the effective thread token.

Samples

// 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)
{
    // ...
}

Method Variants

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);

wil::impersonate_token

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(), ... );

Method Variants

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);

wil::run_as_self

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

wil::make_static_sid

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.

Method Variants

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);

wil::test_token_membership

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);
}

Parameters

  • token
    The token to use when looking for membership. Passing nullptr uses the effective thread token instead.
  • authority
    A SID_IDENTIFIER_AUTHORITY used in relationship to the subauthorities. MSDN has a list of know authorities.
  • subAuthorities
    Zero or more subauthorities may be passed in.

Variants

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);

wil::get_token_is_appcontainer

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);