Events and transport

The audit layer is the part of the access check that generates events. Once an event has been produced, its further life is the transport layer's concern — getting the event from the kernel to whatever userspace consumer is interested. Peios uses KMES (Kernel Message Event Stream) for this. The kernel writes events to KMES; userspace daemons subscribe and receive them; eventually they are written to wherever the deployment's audit pipeline wants them.

This page covers the event schemas — what each event type contains — and the transport mechanics at a conceptual level.

The event types #

Five event types come out of the access-check layer and its surroundings:

Event typeWhen it firesFired by
access-auditAn SACL audit ACE matched, or a token audit_policy OBJECT_ACCESS_* flag forced itStep 14 and 14b of AccessCheck
continuous-auditAn operation on an open handle matched the handle's continuous audit maskThe kernel enforcement point for the operation (FACS for file handles; other kernel subsystems such as LCS for their own objects)
privilege-useA privilege contributed bits AND the token's audit_policy PRIVILEGE_USE_* requested itStep 13 of AccessCheck
caap-policy-diagnosticA CAAP SACL evaluation error, or a staged/effective result mismatchStep 12 of AccessCheck
logon-session-destroyedA logon session lost its last token referenceSession destruction path (independent of access check)

Each event is a self-describing record. Consumers do not need to know which check produced an event in order to parse it; the event itself carries enough metadata to identify itself and its context.

Event encoding #

All audit events are encoded as msgpack maps with UTF-8 string keys. msgpack is the binary serialisation format Peios uses across kernel/userspace boundaries; it produces compact records that are quick to parse and round-trip cleanly.

SIDs and ACEs appear as bin (binary blob) values in the msgpack — the same binary forms used in the kernel. UIDs, timestamps, and bitmasks appear as uint values. Strings appear as msgpack strings (UTF-8).

Consumers are expected to ignore unknown keys. Future kernel versions may add fields to event records without changing the existing ones. A consumer that processes only the fields it knows about and ignores the rest will continue to work across kernel upgrades.

The msgpack format and the key conventions are stable across the v0.20 line. The exact byte-level layout is documented in Wire formats reference; this page covers the logical structure.

The subject record #

A subject record appears in every event that involves a calling principal. It identifies the token under which the operation ran:

FieldTypeMeaning
user_sidbinThe token's user SID.
group_sidsarray of binThe token's group SIDs (with attributes, in the same SID_AND_ATTRIBUTES form the token holds).
integrity_leveluintThe token's integrity level (one of the well-known integrity RIDs).
pip_typeuintThe PIP type of the calling process.
pip_trustuintThe PIP trust level of the calling process.

For events that fire from inside an impersonating thread, the subject reflects the effective token — the impersonation token — not the primary. This is what the access check ran against, and it is what should be recorded.

For continuous-audit events specifically, the subject is the effective token at the moment of the operation, not at the moment the handle was opened. A process whose effective token has changed since opening the handle gets the up-to-date subject in each continuous-audit event.

The process record #

A process record identifies the kernel-level context the event came from:

FieldTypeMeaning
piduintProcess ID.
namestringProcess executable name.
executable_pathstringFull path to the executable.

These fields let a consumer correlate the event with a running (or recently-running) process. The pid is useful in the short term — the process may still exist when the consumer is processing the event. The name and path are useful for human-readable correlation and for long-term records where the pid may have been reused.

access-audit event schema #

The most common event type. Fired by step 14 (SACL audit walk) and step 14b (token audit_policy).

FieldTypeMeaning
subjectmapThe subject record.
object_contextbin or nilCaller-supplied opaque object identifier. nil if not provided.
requested_accessuintThe access mask the caller requested (after generic mapping).
granted_accessuintThe mask of rights actually granted.
successboolWhether the access succeeded (granted contains all of requested).
triggermapTrigger record — see below.
processmapThe process record.

The trigger map identifies why this event fired:

FieldTypeMeaning
kindstringEither "sacl" (a SACL audit ACE matched) or "policy" (token audit_policy forced it).
acebin or nilFor "sacl" triggers, the matched ACE bytes. For "policy" triggers, nil.

A consumer can use trigger.kind to filter: events from SACL ACEs versus events forced by token policy. The ace field for SACL triggers includes the bytes of the matched ACE so a consumer can reconstruct what specific rule produced the event.

continuous-audit event schema #

Fired per-operation on a handle whose continuous audit mask overlaps the operation's required mask.

FieldTypeMeaning
subjectmapThe subject record (operation-time effective token, not handle-open token).
object_contextbin or nilObject identifier, if retained by the enforcement point.
operationstringThe operation name. For FACS operations, prefixed with file. (e.g. file.read, file.write).
requested_accessuintThe required mask for this operation.
matched_accessuintThe subset of required_access that overlapped the handle's continuous audit mask.
granted_accessuintThe mask cached on the handle at the time of the operation.
successboolWhether the operation succeeded.
processmapThe process record.

The two access-mask fields require explanation:

  • requested_access is what this operation asks for. A read on a file requires read; an mmap requires execute or write depending on mode.
  • matched_access is the subset of requested_access that intersected the handle's continuous audit mask. The event fires because of this overlap; the mask is recorded so consumers know which alarm ACEs were relevant.
  • granted_access is the cached access mask on the handle from the original open. Operations can only succeed if their required mask is a subset of the cached granted mask, so this field tells consumers what the handle was actually authorised for.

