These docs are under active development.
On this page
reference 7 min read

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 user and device 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:

  1. Validates the class against the catalog. Unknown classes return -EINVAL.
  2. Checks that the fd grants TOKEN_QUERY. If not, returns -EACCES.
  3. Computes the size the response needs.
  4. If buf_ptr is zero or buf_len is zero — this is a size query — writes the required size to buf_len and returns 0.
  5. If buf_ptr is non-zero but buf_len is smaller than required, returns -ERANGE with the required size still written to buf_len.
  6. 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:

  1. Call once with buf_ptr = NULL (or buf_len = 0). The kernel writes the required size into buf_len and returns 0.
  2. Allocate a buffer of the indicated size.
  3. Call again with buf_ptr set to the buffer and buf_len set to its size. The kernel writes the response.

For classes with a fixed-size response, 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 restricted-SIDs class, the default-DACL 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 four privilege bitmasks — present, enabled, enabled-by-default, used.
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 (Primary tokens return Anonymous).
TokenStatistics token_id, auth_id (the logon-session ID), modified_id, token type, and expiry.
TokenRestrictedSids The restricted_sids array. Variable length.
TokenSessionId The interactive session ID.
TokenOrigin The originating logon-session ID.
TokenElevationType Default / Full / Limited.
TokenIntegrityLevel The integrity SID.
TokenMandatoryPolicy The mandatory_policy flags (NO_WRITE_UP, NEW_PROCESS_MIN).
TokenLogonType How the token's logon session was created — interactive, network, batch, service, and so on.
TokenLogonSid The logon session's logon SID (S-1-5-5-X-Y).
TokenAppContainerSid The confinement SID. Empty if the token is not confined.
TokenCapabilities The confinement capability SIDs with their attributes. Variable length.

The remaining classes are TokenDeviceGroups (the device group SIDs), TokenUserClaims and TokenDeviceClaims (the claim arrays evaluated by conditional ACEs), and TokenProjectedSupplementaryGids (the token's projected Linux supplementary GIDs). Note there is no query class for the partner of a linked token pair — that goes through a separate ioctl, KACS_IOC_GET_LINKED_TOKEN, with its own access rules.

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/*/token to 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_QUERY lets 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 undefined classes. Class numbers outside the defined range (1–24) return -EINVAL. There are no hidden or reserved slots — all 24 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.