On this page
file.h — File security
<peios/file.h> is the file surface of KACS. Where ordinary POSIX open() gives you a file descriptor governed by mode bits, peios_file_open performs a native KACS open — an NtCreateFile-shaped call carrying a desired access mask, a create disposition, create options, and an optional creator security descriptor — and hands back an ordinary Linux file fd whose granted access mask is fixed for the fd's lifetime. Because the grant is baked into the fd, it can be delegated safely by dup, SCM_RIGHTS, or across exec: whoever holds the fd holds exactly the access it was opened with, no more.
Alongside the open, this module reads and writes a file's security descriptor (by path or by fd) and governs how a superblock without native SD storage is treated.
The wire constants (KACS_DISPOSITION_*, KACS_CREATE_OPT_*, KACS_FILE_*, KACS_SECINFO_*, KACS_MOUNT_POLICY_*, KACS_STATUS_*) come from <pkm/file.h> and <pkm/sd.h>. The security descriptors these calls exchange are built and parsed with <peios/security.h>.
Opening a file
struct peios_open_params {
uint32_t desired_access; /* KACS_FILE_* | standard | generic (strict-mode) */
uint32_t disposition; /* KACS_DISPOSITION_* */
uint32_t options; /* KACS_CREATE_OPT_* */
uint32_t flags; /* AT_SYMLINK_NOFOLLOW | KACS_BACKUP_INTENT | KACS_RESTORE_INTENT */
const void *sd; /* creator SD on create, else NULL */
size_t sd_len;
};
int peios_file_open(int dirfd, const char *path,
const struct peios_open_params *p, uint32_t *status_out);
peios_file_open opens path relative to dirfd (the usual *at convention — an absolute path ignores dirfd, and AT_FDCWD means the current directory). It returns a file fd, or -1 with errno.
The parameters:
| Field | Meaning |
|---|---|
desired_access |
The access mask you are requesting — KACS_FILE_* object rights, standard rights, or (in strict mode) generic bits the file class maps. The granted subset is what the returned fd is fixed at. |
disposition |
What to do about existence: KACS_DISPOSITION_* — open-existing, create-new, open-or-create, supersede, overwrite, and so on. This is the create/open decision open() splits across O_CREAT/O_EXCL/O_TRUNC. |
options |
KACS_CREATE_OPT_* create options — directory-vs-file, no-follow, write-through, delete-on-close, and the rest of the NtCreateFile option set. |
flags |
AT_SYMLINK_NOFOLLOW, plus the privilege-intent flags KACS_BACKUP_INTENT / KACS_RESTORE_INTENT that let SeBackupPrivilege / SeRestorePrivilege widen the access the open is granted. |
sd / sd_len |
The creator security descriptor — the SD to stamp on a newly created file. Pass NULL when opening an existing file (or to let the parent's inheritance decide the new file's SD). |
status_out, if non-NULL, receives a KACS_STATUS_* code telling you what happened — whether the file was opened, created, superseded, overwritten. This is how you distinguish "created a new file" from "opened the existing one" after an open-or-create disposition, without a separate stat race.
Errors: EACCES (a requested right denied — strict mode), EEXIST (create-new and the file exists), ENOENT (open-existing and it doesn't), ENOTDIR (directory option, non-directory target), ELOOP (no-follow and the target is a symlink), EINVAL (MAXIMUM_ALLOWED without a concrete data/execute bit, malformed creator SD, NULL path/p, sd == NULL with sd_len != 0), EBADF (bad dirfd).
struct peios_open_params p = {
.desired_access = KACS_FILE_READ_DATA | KACS_FILE_WRITE_DATA,
.disposition = KACS_DISPOSITION_OPEN_IF, /* open or create */
.options = 0,
.sd = creator_sd, .sd_len = creator_sd_len,
};
uint32_t status = 0;
int fd = peios_file_open(AT_FDCWD, "data.bin", &p, &status);
if (fd < 0) { /* errno */ }
/* status == KACS_STATUS_CREATED or KACS_STATUS_OPENED */
libpeios marshals these params into a struct kacs_open_how for you — setting its size and zeroing the reserved fields — so the call stays forward-compatible across kernel versions.
Reading and writing a file's security descriptor
A file's SD can be accessed by path or by fd. In both cases secinfo is a mask of KACS_SECINFO_* bits selecting which components (owner, group, DACL, SACL, …) the operation touches — you read or write just the parts you name and leave the rest alone.
The rights required scale with the components you touch (see Managing file security):
Component (KACS_SECINFO_*) |
Reading needs | Writing needs |
|---|---|---|
OWNER / GROUP |
READ_CONTROL |
WRITE_OWNER (plus owner-SID validation) |
DACL |
READ_CONTROL |
WRITE_DAC |
SACL |
ACCESS_SYSTEM_SECURITY |
ACCESS_SYSTEM_SECURITY |
LABEL |
READ_CONTROL |
WRITE_OWNER (the label cannot rise above the caller's integrity without SeRelabelPrivilege) |
ACCESS_SYSTEM_SECURITY is itself gated by SeSecurityPrivilege; READ_CONTROL and WRITE_DAC are implicitly granted to the owner. SACL and LABEL cannot be combined in one call (EINVAL). The check is all-or-nothing: if any requested component fails its check, the whole call fails.
By path
ssize_t peios_file_get_sd(int dirfd, const char *path, uint32_t secinfo,
void *buf, size_t cap, uint32_t at_flags);
int peios_file_set_sd(int dirfd, const char *path, uint32_t secinfo,
const void *sd, size_t len, uint32_t at_flags);
peios_file_get_sdreads thesecinfo-selected components ofpath's SD intobuf, getxattr-style (two-call protocol — probe withcap == 0, and a too-small non-zero buffer failsERANGEwithout truncating).at_flagsacceptsAT_SYMLINK_NOFOLLOW. Errors:EACCES(component right missing),EINVAL(SACL+LABELtogether;NULLpath, orNULLbuffer with non-zerocap),ERANGE(non-probe buffer too small),ENOENT(path doesn't exist),ELOOP(no-follow and symlink).peios_file_set_sdwrites thesecinfocomponents ofsdontopath, preserving the components you did not select. So to change only the DACL, build an SD with a DACL, passsecinfo = KACS_SECINFO_DACL, and the owner/group/SACL are untouched. Errors:EACCES(component right missing),EPERM(owner-SID validation failed withoutSeRestorePrivilege; label raised withoutSeRelabelPrivilege; MANDATORY attribute removed withoutSeTcbPrivilege),EINVAL(malformed SD,SACL+LABELtogether,NULLor zero-lengthsd),ENOENT,ELOOP.
By fd
ssize_t peios_fd_get_sd(int fd, uint32_t secinfo, void *buf, size_t cap);
int peios_fd_set_sd(int fd, uint32_t secinfo, const void *sd, size_t len);
The same operations against the object fd already refers to. The access check they perform depends on the fd type: a normal file fd is checked against its cached granted mask (the one baked in at open), while an O_PATH, pidfd, or token fd triggers a live check. That distinction — cached for the fixed-grant file fd, live for the others — is documented in PSD-004 §11.5; the practical upshot is that a file fd already opened with the right access can get/set its SD without a second path resolution.
The required rights and errors match the by-path calls, minus the path-resolution failures (ENOENT/ELOOP), plus EBADF (bad fd).
Mount policy
Not every filesystem can store native security descriptors. The mount policy governs how KACS treats a superblock that has no native SD storage — whether files there get a synthesised SD, a template SD, or are denied. These calls target the superblock the object fd lives on and require SeTcbPrivilege.
struct peios_mount_policy {
uint32_t policy; /* KACS_MOUNT_POLICY_* */
uint32_t flags;
uint32_t generation;
const void *template_sd;
size_t template_sd_len;
};
int peios_mount_get_policy(int fd, struct peios_mount_policy *out,
void *tmpl_buf, size_t tmpl_cap);
int peios_mount_set_policy(int fd, const struct peios_mount_policy *p);
peios_mount_get_policyreads the policy forfd's superblock intoout. The template SD is returned into yourtmpl_bufgetxattr-style: on successout->template_sdpoints intotmpl_bufwhen that buffer was large enough, or isNULLif the superblock has no template. ANULLtemplate buffer (ortmpl_cap == 0) is valid only when you don't need the template bytes. A too-small template buffer is not an error — the call still succeeds, reports the true length inout->template_sd_len, and leavesout->template_sdNULLso you can size a retry. Errors:EPERM(SeTcbPrivilegemissing),EBADF(bad fd),EINVAL(NULLout, orNULLtmpl_bufwith non-zerotmpl_cap),EFAULT(bad buffer pointer),ENOMEM(allocation failed).peios_mount_set_policyinstallspas the superblock's policy.policyis aKACS_MOUNT_POLICY_*value;template_sd/template_sd_lensupply the template SD when the policy calls for one.flagsandgenerationmust be zero on set — the kernel manages the generation counter itself and rejects a non-zero input. Errors:EPERM(SeTcbPrivilegemissing),EINVAL(unknown or unmanagedpolicy, non-zeroflags/generation, malformed or oversized template,NULLtemplate with non-zero length),EOPNOTSUPP(superblock not KACS-managed),EBADF(bad fd),EFAULT(bad pointer).
The generic mapping
extern const struct kacs_generic_mapping peios_file_generic_mapping;
The canonical generic→specific rights mapping for the file object class. Pass it to peios_access_map_generic, or as the mapping in a peios_access_request when checking access against a file's SD — for example to pre-flight whether a caller could open a file before you actually open it.
See also
<peios/security.h>— building the creator SDs and parsing the SDs these calls return.<peios/access.h>— evaluating a file SD withpeios_file_generic_mapping.- File access and Mount policies — the operator-side model of native file security.