These docs are under active development.
On this page
How-to 3 min read

Checking access

This guide walks a complete access decision: you have an object to protect, a caller to check, and you want KACS to tell you what they're allowed. The exhaustive detail lives in access.h and security.h; here we put them together.

When to use this

Reach for peios_access_check when your program is the resource manager — you own something that isn't a kernel object (a document, an API route, a record) and you want to gate it with Peios identities and rules. For actual kernel objects, don't pre-check; just perform the operation and let the kernel enforce.

Remember the check is advisory: it computes the answer, you enforce it.

Step 1 — describe what protects the object

An object is protected by a security descriptor. If you don't already have one, build it. Say the resource should be readable and writable by its owner and read-only for a "viewers" group:

/* An ACL: allow OWNER full, allow VIEWERS read. */
peios_acl_builder *acl = peios_acl_builder_new();
peios_acl_builder_allow(acl, owner_sid, owner_len, KACS_ACCESS_ALL, 0);
peios_acl_builder_allow(acl, viewers_sid, viewers_len, KACS_ACCESS_READ, 0);

size_t acl_len;
const void *acl_bytes = peios_acl_builder_bytes(acl, &acl_len);

/* Wrap it in a security descriptor with an owner. */
peios_sd_builder *sd = peios_sd_builder_new();
peios_sd_builder_owner(sd, owner_sid, owner_len);
peios_sd_builder_dacl(sd, acl_bytes, acl_len);

size_t sd_len;
const void *sd_bytes = peios_sd_builder_bytes(sd, &sd_len);

(You can also write the descriptor as SDDL text and parse it — often easier when the policy is fixed.)

Step 2 — identify the subject

The subject is a token. Most often it's the caller you opened from a socket, or your own effective token. In the request you pass either a token fd or -1 for your own effective token.

Step 3 — run the check

Fill in a request and call. desired is what you're testing; mapping folds any generic rights to the object class's specific bits (use the class's published mapping — here we'll treat the object like a file):

struct peios_access_request req = {
    .token_fd = caller_fd,               /* or -1 for my own token */
    .sd       = sd_bytes, .sd_len = sd_len,
    .desired  = KACS_ACCESS_READ | KACS_ACCESS_WRITE,
    .mapping  = peios_file_generic_mapping,
};

uint32_t granted = 0;
int rc = peios_access_check(&req, &granted, NULL);

Step 4 — interpret the result

if (rc == 0) {
    /* Every desired right was granted. */
} else if (errno == EACCES) {
    /* Denied. `granted` still holds what WAS allowed — e.g. maybe READ
       succeeded but WRITE did not. Decide per-right from `granted`. */
    bool may_read  = granted & KACS_ACCESS_READ;
    bool may_write = granted & KACS_ACCESS_WRITE;
} else {
    /* A real error: bad token fd, malformed SD, etc. */
    perror("access_check");
}

The key idea: a denial is not an error to log and bail on — it's the expected "no". And because granted is filled even on denial, you can ask for a broad set of rights in one call and read back exactly which subset the subject has, rather than probing right by right. Clean up the builders (peios_acl_builder_free, peios_sd_builder_free) and the token fd when done.

Per-property checks

If your object has properties or property-sets with their own object ACEs, evaluate the whole tree in one call with peios_access_check_list: supply an object-type tree and get one result per node, so you learn (for instance) that a caller may read most of an object but not one protected field — without a separate check per field.

Auditing a decision

Pass a non-NULL peios_access_audit to learn what a SYSTEM_AUDIT ACE match would log, and whether a staged central access policy would decide differently — the signal you watch when rolling out a policy change.

Next

  • access.h reference — every field of the request and the audit outputs.
  • Access decisions — the operator-side account of how KACS reaches a verdict (and how to debug a surprising denial).