# Access control

---

# Access control overview

_Peios / Developing for Peios / Access control_

> How the KACS pieces fit together from a developer's seat — identities, tokens, security descriptors, and access checks — and which part of the SDK you reach for.

KACS — the Kernel Access Control Subsystem — is the heart of Peios's security model, and it is the part of the SDK you are most likely to reach for. This section is a tour: it explains how the pieces fit together and points you at the right guide (and the right reference page) for each task. If you have not yet read the [library conventions](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md), read those first — everything here assumes the error and buffer rules they describe.

## The four nouns

Almost everything in KACS is built from four things:

| Noun | What it is | SDK home |
|---|---|---|
| **SID** | The unique binary name of a principal — a user, group, machine, or well-known system actor. | [`security.h`](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md#sids) |
| **Security descriptor (SD)** | What protects an object: its owner, its group, and the ACLs that grant or deny access. Built from ACEs, each naming a SID and an access mask. | [`security.h`](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md#building-security-descriptors) |
| **Token** | The runtime object that carries an identity — a user SID, groups, privileges, an integrity level, claims. Every access decision is made *against a token*. A token is a file descriptor. | [`token.h`](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md) |
| **Access check** | The act of deciding whether a token may perform a desired access on an object, given the object's SD. | [`access.h`](/peios/developing-for-peios/sdk-reference/sdk-access/access-h-access-checks.md) |

The relationship is simple to state: an **access check** asks whether a **token** (the subject) is granted some access to an object protected by a **security descriptor**, and both the token and the SD are expressed in terms of **SIDs**.

## The shape of a decision

When you need to make an authorisation decision in your own code, the pattern is almost always the same three steps:

1. **Get the subject's token.** Usually the caller's — often via [`peios_token_open_peer`](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md#opening-and-creating-tokens) on a socket, so you learn who connected — or your own effective token (`token_fd = -1`).
2. **Get the object's security descriptor.** You either hold it already, read it off a file with [`peios_file_get_sd`](/peios/developing-for-peios/sdk-reference/sdk-files/file-h-file-security.md), or build one with a [`peios_sd_builder`](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md#building-security-descriptors).
3. **Run the check.** [`peios_access_check`](/peios/developing-for-peios/sdk-reference/sdk-access/access-h-access-checks.md#the-check) tells you whether the desired rights are granted, and exactly which subset was granted.

The [checking access](/peios/developing-for-peios/sdk-access-control/checking-access.md) guide walks that end to end.

## Advisory versus enforced

There is one distinction worth internalising early. The SDK's access check is **advisory**: it computes what the answer *would* be. It does not *enforce* anything — enforcement of a real operation (opening a file, adjusting a token) happens inside the kernel against the subject's own process security block.

So you use `peios_access_check` when **your** code is the resource manager: you own some object — a record in your database, a slot in your service — that isn't a kernel object, and you want to make the grant/deny decision using Peios identities and the same rules the kernel would apply. For actual kernel objects (files, tokens, registry keys), you don't pre-check and then act; you just act, and the kernel enforces, returning `EACCES` if denied.

## Identity is not always *your* identity

A recurring theme in KACS is acting as someone other than yourself:

- **Impersonation** lets a service temporarily adopt a caller's identity so its access checks run as *them* — the way a server does work "on behalf of" a client without running as root and hand-rolling permission logic. See [working with tokens](/peios/developing-for-peios/sdk-access-control/working-with-tokens.md).
- **Restricted tokens** let you *drop* power — derive a strictly less-privileged token to hand to less-trusted code.
- **Integrity levels** and **confinement** bound what a token can touch regardless of its SIDs.

These are what make KACS more than POSIX uid/gid, and the token module is where you reach for them.

## Where to go in this section

- **[Working with tokens](/peios/developing-for-peios/sdk-access-control/working-with-tokens.md)** — who am I, who is calling me, and how to act as someone else.
- **[Checking access](/peios/developing-for-peios/sdk-access-control/checking-access.md)** — building a security descriptor and making a decision end to end.
- **[Securing files](/peios/developing-for-peios/sdk-access-control/securing-files.md)** — the native open and reading/writing file security descriptors.
- **[Hardening a process](/peios/developing-for-peios/sdk-access-control/hardening-a-process.md)** — turning on process mitigations.

For the exhaustive per-function detail behind any of these, the [reference section](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md) documents every symbol.

---

# Working with tokens

_Peios / Developing for Peios / Access control_

> Answer "who am I?", "who is calling me?", and "act as someone else" with the KACS token API — opening tokens, peer identity over sockets, impersonation, and dropping privilege.

A **token** is the runtime carrier of an identity, and it is a file descriptor. This guide covers the everyday token tasks; [`token.h`](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md) is the exhaustive reference for every call and field.

## Who am I?

To inspect your own identity, open your effective token and query it:

```c
int tok = peios_token_open_self(0, KACS_TOKEN_QUERY);
if (tok < 0) { /* errno */ }

unsigned char sid[PEIOS_SID_MAX_BYTES];
ssize_t n = peios_token_user(tok, sid, sizeof sid);   /* the user SID */

uint32_t il;
peios_token_integrity(tok, &il);                      /* integrity level RID */

struct peios_privilege_set privs;
peios_token_privileges(tok, &privs);                  /* held/enabled privileges */

close(tok);
```

`peios_token_open_self` gives you the *effective* token — if your thread is impersonating, that's the impersonated identity. Pass `KACS_TOKEN_OPEN_REAL` in the flags to get your process's real primary token regardless. The `access` argument is the handle rights you want; `KACS_TOKEN_QUERY` is enough to read.

Group SIDs and other list-valued classes come back as buffers you parse with the [`security.h` views](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md#sid-and-attributes-arrays): read `CLASS_GROUPS` with [`peios_token_query`](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md#query), then `peios_sid_array_parse` it.

## Who is calling me?

The most useful token trick in a service is learning the identity of whoever connected to your socket. When a client connects over a Unix stream or seqpacket socket, KACS captures their token at `connect()` time, and you open it from the accepted connection:

```c
int conn = accept(listener, NULL, NULL);
int caller = peios_token_open_peer(conn);   /* QUERY | IMPERSONATE rights */
if (caller < 0) { /* errno */ }

/* Now query the caller's identity, or impersonate them (below). */
```

This is local authentication with no passwords and no handshake — the kernel vouches for who is on the other end. The handle comes with fixed `QUERY | IMPERSONATE` rights, which is exactly what a server needs.

## Acting as the caller

Once you hold a caller's (impersonation) token, you can **impersonate** them: adopt their identity on your current thread so every subsequent access check runs as *them*, not as your service. This is how you do work on a client's behalf without running privileged and re-implementing their permissions.

```c
if (peios_token_impersonate(caller) != 0) { /* errno */ }

/* ... do the work here: file opens, access checks, etc. all run as the caller ... */

peios_token_revert();   /* back to your own identity */
close(caller);
```

Always pair `peios_token_impersonate` with [`peios_token_revert`](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md#impersonation-and-installation), ideally in the cleanup path, so a failure partway through can't leave your thread wearing someone else's identity. `peios_token_revert` is a safe no-op if you weren't impersonating.

The full flow for a request handler is: `accept` → `peios_token_open_peer` → `peios_token_impersonate` → serve the request → `peios_token_revert` → `close`.

## Dropping power

To run less-trusted code with less authority than you hold, derive a **restricted** token and hand it over. `peios_token_restrict` can delete privileges, demote groups to deny-only, and add restricting SIDs:

```c
struct peios_token_restrict spec = {
    .privs_to_delete = KACS_SE_DEBUG_PRIVILEGE | KACS_SE_IMPERSONATE_PRIVILEGE,
    .flags           = KACS_TOKEN_RESTRICT_WRITE_RESTRICTED,
};
int weak = peios_token_restrict(my_primary, &spec);
```

The result is a strictly less-powerful token. Combined with [integrity levels](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md#integrity-levels) and confinement (both set when [minting a token](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md#the-token-spec-builder)), this is the basis of sandboxing on Peios.

## Minting tokens

Creating a token from scratch requires `SeCreateTokenPrivilege` and is the province of authentication authorities, not ordinary programs. When you do need it, the [token-spec builder](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md#the-token-spec-builder) is the ergonomic path — typed setters for the user SID, groups, privileges, integrity, claims, and the rest, then `peios_token_builder_create`. Mind the [index convention](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md#the-index-convention) for owner/primary-group references, and don't add the logon SID yourself — the kernel injects it.

## Next

- **[Checking access](/peios/developing-for-peios/sdk-access-control/checking-access.md)** — use a token to make an authorisation decision.
- **[`token.h` reference](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md)** — every token call in full.
- **[Impersonation](/peios/security-fundamentals/impersonation/overview.md)** — the operator-side model and its two gates.

---

# Checking access

_Peios / Developing for Peios / Access control_

> Make an authorisation decision end to end — build a security descriptor, run an access check against a token, and interpret the granted mask.

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`](/peios/developing-for-peios/sdk-reference/sdk-access/access-h-access-checks.md) and [`security.h`](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md); here we put them together.

## When to use this

Reach for [`peios_access_check`](/peios/developing-for-peios/sdk-reference/sdk-access/access-h-access-checks.md#the-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:

```c
/* 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](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md#sddl-text-codec) 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](/peios/developing-for-peios/sdk-access-control/working-with-tokens.md#who-is-calling-me), 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):

```c
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

```c
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`](/peios/developing-for-peios/sdk-reference/sdk-access/access-h-access-checks.md#the-object-type-list-variant): 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`](/peios/developing-for-peios/sdk-reference/sdk-access/access-h-access-checks.md#audit-outputs) 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](/peios/developing-for-peios/sdk-reference/sdk-access/access-h-access-checks.md)** — every field of the request and the audit outputs.
- **[Access decisions](/peios/security-fundamentals/access-decisions/overview.md)** — the operator-side account of how KACS reaches a verdict (and how to debug a surprising denial).

---

# Securing files

_Peios / Developing for Peios / Access control_

> Open files the native KACS way, and read and write a file's security descriptor by path or by fd.

Peios files carry real security descriptors, and the SDK opens them with a native KACS open rather than POSIX `open()`. This guide covers the two everyday tasks: opening a file with a specific access, and reading or changing a file's security. The full surface is in [`file.h`](/peios/developing-for-peios/sdk-reference/sdk-files/file-h-file-security.md).

To keep the fragments readable, most error checks are elided here — every call below returns `-1` with `errno` on failure, and real code must check each one (see [Library conventions](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md)).

## The native open

[`peios_file_open`](/peios/developing-for-peios/sdk-reference/sdk-files/file-h-file-security.md#opening-a-file) is shaped like `NtCreateFile`: you state the access you want, what to do about existence (the *disposition*), any create options, and — when creating — the security descriptor to stamp on the new file. It returns an ordinary Linux fd whose **granted access is fixed for the fd's lifetime**, which means you can safely hand it to another process by `SCM_RIGHTS`, `dup`, or across `exec`: the fd carries exactly the access it was opened with.

Open-or-create a file, readable and writable, stamping a creator SD if it's new:

```c
struct peios_open_params p = {
    .desired_access = KACS_FILE_READ_DATA | KACS_FILE_WRITE_DATA,
    .disposition    = KACS_DISPOSITION_OPEN_IF,   /* open existing, else create */
    .sd             = creator_sd, .sd_len = creator_sd_len,   /* used only on create */
};

uint32_t status = 0;
int fd = peios_file_open(AT_FDCWD, "state.db", &p, &status);
if (fd < 0) { perror("open"); return -1; }

if (status == KACS_STATUS_CREATED) { /* we made it */ }
else                               { /* it already existed */ }
```

`status_out` tells you *what happened* — created versus opened versus overwritten — without a separate `stat` and its attendant race. If you're only ever opening existing files, use a plain open disposition and pass `sd = NULL`.

## Reading a file's security descriptor

To see who can do what to a file, read its SD. `secinfo` selects which components you want — owner, group, DACL, SACL — so you fetch only what you need:

```c
/* Probe, allocate, read (two-call). */
ssize_t need = peios_file_get_sd(AT_FDCWD, "state.db",
                                 KACS_SECINFO_OWNER | KACS_SECINFO_DACL,
                                 NULL, 0, 0);
void *sd = malloc(need);
peios_file_get_sd(AT_FDCWD, "state.db",
                  KACS_SECINFO_OWNER | KACS_SECINFO_DACL, sd, need, 0);

/* Parse it with a security.h view. */
peios_sd_view v;
peios_sd_parse(sd, need, &v);
peios_acl_view dacl;
if (peios_sd_view_dacl(&v, &dacl) == 0) {
    unsigned n = peios_acl_view_count(&dacl);
    /* iterate ACEs … */
}
free(sd);
```

If you already hold a file fd, use the fd-targeted [`peios_fd_get_sd`](/peios/developing-for-peios/sdk-reference/sdk-files/file-h-file-security.md#by-fd) instead of a path — no second path resolution, and for a normal file fd the check uses the access already baked in at open.

## Changing a file's security descriptor

Writing an SD is component-selective too: name the components you're changing in `secinfo`, and everything you *don't* name is preserved. To tighten a file's DACL without touching its owner or SACL:

```c
/* Build an SD carrying only a DACL. */
peios_sd_builder *b = peios_sd_builder_new();
peios_sd_builder_dacl(b, new_acl, new_acl_len);
size_t sd_len; const void *sd_bytes = peios_sd_builder_bytes(b, &sd_len);

peios_file_set_sd(AT_FDCWD, "state.db", KACS_SECINFO_DACL, sd_bytes, sd_len, 0);
peios_sd_builder_free(b);
```

Because only `KACS_SECINFO_DACL` is selected, the owner, group, and SACL are left exactly as they were. The [`security.h` builders](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md#building-security-descriptors) are how you assemble the SD to apply.

## Pre-flighting an open

Sometimes you want to know whether a caller *could* open a file before you actually do. Read the file's SD, then run an [access check](/peios/developing-for-peios/sdk-access-control/checking-access.md) against the caller's token with `peios_file_generic_mapping` — no open, no side effects, just the verdict.

## Next

- **[`file.h` reference](/peios/developing-for-peios/sdk-reference/sdk-files/file-h-file-security.md)** — every parameter, plus the fd-targeted calls and mount policy.
- **[File access](/peios/security-fundamentals/file-access/overview.md)** — the operator-side model of native file security.

---

# Hardening a process

_Peios / Developing for Peios / Access control_

> Turn on process mitigations to harden your program, and understand the one-way, fail-closed semantics.

Peios lets a process opt into hardening — exploit mitigations enforced by the kernel on that process's security block. The SDK exposes this through a single call, [`peios_process_set_mitigations`](/peios/developing-for-peios/sdk-reference/sdk-processes/process-h.md#setting-mitigations). This short guide covers using it well; the full flag set and semantics are in [`process.h`](/peios/developing-for-peios/sdk-reference/sdk-processes/process-h.md) and the [operator docs](/peios/security-fundamentals/process-mitigations/overview.md).

## Harden yourself at startup

The common case is a program hardening itself early in `main`, before it processes any untrusted input:

```c
#include <peios/process.h>

int main(void)
{
    /* Enforce W^X and shadow stacks; abort if either can't be turned on. */
    if (peios_process_set_mitigations(-1, KACS_MIT_WXP | KACS_MIT_SML) != 0) {
        perror("set_mitigations");
        return 1;   /* refuse to run unhardened */
    }
    /* ... the rest of the program runs with those protections on ... */
}
```

`pidfd == -1` targets the calling process. `mitigations` is a mask of `KACS_MIT_*` bits (from `<pkm/psb.h>`); combine the ones you want and set them together.

## Three things to know

**It's one-way.** Mitigation bits can only be *set*, never cleared — once on, they stay on for the life of the process. That's the point: a mitigation you could turn off is one an attacker could turn off. Treat each call as a permanent commitment.

**It's all-or-nothing, and fails closed.** If any requested protection can't actually be activated, the call changes *nothing* and returns `-1`. You never end up believing a mitigation is on when it isn't. So request the set you require together and check the result once — success means the whole set is active. (Bits from earlier successful calls stay on regardless.)

**Targeting another process is privileged.** To harden a process other than your own, you need `PROCESS_SET_INFORMATION` on it *and* PIP dominance over it — you can't reach into a process you don't already dominate. For self-hardening (`-1`), neither is needed.

## Choosing what to enable

The bit catalogue and what each mitigation defends against is documented operator-side under [process mitigations](/peios/security-fundamentals/process-mitigations/overview.md) (and in the Peios Kernel TRM §3.3, the Process Security Block). A couple of notes for the SDK caller:

- `KACS_MIT_ALL` is the mask of all valid bits — useful for validating input, not usually what you'd blanket-enable without thought.
- `KACS_MIT_CFI` is a legacy alias that expands to `KACS_MIT_CFIF | KACS_MIT_CFIB`.

Enable the specific protections your program can tolerate, verify the call succeeded, and prefer to do it before you touch untrusted data.

## Next

- **[`process.h` reference](/peios/developing-for-peios/sdk-reference/sdk-processes/process-h.md)** — the call in full.
- **[Process mitigations](/peios/security-fundamentals/process-mitigations/overview.md)** — every mitigation and its threat model.
