3.5.3 Impersonation Lifecycle
3.5.3.1 The sequence #
The client connects. It optionally sets the maximum impersonation
level on the socket with setsockopt(SOL_KACS, KACS_SO_IMPERSONATION_LEVEL) — the default is Impersonation; the
option may be changed at any time and bounds every capture made from
then on — and calls connect(). The kernel's LSM hook fires on the
Unix stream connection.
Identity is captured, both ways. The hook examines the client
thread's effective credential together with the socket's maximum
level, derives the identity at the lower of that level and the
effective token's own (§3.5.1), and stores the result on the accepted
end. It also stores, on
the connecting end, the identity the listener captured when listen()
was called — so a client can read KACS_SO_PEER_TOKEN on its own
socket immediately after connect() and verify who it reached, at
Identification level unless the listener set its own level. The
listener's identity is what listen() saw; a process that inherits a
listener it did not create (from a descriptor store after a restart,
or from a broker) replaces it with its own using setsockopt(SOL_KACS, KACS_SO_RESTAMP), which is self-gated and fails with EINVAL on a
socket that is not listening. Clients that connect before the restamp
see the previous holder, which is honest: that is who was listening.
Squatting a name therefore buys nothing against a client that checks —
the squatter's own identity is what it conveys. At
Anonymous, a token whose user SID is S-1-5-7, whose enabled groups
include Everyone, and which does not carry Authenticated Users is
stored on the socket's LSM blob, and the client's real identity is
never recorded. At Impersonation or Delegation, the thread's effective
token is stored — and if the connecting thread is itself
impersonating, the impersonated identity is what flows through, which
is how identity cascades across local services. At Identification, the
effective token is stored but tagged at that level.
The server reads the peer token with getsockopt(SOL_KACS, KACS_SO_PEER_TOKEN) on the accepted connection, which opens a token fd
carrying TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE for the
identity in that
end's conveyed-identity register — at this point, the identity captured
at connect (see Per-message identity below for how the register
moves).
It impersonates by installing that fd with KACS_IOC_IMPERSONATE:
the kernel evaluates both gates against the server thread's primary
token, computes the effective level, and constructs a new credential
carrying the impersonation token at that level. libpeios fuses the
two steps as peios_token_impersonate_peer for the handler that
impersonates, works and reverts on one thread.
Access control follows the impersonation token. Every subsequent AccessCheck on the thread evaluates it, and MIC uses its integrity level. PIP continues to read the PSB, unchanged.
The server reverts with kacs_revert(), restoring the thread's
credential to real_cred and its service identity with it.
3.5.3.2 Anonymous #
Any thread may impersonate the Anonymous identity without passing
either gate. Assuming Anonymous is always a downgrade — the token has
no access beyond what is explicitly granted to S-1-5-7 or Everyone —
so no privilege is needed, no identity gate runs, and no integrity
ceiling applies.
Anonymous tokens carry the Anonymous SID as the user SID, no privileges, and Untrusted integrity. The socket path constructs that minimal shape rather than preserving any part of the caller's real identity.
3.5.3.3 Double impersonation #
A thread already impersonating that installs another token with
KACS_IOC_IMPERSONATE causes the kernel to revert internally and then
re-impersonate.
Because the gates are evaluated against the primary token, the
previous impersonation has no bearing on the new one.
3.5.3.4 Interaction with MIC and PIP #
MIC reads the effective token for tokens permitted to act, which is safe precisely because the integrity ceiling makes acting impersonation safe. A thread cannot act at Impersonation or Delegation level with a client token whose integrity exceeds the server primary token's. When the ceiling caps the level to Identification, the installed token may still preserve the client's literal label as metadata, but it authorizes no resource access because Identification-level tokens are barred from AccessCheck. An acting impersonation token therefore preserves or lowers the server's integrity, and a higher literal label can only ever exist on a non-acting Identification-level token.
PIP reads the PSB, because there is no equivalent ceiling for it.
PIP operates on pip_type and pip_trust, which are orthogonal to
integrity level. Reading them from the effective token would let a
process impersonate a token carrying higher PIP values and acquire
protection it has not earned.
3.5.3.5 SeImpersonatePrivilege #
The privilege permits a service to impersonate arbitrary clients — those with different user SIDs. Without it a process can impersonate only tokens matching its own user SID and restriction status.
It is checked against the server's primary token, so a thread already impersonating one client is judged on its real service identity. It has to be enabled at the moment of the call. And it bypasses the identity gate only, never the integrity ceiling.
3.5.3.6 Delegation and the network boundary #
Locally, Impersonation and Delegation behave identically. The distinction activates at the network boundary, where a Delegation-level token carries authorization for Kerberos credential forwarding to services on other machines.
KACS tracks the level on the token and the socket, and authd checks it when it needs to perform Kerberos authentication for an impersonating thread. KACS itself has no Kerberos awareness and no network awareness: the level is a flag that authd interprets.
3.5.3.7 Per-message identity #
The connect-time capture is the first entry in a per-end
conveyed-identity register: the peer identity associated with the
data that end has consumed so far. Identity can also travel with the
data itself, as a KACS_SCM_TOKEN ancillary message under the
SOL_KACS level, and each such message the reader's position passes
advances the register. The anchor is the read position, never arrival:
a token still queued behind unread data is not yet visible, so a
handler can never be told an identity for a request it has not read.
KACS_SO_PEER_TOKEN returns a fresh fd to an immutable snapshot each
time; two calls may name different tokens, but a token never changes.
Sending. A sender conveys identity in one of two ways, and the receiver cannot tell them apart, because both mean the same thing — the kernel verified that the sender could be this identity when it sent these bytes:
- Automatically, with
KACS_SO_PASS_TOKENset on the sending end: every send carries the sender's effective token, derived at the lower of the end's impersonation level and the token's own. The derivation is cached against the effective token it was made from, so a run of sends under one identity costs one derivation. A thread that impersonates client A, writes to a pooled connection, reverts and writes again conveys A for the first write and its own identity for the second, with no change to the library doing the writing. - Explicitly, by attaching a token fd in a
KACS_SCM_TOKENcmsg. The kernel gates the attach exactly as it would gate impersonating that token: the fd must carryTOKEN_IMPERSONATE, a primary token is first derived to an impersonation token at the lower of the end's level and its own, and the two-gate model runs with the sender as installer. An attach the gates would cap below the token's own level fails withEPERM— an explicit act fails loudly where an install would silently downgrade. Attaching a token is therefore never more than impersonating it, sending, and reverting; it just avoids the install. A router forwarding on behalf of many clients re-attaches each client's token to the messages it forwards, and each downstream receiver gets a kernel-verified identity through the router.
Anonymous-level tokens attach without a gate, as assuming Anonymous is always a downgrade. At most one token per message.
Receiving. Every skb a send produces carries the conveyed token, so
a large stream write conveys the same identity on all of its
fragments. A stream read stops at an identity boundary rather than
gluing bytes conveyed under different identities into one return,
which keeps the register exact even on a byte stream; on
SOCK_SEQPACKET one send is one message and one identity, which is
why it is the recommended type for Peios-native protocols. As the
reader's position passes conveyed data, the register moves to that
identity. MSG_PEEK neither advances the register nor consumes; a
read() or splice() that consumes data advances it just as
recvmsg does, because the register is kept at the point of
consumption, not where ancillary data is copied out.
A KACS_SCM_TOKEN cmsg — carrying a fresh TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE fd — is delivered with a read when
the receive call
supplied room for it and the read's identity differs from the register
the reader had before the read; otherwise nothing is delivered, and
MSG_CTRUNC is set only when a token was due and no room was
supplied. A handler that never reads ancillary data still has the
register: recvmsg, then getsockopt(KACS_SO_PEER_TOKEN), gives the
identity of what it just read. Parsing the cmsg is the path for
pipelined or concurrent consumers that need the token bound to a
particular message.
SOCK_DGRAM sockets carry no register — many senders share one
receiving socket — so a datagram's token is delivered with every
datagram that carries one and is never retained.
3.5.3.8 Supported transports #
Peer-token capture works on two AF_UNIX socket types. SOCK_STREAM
is the connection-oriented byte stream, and SOCK_SEQPACKET is
connection-oriented with message boundaries and uses the same identity
capture model. Both follow the same lifecycle: capture at connect(),
read with KACS_SO_PEER_TOKEN, impersonate, revert — with per-message
identity layered on top as described above. SOCK_DGRAM supports
per-message identity only: KACS_SO_PASS_TOKEN and KACS_SCM_TOKEN
work, since a send needs no connection, and
KACS_SO_IMPERSONATION_LEVEL bounds them as on any socket, but there
is no register.
Where the register does not apply, KACS_SO_PEER_TOKEN reports which
case it is. On a datagram socket, or any non-Unix family, the option
fails with EOPNOTSUPP. A socketpair() end is connected but starts
with an empty register — no connect happened to capture anything — so
the option fails with ENODATA until the peer conveys an identity,
after which the register works as on any connection. A socket that is
not yet connected fails with ENOTCONN. Pipes and FIFOs are not
sockets at all, so getsockopt itself fails with ENOTSOCK before
KACS is reached.
For all of these, the universal fallback is explicit token fd
impersonation through KACS_IOC_IMPERSONATE, which works regardless
of how the token fd was obtained — the peer-token option, an
SCM_RIGHTS transfer, or any other path.
3.5.3.9 Audit records #
Every KACS_IOC_IMPERSONATE writes a kacs.impersonation.started
record through KMES (§3.C). Its subject is the server's token, the one
that acted, and its object is the client token: object.token.sid,
.type, .integrity, .auth-id and .restricted, with the level the
client token offered as object.token.impersonation and the level the
gate allowed as object.token.impersonation-permitted. The thread that
took the identity on is emitter.thread.tid, since impersonation is per
thread. Where the gate needed SeImpersonatePrivilege and used it, the
record carries privilege.name and privilege.held.
A level the gate lowered is not a failure: the record says
outcome.success with impersonation-permitted below
impersonation. A lowered level installs a new token, and the record's
object.token.guid and object.token.id are always the installed
token's — the client's own when the level was not lowered, the new one
when it was — so they match the header of the records the thread writes
next.
A refused impersonation is recorded too, with outcome.errno and, where
the kernel knows why, outcome.reason: primary-token for a primary
token (EINVAL), restriction-escape for a restricted server reaching
for an unrestricted token of its own user (EPERM),
no-impersonate-access for a handle without TOKEN_IMPERSONATE
(EACCES), and projection-failed when the identity could not be
projected onto the thread's credentials. A refusal before the gate
answered carries no impersonation-permitted.
Ending an impersonation writes kacs.impersonation.reverted, a
verbose type, so only where the emission policy (§2.8) switches it on.
The record names the
token given up in object.token.guid and object.token.sid, with the
thread's token after the revert as subject. operation.name says why:
revert for kacs_revert, exec for the revert every exec performs,
and replaced when a second impersonation ends the first. A revert with
no impersonation in force writes nothing, nor does the revert and
re-apply around a primary-token install, which leaves the impersonation
in place, nor does a thread that exits while impersonating.
Both records are best effort: a record that cannot be built does not
change the outcome of the impersonation or the revert.