The documented public contract is insufficient to treat a class-38 failure as emptiness or to prove lossless restricted-device-group preservation across distinct tokens; the application must reject that state or obtain a class-specific support statement from Microsoft.
Status of TokenRestrictedDeviceGroups
Microsoft’s two API surfaces conflict:
- The Win32
TOKEN_INFORMATION_CLASSdocumentation saysTokenRestrictedDeviceGroupsreturns aTOKEN_GROUPScontaining the token’s restricted device groups and associates the enumeration withGetTokenInformation.^1^ - The WDK/native enumeration says the same class is reserved for system use.^2^
The available documentation does not resolve:
- Supported Windows versions or builds for this particular query
- Applicable token types or token states
- Whether a
NULLsizing call is supported for this class - The representation of an empty result
- Class-specific failure conditions
The general GetTokenInformation contract requires TOKEN_QUERY for every class except TokenSource, which requires TOKEN_QUERY_SOURCE. It also says that if the output pointer is NULL, its length must be zero—but that requirement does not guarantee that every information class supports a NULL sizing probe.^3^
Therefore, TOKEN_QUERY is the documented general access right, but there is no documented class-38 contract establishing additional rights, token prerequisites, versions, or reliable buffer behavior.
Meaning of error 87
ERROR_INVALID_PARAMETER means only “The parameter is incorrect.” Microsoft notes that system error codes are generic and must be interpreted in the context of the API that returned them.^4^
No cited GetTokenInformation documentation defines error 87 specifically for class 38. Consequently, it does not establish any of these conditions:
- The collection is empty
- The token has no restricted device groups
- The buffer is too small
- The token type is inapplicable
- Class 38 is unsupported
At the native interface, invalid or unavailable information classes can produce STATUS_INVALID_INFO_CLASS, while insufficient storage is separately represented by STATUS_BUFFER_TOO_SMALL with the required length returned.^5^ That does not document how every native status is surfaced through the Win32 class-38 call, so error 87 cannot safely be reverse-mapped to one specific cause.
Buffer implications
A fixed 24-byte buffer is not a general TOKEN_GROUPS buffer. TOKEN_GROUPS contains a count followed by a variable-length array of SID_AND_ATTRIBUTES; each entry contains a SID pointer and attributes.^6^ A successful self-contained result may require storage for the structure, all array entries, alignment/padding, and SID bodies.
The native NtQueryInformationToken documentation requires 32-bit alignment for its output structures.^5^ That alignment statement is documented for the native function and should not be treated as an additional class-specific Win32 guarantee.
Because the initial zero-length call already returns error 87 without a documented required length, allocating a larger speculative buffer cannot establish support or emptiness.
No documented lossless substitute in these APIs
The documented alternatives do not provide the requested proof:
-
TokenRestrictedSidsreports the restricting SID list of a restricted token; it is not documented as the restricted device-group list.^2^ -
TOKEN_ACCESS_INFORMATIONcontains information needed for access checks, including SID hashes, but is not documented as a lossless serialization of restricted device-group SIDs and attributes.^7^ -
NtCompareTokensproves equivalence with respect toAccessCheck; it does not promise byte-for-byte or attribute-for-attribute token identity.^8^ -
CompareObjectHandlesproves that two handles refer to the same kernel object, not that two distinct token objects have identical contents.^9^
The documentation supplied here identifies no public API that provides an independent, complete class-38 readback or exact comparison guarantee across three distinct token objects. That is not proof that no specialized Microsoft mechanism exists; it means the public contracts cited here do not establish one.
Supported fail-closed design
For narrowly scoped privilege removal:
- Create one private primary-token duplicate rather than modifying the caller’s shared token.
DuplicateTokenExsupports creation of a primary token.^10^ - Open/request the duplicate with at least
TOKEN_QUERY,TOKEN_DUPLICATE,TOKEN_ADJUST_PRIVILEGES, and the process-creation rights required later. - Snapshot
TokenPrivilegesusing the normal size-query/allocation/retrieval sequence. - Remove only the intended privileges with
AdjustTokenPrivileges. - Require:
- a nonzero function result;
-
GetLastError() == ERROR_SUCCESS, notERROR_NOT_ALL_ASSIGNED; - a successful post-operation
TokenPrivilegesquery; - exact expected before/after privilege differences.
Privilege removal is irreversible for that token, and AdjustTokenPrivileges cannot add privileges.^11^
- Launch the worker with that resulting primary token.
CreateProcessAsUserrequires the token handle to haveTOKEN_QUERY,TOKEN_DUPLICATE, andTOKEN_ASSIGN_PRIMARY.^12^ - Treat the privilege comparison as proving only the intended privilege mutation—not preservation of every token field.
- If exact restricted-device-group preservation is mandatory, abort whenever class 38 cannot be successfully and unambiguously queried. Do not substitute error 87,
NtCompareTokens, access-check tests, or matching ordinary/restricted SID lists.
For a production contract requiring class-38 verification, open a Microsoft support request with the exact Windows build, architecture, token provenance, minimal reproduction, returned ReturnLength, and all three token-creation transitions. Request an authoritative statement covering class-38 availability, empty-result representation, buffer contract, and preservation semantics.
References
- TOKEN_INFORMATION_CLASS enumeration (winnt.h)
- TOKEN_INFORMATION_CLASS enumeration (ntifs.h)
- GetTokenInformation function (securitybaseapi.h)
- System Error Codes (0-499)
- NtQueryInformationToken function (ntifs.h)
- TOKEN_GROUPS structure (winnt.h)
- TOKEN_ACCESS_INFORMATION structure (winnt.h)
- NtCompareTokens function
- CompareObjectHandles function (handleapi.h)
- DuplicateTokenEx function (securitybaseapi.h)
- AdjustTokenPrivileges function (securitybaseapi.h)
- CreateProcessAsUserW function (processthreadsapi.h)