3.9.6 The Set-Security Interface
All descriptor modification flows through one syscall. The caller provides a file descriptor or path, a bitmask naming which components to modify, and a self-relative descriptor blob carrying the new values.
The blob is validated structurally — parseable, well-formed ACEs, valid SIDs, at most 65535 bytes. With an existing valid file security descriptor, only the indicated components are merged into it. Unindicated components are preserved unchanged.
The input is always one self-relative descriptor subset, never a raw
SID or ACL fragment. SACL_SECURITY_INFORMATION and
LABEL_SECURITY_INFORMATION cannot be combined in one call, because
both target the SACL field with incompatible meanings; the pair fails
with EINVAL.
A SACL_SECURITY_INFORMATION write replaces the object's entire
SACL. A LABEL_SECURITY_INFORMATION write interprets the input SACL
as the label subset only: no SACL component removes the explicit
mandatory label and returns the object to the default unlabelled
state; a present SACL contains exactly one non-inherit-only
SYSTEM_MANDATORY_LABEL_ACE and nothing else; and the object's
non-label SACL ACEs are preserved.
After merging, the result still has a non-null owner — the group SID may be null — and a merge that would leave no owner fails.
MIC and PIP apply to these checks. A low-integrity caller cannot
modify a high-integrity file's descriptor even where the DACL grants
WRITE_OWNER.
A read-only mount withholds the write along that path whatever the
descriptor grants: the call fails with EROFS, as every modifying
operation through a read-only bind does, and the descriptor stays
writable through any writable path to the same object. The mount is
consulted after the access check, so a caller the descriptor refuses
sees the denial rather than a read-only error that would confirm the
object exists.
3.9.6.1 Component merging and replacement #
For a file whose cached descriptor is valid, the C dispatcher selects the merge path. The control-bit merge starts from the destination control value and replaces these bits from the input when their component is selected:
| Selected component | Replaced control bits |
|---|---|
| Owner | OWNER_DEFAULTED |
| Group | GROUP_DEFAULTED |
| DACL | DACL_DEFAULTED, DACL_TRUSTED, DACL_AUTO_INHERIT_REQ, DACL_AUTO_INHERITED, DACL_PROTECTED |
| Full SACL | SACL_DEFAULTED, SACL_AUTO_INHERIT_REQ, SACL_AUTO_INHERITED, SACL_PROTECTED |
This copies more than ACE lists, but is not byte-identical descriptor replacement. If group is not selected, the destination group remains. The resource-manager control byte remains the destination's, as does its RM_CONTROL_VALID state. A label-only merge does not replace the full-SACL control-bit set in the table.
A missing or invalid cached file security descriptor does not take that merge path. The dispatcher requires enabled SeRestorePrivilege and calls the replacement builder, which requires owner, group and DACL selections and validates the supplied owner and label. Do not apply valid-descriptor field-retention guarantees to that repair case.
These details are source-checked against kernel commit 8e0e22de, not a runtime-validated restore procedure.
3.9.6.2 Ownership #
A new owner may be set only to the caller's own SID, or to a group SID
on the token carrying SE_GROUP_OWNER. SeTakeOwnershipPrivilege
allows setting ownership to the caller's own SID regardless of what
the current descriptor says, and SeRestorePrivilege allows any
arbitrary SID.
3.9.6.3 Integrity labels #
Without enabled SeRelabelPrivilege a caller may set a label only at or below
its own integrity level; with it, the label-level check permits any level.
Other access and validation checks still apply.
The constraint applies through both paths — the dedicated label
subset, and a label ACE embedded in a full SACL write. A SACL write
whose ACL contains a mandatory label ACE raising integrity above the
caller's level requires SeRelabelPrivilege exactly as the label path
does, even though the SACL component itself is gated only by
ACCESS_SYSTEM_SECURITY.
SeSecurityPrivilege or SeRestorePrivilege alone is not sufficient for
a label above the caller's integrity: the label-assignment check independently requires enabled
SeRelabelPrivilege.
3.9.6.4 The SeRestorePrivilege bypass #
SeRestorePrivilege fires inside the AccessCheck pipeline, so it
bypasses the check only where kacs_set_sd runs a live one: an
O_PATH descriptor with AT_EMPTY_PATH, a pidfd, a token descriptor
with AT_EMPTY_PATH, or a path. On those paths it grants every
requested right, WRITE_OWNER, WRITE_DAC and
ACCESS_SYSTEM_SECURITY included.
Called on an ordinary file descriptor the required rights are checked
against the cached mask instead, no AccessCheck runs, and the
privilege has no effect at all. A caller needing the bypass has to use
the O_PATH route — which is the mechanism behind backup restoration,
administrative repair, and the missing-descriptor repair path
(§3.9.5).
3.9.6.5 Mandatory resource attributes #
When a caller modifies the SACL, the existing and new SACLs are
compared for changes to SYSTEM_RESOURCE_ATTRIBUTE_ACE entries. An
existing attribute carrying CLAIM_SECURITY_ATTRIBUTE_MANDATORY
(0x0020) cannot be removed, nor its values modified, without
SeTcbPrivilege — and an attempt without it fails the entire call
rather than silently dropping the change.
3.9.6.6 Write mechanics #
The updated descriptor is serialised to self-relative binary form and written to the xattr through an internal kernel path bypassing the denial hook, and the in-memory cache is updated. An audit event is emitted if the file's SACL carries a matching audit ACE.
The cache update is not atomic with the xattr write; §3.9.5 describes the lock ordering that forces this and the last-writer-wins window it produces.