Skip to content
Jon Wiswall edited this page Jul 24, 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_acess_token(unsigned long access = TOKEN_QUERY, OpenThreadTokenAs openAs = OpenThreadTokenAs::Self);

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.

The OpenThreadTokenAs::Self option controls whether the call to OpenThreadToken passes TRUE for OpenAsSelf. 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);
  • The nothrow version clears the output token parameter and returns GetLastError as an HRESULT.
  • The failfast version fails fast if the token cannot be opened.

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:

wil::GetCurrentThreadEffectiveTokenWithOverride takes a token parameter, and if it's nullptr, returns GetCurrentThreadEffectiveToken instead. Useful for methods that want to take nullptr to mean "the current token" rather than pushing a call to GetCurrentThreadEffectiveToken upstream.

Examples:

THROW_IF_WIN32_BOOL_FALSE(::AccessCheck(
    ...,
    wil::GetCurrentThreadEffectiveTokenWithOverride(m_myToken.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 emitted in a unique_ptr<>
template<typename T>
wistd::unique_ptr<T> get_token_information(_In_ HANDLE token = nullptr);

// Statically-sized data emitted in a unique_ptr<>
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 emitted inside a wistd::unique_ptr<T>.

A list of the supported structures and class levels:

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. Many implementations treat the thread token like a 'stack', pushing and popping tokens onto the thread and removing them after access operations are completed.

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 beena acquired with TOKEN_IMPERSONATE access.

If impersonation fails GetLastError is thrown inside a wil::ResultException. as an HRESULT.

Impersonation is reverted when the returned object is destructed or .reset().

// Open the caller's log file
auto runAs = wil::impersonate_token(m_callerToken.get());
wil::unique_hfile log { ::CreateFile( ..., FILE_READ, ... ) };

// Go back to running as us - the log file handle still has 'read' access
runAs.reset();
::ReadFile( log.get(), ... );

Method Variants

A nonthrowing version takes the "undo" 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, ...) };
    ::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 by using a stack/heap allocated structure. It's convenient when the SID is fixed and 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, ...));

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

As a sample this method checks to see if 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);
Clone this wiki locally