privilege-use event schema #

Fired at step 13 of AccessCheck for each privilege that contributed bits.

FieldTypeMeaning
subjectmapThe subject record.
object_contextbin or nilObject identifier (the same one the access check was for).
privilegestringThe canonical privilege name (e.g. SeBackupPrivilege).
requested_accessuintThe bits the caller requested that this privilege might address.
granted_accessuintThe bits the privilege actually contributed.
surviving_accessuintThe subset of granted_access that survived to the final granted mask.
successboolTrue if surviving_access is non-empty (the privilege contributed bits that made it through).
processmapThe process record.

The three access masks tell the full story: what the caller wanted, what the privilege tried to grant, what actually survived. A successful privilege-use event has a non-empty surviving_access; a failed event has surviving_access == 0 — the privilege tried to grant bits but they were stripped.

logon-session-destroyed event schema #

Fired when a logon session loses its last token reference and is destroyed.

FieldTypeMeaning
session_iduintThe destroyed session's LUID.
user_sidbinThe user SID the session belonged to.
logon_typeuintThe session's logon type.
auth_packagestringThe auth-package name (e.g. "Kerberos", "NTLM", "local").
created_atuintThe session's creation timestamp.

No subject record (the subject was this session). No process record (the session is identified by its ID, not by any specific process).

The event lets userspace consumers — notably authd — release session-scoped state (Kerberos tickets, cached directory data, per-session credentials).

KMES transport #

The kernel does not directly write audit events to disk, send them over a network, or hand them to a specific userspace process. The kernel writes events to KMES — Kernel Message Event Stream — which is a per-subscriber ring buffer with kernel-side production and userspace-side consumption.

Conceptually:

  1. The kernel produces an event (e.g. an audit fires during step 14).
  2. The kernel writes the event into each subscriber's KMES buffer.
  3. Userspace consumers read from their KMES buffers when they are ready.
  4. Once a buffer is drained, the kernel can continue producing into it.

The buffers are per-subscriber. A subscriber that falls behind has its own buffer fill up; eventually KMES applies flow control to that buffer (typically dropping the oldest events). The kernel's production of events to other subscribers is unaffected.

The audit subsystem typically has at least one subscriber: eventd, the userspace audit daemon. eventd subscribes to the audit event types, reads them from KMES, and writes them to wherever the deployment's audit pipeline goes (a local file, a remote syslog, a SIEM collector). Other subscribers — debugging tools, real-time monitors — can also exist.

The exact KMES protocol, buffer sizes, flow-control mechanics, and reliability guarantees are outside the scope of this auditing topic; they are defined in PSPK §2, the kernel-boundary protocol standard in which KMES is the core abstraction. For auditing purposes, what matters is:

  • Events are not retained by the kernel beyond writing them to KMES.
  • A subscriber that falls behind may lose events.
  • Reliable persistence is the job of whoever drains the KMES buffer, not the kernel.

What consumers should look at #

For someone writing or operating an audit consumer:

  • Filter by event type. Most consumers care about specific types (just access-audit for compliance, just privilege-use for privilege monitoring, etc.). Filtering early reduces processing volume.
  • Correlate by subject + object_context. A consumer that wants to track "everything user X did to object Y" should index events by these two fields.
  • Use the trigger field to distinguish event sources. An access-audit with trigger "policy" was forced by token audit policy; one with trigger "sacl" came from a specific ACE. The distinction matters for some compliance scenarios.
  • Be tolerant of new fields. Future kernel versions may add fields to event records. Ignoring unknown keys is the rule; rejecting events with unfamiliar fields is a bug.
  • Plan for event loss. A KMES subscriber that falls behind loses events. Consumers that need lossless audit must implement their own persistence layer above KMES and handle backpressure appropriately.

What the audit transport does not provide #

A few clarifications:

  • No reliable delivery to the kernel's edge. Events written to KMES are best-effort; a crashing subscriber loses its buffer. The kernel does not block access checks on subscriber readiness.
  • No replay. A consumer that joins late, or one that loses its connection, cannot ask for old events. The events are produced once; missing them means missing them.
  • No event correlation across boots. Each boot starts fresh. Audit logs that span reboots are assembled by userspace persistence (eventd writing to disk, log aggregators collecting across systems).
  • No authentication of events at the consumer. Events are produced by the kernel and received by KMES subscribers. There is no signature on events; the trust model is "the kernel said this, so it is true". A subscriber that needs cryptographic non-repudiation of events would need to add a signing layer in userspace.

The model is built for performance and simplicity over absolute guarantees. For deployments that need stronger properties, the userspace audit pipeline is where additional layers belong — not in the kernel-to-KMES boundary.

See also #

  • Auditing — the model these events come from.
  • Audit ACEs — the SACL mechanisms behind access-audit and continuous-audit events.
  • Policy-forced auditing — the token policy behind privilege-use and forced object-access events.
  • Wire formats reference — byte-level encoding of the SIDs and ACEs carried in events.

Edit this page