3.2.4 Token Creation

Three operations create tokens, each for a different purpose: CreateToken mints one from nothing, DuplicateToken copies one, and FilterToken produces a strictly weaker copy.

3.2.4.1 CreateToken #

Mints a new token from scratch. The caller supplies the security-meaningful content; the kernel generates the internal bookkeeping and validates the structural invariants. The operation is gated by SeCreateTokenPrivilege.

The caller supplies user_sid, groups with their attributes, privileges as privs_present plus privs_enabled, owner_sid_index, primary_group_index, default_dacl, integrity_level, mandatory_policy, token_type, impersonation_level, auth_id referencing an existing LogonSession, expiration (0 for none), audit_policy, source as a name plus LUID, user_claims, device_claims, lcs_scope_guids, lcs_private_layers, device_groups, restricted_sids, restricted_device_groups, confinement_sid, confinement_capabilities, confinement_exempt, isolation_boundary, write_restricted, user_deny_only, projected_uid, projected_gid, projected_supplementary_gids, origin, and interactivity_scope. The wire format is in §3.A.

Including the well-known implicit groups is the caller's responsibility — Everyone (S-1-1-0), Authenticated Users (S-1-5-11), and whatever else the principal's authentication context implies, such as S-1-5-4 Interactive, S-1-5-6 Service, or S-1-5-15 This Organization. The kernel injects none of these. The logon SID is the sole kernel-generated group.

The kernel generates token_id as a LUID, token_guid, modified_id initialised to token_id, created_at as the current time, elevation_type always Default, the token's own default security descriptor (§3.2.7), and logon_sid, derived from the LogonSession ID as S-1-5-5-{id >> 32}-{id & 0xFFFFFFFF}.

The logon SID is injected into the groups array carrying SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED | SE_GROUP_LOGON_ID, appended after the caller's groups. Callers do not include it themselves. Because the injected entry is appended, owner_sid_index and primary_group_index are interpreted relative to the caller-supplied groups — 0 for the user SID, 1..N for the caller's groups — and not against the array with the logon SID in it.

Validation covers, in turn: that the caller holds SeCreateTokenPrivilege; that every SID is structurally well-formed; that the owner SID is the user SID or a group carrying SE_GROUP_OWNER, resolved to owner_sid_index; that the primary group SID is the user SID or a group on the token, resolved to primary_group_index; that auth_id references an existing LogonSession; that a Primary token carries impersonation level Anonymous; that user_deny_only is true whenever write_restricted is; that confinement_sid is present whenever isolation_boundary is; that the wire format's elevation_type field is 0, since the kernel always sets Default itself; and that the caller's group count plus the injected logon SID fits the 1024-entry limit.

The optional LCS registry credential extension, when present, has to use the version and layout of §3.A, and carries at most 256 scope GUIDs and at most 256 private layer names, with no nil scope GUID, no duplicate scope GUIDs, no empty or overlong layer names, and no duplicate layer names under LCS case-insensitive matching. Malformed LCS credentials fail the whole call closed.

The kernel does not authenticate the user, look up SIDs in the directory, resolve SID-to-UID mappings, or check that the principal exists at all. Holding SeCreateTokenPrivilege is what makes the caller trusted, and that trust is total.

The call returns a token file descriptor. Since CreateToken takes no desired-access parameter, the returned handle always carries a cached access mask of TOKEN_ALL_ACCESS.

3.2.4.2 DuplicateToken #

Creates an independent copy of an existing token, requiring TOKEN_DUPLICATE access on the source.

Two things may change during duplication. The token type may go from primary to impersonation or the reverse; duplicating to Primary forces impersonation_level to Anonymous. The impersonation level may be chosen freely when the source is a Primary token, but when the source is itself an impersonation token the new level has to be equal to or lower than the source's — an Identification-level token cannot be duplicated up to Impersonation or Delegation.

On the new token, token_id and token_guid are fresh, modified_id is initialised to the new token_id, and elevation_type resets to Default because the copy belongs to no linked pair. token_type and impersonation_level are as the caller specified, within the rules above. The token's own descriptor is a fresh default (§3.2.7): no custom descriptor can be supplied at duplication time, and changing it afterwards means using WRITE_DAC on the new handle.

Everything else is copied from the source: user_sid, user_deny_only, logon_sid; groups with all per-group attributes; restricted_sids and write_restricted; the privilege present, enabled, enabled-by-default and used states; integrity_level and mandatory_policy; auth_id, origin, source, created_at, expiration, audit_policy; interactivity_scope; default_dacl, owner_sid_index, primary_group_index; user_claims, device_claims, device_groups, restricted_device_groups; lcs_scope_guids and lcs_private_layers; confinement_sid, confinement_capabilities, confinement_exempt; and the three projection fields.

One target is not a copy at all. Duplicating to Impersonation at Anonymous level discards the source entirely and returns a fresh token of the boot Anonymous shape — user SID S-1-5-7, Everyone as its only group, no privileges, Untrusted integrity, and LogonSession 998 rather than the source's. None of the copied-field rules above apply to it. Assuming Anonymous is an identity boundary rather than a level change, so the operation constructs the minimal identity instead of narrowing the caller's.

The original token is unaffected.

3.2.4.3 FilterToken #

Creates a restricted copy, requiring TOKEN_DUPLICATE access on the source. Filtering only ever weakens: there is no parameter that grants anything.

It can remove privileges, deleting them permanently from the new token by clearing them from the present, enabled, and enabled-by-default states at once. It can set groups to deny-only, giving them SE_GROUP_USE_FOR_DENY_ONLY so they block access through deny ACEs but never grant it through allow ACEs — permanently, with no way back. It can add restricted SIDs, a secondary list that makes AccessCheck evaluate the DACL twice, granting access only when the normal SIDs and the restricted SIDs independently both pass. And it can enable write-restricted mode, limiting that second evaluation to write operations so reads use the normal list alone; enabling it forces user_deny_only true on the new token.

Input validation is all-or-nothing — a single malformed entry means no token is created. The deny-only list uses zero-based group indices into the source's group array, and a duplicate or out-of-range index is invalid. The restricting SID blob has to parse exactly as the declared packed SID list, with no truncated or trailing bytes. If the source token is already restricted and the intersection of its restricted SID list with the supplied list is empty, the request is invalid and nothing is created.

On the new token, token_id and token_guid are fresh, modified_id is initialised to the new token_id, and elevation_type resets to Default. The privilege states are the source's modified by the removal list, except that used resets to 0 — unlike duplication, which carries it across. groups keeps the source's SIDs with attributes modified per the deny-only list, adding and removing nothing. restricted_sids is the supplied list, or its intersection with the source's when the source was already restricted. write_restricted is sticky: set if requested or if the source had it. user_deny_only is true when write-restricted is enabled and otherwise copied. user_sid, logon_sid, integrity_level, mandatory_policy, token_type and impersonation_level are copied, as are auth_id, origin, source, created_at, expiration, audit_policy, default_dacl, owner_sid_index, primary_group_index, the claims and device group arrays, the LCS credentials, the confinement fields, and the projection fields. The token's descriptor is a fresh default.

Edit this page