4.6 The audit context
A daemon that guards objects of its own (a service manager's services, an event store's namespaces) makes each access decision through peios_access_check. The kernel records that decision in a kacs.audit.access.checked event when the descriptor's SACL asks for one, but the kernel cannot tell what the descriptor belongs to. The audit context tells it. It is the request's audit_context field: one MessagePack map holding kind and, under a key equal to the kind, the fields that identify the object.
{kind: "service", service: {name: "jellyfin"}}
The kernel copies it into the record as object.kind and object.service.name, and marks the record fields.attestation.userspace, because the kernel did not observe those values: they are the daemon's claim. It refuses any other shape with EINVAL. PGSS §6.7 is the specification. A component that guards objects must pass one, and each field it names must be defined, as asserted, in that component's catalogue fragment.
4.6.1 Encoding one #
;
;
ssize_t ;
peios_audit_context_encode writes the map for an object of kind kind, identified by count fields, using the two-call protocol: pass cap == 0 to learn the size. With no fields (fields may then be NULL), the context is {kind: <kind>}.
kind and every key must be a single segment of an event name: kebab-case, [a-z][a-z0-9]*(-[a-z0-9]+)*, so service, log-namespace or unit-name. A value is a scalar in one of the event wire forms: a string, an unsigned or signed integer, a boolean, or binary. A SID goes in as PEIOS_AUDIT_BIN with its binary bytes, never as SDDL text. There is no nil: a field with no value is left out of the array.
struct peios_audit_field fields = ;
uint8_t ctx;
ssize_t n = ;
if
struct peios_access_request req = ;
uint32_t granted = 0;
int rc = ;
The encoder fails with EINVAL when:
- the kind or a key is not a segment, or a key is
NULL; - a key appears twice;
value_typeis not one of the five, or a boolean'sscalaris not 0 or 1;- a string is not valid UTF-8, or
bytesisNULLwhilelenis not zero; - the encoded map would be longer than
KACS_ACCESS_CHECK_MAX_AUDIT_CONTEXT_LEN(4096 bytes).
The kernel itself allows a nil value, a nested value, or a key that appears twice inside the body. The encoder refuses all three. A field with no value is left out (PGSS §6.5), an identifying field is a scalar, and a repeated key would make object.<kind>.<key> ambiguous.
4.6.2 Validating one built another way #
int ;
If you build the map yourself, for example with the msgpack writer, peios_audit_context_validate checks it against the kernel's own rules. It returns 0 if the kernel would accept it, and -1 with EINVAL otherwise, including for NULL or empty input. The rules are:
- the top level is a map holding a string
kindthat is one segment, and at most one other key; - that key, if present, equals the kind, and its value is a non-empty map whose keys are segments;
- nothing follows the map;
- the bytes are well-formed MessagePack (UTF-8 strings, no reserved
0xc1byte), nested no deeper than 31 levels. That is the event nesting limit less one, because the context lands one level below the record's root; - the map is at most 4096 bytes.
peios_access_check and peios_access_check_list apply the same check to any non-NULL audit_context before they make the syscall. A malformed context therefore fails there with EINVAL, without reaching the kernel. A non-NULL pointer with a zero length is refused too, as the kernel refuses it.
4.6.3 From Rust and Go #
The peios crate builds the same map with an AuditContext and hands it to the AccessCheck builder. Field values convert from &str, the integer types, bool, &[u8] and &SidRef:
use ;
let ctx = new?;
let decision = new
.token
.audit_context
.check?;
AuditContext::new fails with EINVAL for the same reasons as the C encoder. AuditContext::from_bytes adopts a map you encoded yourself, after validating it.
In Go, libp-go's sd package encodes the map for CheckRequest.AuditContext:
ctx, err := sd.EncodeAuditContext("service", sd.AuditString("name", "jellyfin"))
if err != nil
dec, err := sd.Check(sd.CheckRequest)
The field constructors are AuditString, AuditUint, AuditInt, AuditBool, AuditBytes and AuditSID. A refusal wraps sd.ErrBadAuditContext. sd.ValidateAuditContext checks a map built by hand, and Check and CheckList run that check on a non-empty AuditContext before the syscall.