On this page
Inspecting tokens
A token's fields are read through the KACS_IOC_QUERY ioctl on a token fd. The ioctl takes a query class — a small integer naming what to return — and returns the corresponding data structured per that class. There are 24 defined classes covering everything from "the user SID" to "the full set of resource attribute claims".
This page covers how to obtain a token fd, the query ioctl mechanics, the two-call pattern for queries with variable-length output, and an overview of the class catalog.
Obtaining a token fd
Token fds come from a handful of syscalls and pseudo-files:
| Source | Returns | Access check |
|---|---|---|
kacs_open_self_token |
The calling thread's effective token (or primary, with KACS_REAL_TOKEN flag) |
None — always succeeds |
kacs_open_process_token(pidfd) |
A target process's primary token | PROCESS_QUERY_INFORMATION + PIP dominance + token SD rights |
kacs_open_thread_token(tid) |
A specific thread's effective token | Same as above |
kacs_open_peer_token(sock_fd) |
The peer's captured identity on a connected Unix socket | None beyond the connection itself |
/proc/<pid>/token |
The primary token of process <pid> |
PROCESS_QUERY_INFORMATION + PIP dominance |
/proc/<pid>/task/<tid>/token |
The effective token of thread <tid> in process <pid> |
Same |
/sys/kernel/security/kacs/self |
The calling thread's effective token | None — always readable |
The fds carry an access mask. The mask is what the kernel granted at open time and what the subsequent ioctl will check against. A fd opened with TOKEN_QUERY cannot be used to install or duplicate the token; the ioctl will see the request as exceeding the fd's mask and refuse.
The pseudo-files under /proc and /sys/kernel/security/kacs/ return read-only fds — they carry TOKEN_QUERY and nothing else. To get a fd with more access you need one of the syscalls.
KACS_IOC_QUERY
The ioctl is straightforward in shape:
ioctl(token_fd, KACS_IOC_QUERY, &args)
Where args is a kacs_query_args struct:
| Field | Meaning |
|---|---|
token_class |
The numeric class identifying what to return (1–24 in v0.20). |
buf_len |
Input: the size of the output buffer in bytes. Output: the actual number of bytes the query needed. |
buf_ptr |
Userspace pointer to the output buffer. |
The kernel:
- Validates the class against the catalog. Unknown classes return
-EINVAL. - Checks that the fd grants
TOKEN_QUERY. If not, returns-EACCES. - Computes the size the response needs.
- If
buf_ptris zero orbuf_lenis zero — this is a size query — writes the required size tobuf_lenand returns 0. - If
buf_ptris non-zero butbuf_lenis smaller than required, returns-ERANGEwith the required size still written tobuf_len. - Otherwise writes the response to the buffer and returns 0.
The "two-call pattern" — size query then fetch — is the standard way to handle variable-length output:
- Call once with
buf_ptr = NULL(orbuf_len = 0). The kernel writes the required size intobuf_lenand returns 0. - Allocate a buffer of the indicated size.
- Call again with
buf_ptrset to the buffer andbuf_lenset to its size. The kernel writes the response.
For classes with a fixed-size response (most of them), a single call with a buffer of the known size works in one go. The two-call pattern is needed only for classes whose response size depends on the token's contents (the groups class, the privileges class, the claims classes).
The ioctl is idempotent — multiple queries for the same class produce the same result as long as the token has not been modified. Tokens carry a modified_id counter that increments on adjustment; if a query is part of a pipeline that depends on consistency across multiple queries, the modified_id can be queried first to detect mid-pipeline changes.
Query class catalog
There are 24 defined query classes. Each returns a structured payload defined for that class. The most commonly used:
| Class | Returns |
|---|---|
TokenUser |
The token's user_sid and its attributes. |
TokenGroups |
The groups array — every group SID with its attributes. Variable length. |
TokenPrivileges |
The privilege bitmask — present, enabled, used, enabled_by_default. |
TokenOwner |
The default owner SID. |
TokenPrimaryGroup |
The default primary group SID. |
TokenDefaultDacl |
The token's default DACL. Variable length. |
TokenSource |
The source name and source-LUID identifying who minted the token. |
TokenType |
Primary or Impersonation. |
TokenImpersonationLevel |
Anonymous / Identification / Impersonation / Delegation (only meaningful for Impersonation tokens). |
TokenStatistics |
token_id, auth_id, modified_id, creation timestamp, expiry, dynamic charge, and so on. |
TokenRestrictedSids |
The restricted_sids array. Variable length. |
TokenSessionId |
The interactive session ID. |
TokenGroupsAndPrivileges |
A combined version of TokenGroups and TokenPrivileges for performance. Variable length. |
TokenSessionReference |
The session reference (a token holds onto its session via this; less commonly inspected). |
TokenSandBoxInert |
(Reserved / future use.) |
TokenAuditPolicy |
The token's audit_policy bitmask. |
TokenOrigin |
The originating session LUID for derived tokens. |
TokenElevationType |
Default / Full / Limited. |
TokenLinkedToken |
If part of a linked pair, the partner. (Returns a token fd; subject to additional access rules.) |
TokenElevation |
A simplified "is this elevated" query. |
TokenHasRestrictions |
A boolean: is this a restricted token? |
TokenIntegrityLevel |
The integrity SID. |
TokenUiAccess |
The UI-access flag. (Reserved.) |
TokenMandatoryPolicy |
The mandatory_policy flags (NO_WRITE_UP, NEW_PROCESS_MIN). |
This is the full v0.20 list. Each class's exact byte-level payload format is in the Wire formats reference; this page covers what each class is for.
Patterns by use case
A handful of patterns come up repeatedly:
"Who is this thread acting as?" Open the thread's effective token (/proc/<pid>/task/<tid>/token or kacs_open_self_token). Query TokenUser to get the principal SID. Optionally query TokenImpersonationLevel to see if this is an impersonation token, and what level.
"What rights does this token have on this object?" This is not a query — you call AccessCheck with the token, the object's SD, and the access mask you want to test. Querying the token alone does not tell you the answer; the rights depend on the SD too.
"Which session does this token belong to?" Query TokenStatistics to get auth_id. Look up that ID in /sys/kernel/security/kacs/sessions for the session's details.
"Is this token elevated?" Query TokenElevationType. If Full, this token is the elevated half of a linked pair. If Default, it is not part of a pair. If Limited, it is the non-elevated half — the elevated counterpart is reachable via KACS_IOC_GET_LINKED_TOKEN.
"What privileges can this token actually exercise?" Query TokenPrivileges and inspect both the present and enabled bitmasks. A privilege is exercisable if it is both present and enabled. A privilege that is present but disabled can be enabled via AdjustPrivileges; a privilege that is absent cannot.
"Has this token been adjusted since I last looked?" Query TokenStatistics. The modified_id field is a counter that increments on every adjustment. If it has changed since your last query, the token has been adjusted.
What query classes do not let you do
A few clarifications:
- You cannot modify a token through a query class. Queries are read-only. Modification goes through AdjustPrivileges, AdjustGroups, AdjustDefault, or
kacs_set_sd. - You cannot enumerate every token on the system. There is no "list all tokens" call. You can walk
/proc/*/tokento find tokens belonging to currently-running processes, but tokens held only by file descriptors with no associated running process are not enumerable. - You cannot read tokens you do not have authority for. A token fd with only
TOKEN_QUERYlets you query, but the fd had to be opened with appropriate authority. The query ioctl does not bypass the access checks at open time. - You cannot query classes that are reserved. A class number reserved for future use (some of the higher-numbered slots) returns
-EINVAL. Only the defined classes are valid.
Reading from the shell
For a sysadmin debugging at a terminal, the token command is the utility that wraps this ioctl. It handles the two-call pattern, decodes the binary payloads, and renders the results as readable text — so the query classes above become token subcommands rather than raw ioctl calls.
For programmatic use, the ioctl is what you call directly. Language bindings (the C SDK, the Python wrapper) provide ergonomic wrappers but ultimately call the same ioctl.
The pseudo-file approach — /proc/<pid>/token, /sys/kernel/security/kacs/self — gives you the token fd; the actual query still goes through the ioctl. Pseudo-files are just a convenient way to acquire the fd from the shell.