Peios Common Data Structures
The common binary data structures shared across Peios subsystems: the GUID, LUID, and SID identifier types, and the security descriptor family.
Single-page view · as markdown
1.1 Scope
Peios / Advanced Peios / PCDS / Introduction
This document defines the common binary data structures shared across Peios subsystems: three identifier types — the Globally Unique Identifier (GUID), the Locally Unique Identifier (LUID), and the Security Identifier (SID) — and the Security Descriptor (SD) family.
This document covers:
- GUID — binary format, string representation, comparison semantics, and generation requirements
- LUID — binary format, comparison semantics, and allocation model
- SID — binary format, string representation, comparison semantics, and the well-known SID catalogue
- SD — the security descriptor structure and its subtypes: ACL and ACE formats, access masks, ACE ordering, inheritance, ownership, conditional ACEs and their bytecode, claim attributes, and resource attributes
This document does not cover:
- Well-known GUID and LUID values — defined in the specifications of the subsystems that declare them
- SID-bearing aggregate structures such as SID_AND_ATTRIBUTES — described in the Peios Kernel TRM §3.2.2
- The access-check algorithm that evaluates these structures — described in the Peios Kernel TRM §3.8
- Per-object-type SD storage locations — described in the Peios Kernel TRM §3.3.3 for processes and §3.9.5 for files
- Application-specific identifier namespaces
Three of those exclusions point at a manual rather than at another specification, which is deliberate and worth stating once. KACS — the kernel's access-control implementation — is described in the Peios Kernel TRM §3 and is not separately specified. Peios does not offer a standard from which a second, independent access-control implementation could be built; it offers a manual describing the one that exists.
What a third party does need is the other half: the structures that cross the boundary into that implementation, and the rules for reading and writing them. That is this document, and it is specified. Where a structure's meaning depends on what KACS does with it, this document names the manual article that describes the behaviour rather than restating it (Conventions §4.7).
1.2 Conventions
Peios / Advanced Peios / PCDS / Introduction
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119. Text set off as a note is informative, not normative.
Everything else — roles, byte order, sizes, layout tables, notation, strings, timestamps, citation, and the external standards this anthology depends on — is defined in the Conventions book and is not restated here. PCDS departs from none of it.
Where a chapter needs a convention of its own, that chapter states it.
2.1 Binary Format
Peios / Advanced Peios / PCDS / GUID
A GUID (Globally Unique Identifier) is a 128-bit identifier with global uniqueness guarantees, used to identify registry hives, layers, object types, and other entities that require stable identity across systems and reboots. The GUID with all 128 bits set to zero is the nil GUID, a sentinel value meaning "no GUID" or "unset."
A GUID is a 128-bit (16-byte) value with the following binary layout:
| Offset | Size | Field | Type |
|---|---|---|---|
| 0 | 4 | Data1 | uint32, little-endian |
| 4 | 2 | Data2 | uint16, little-endian |
| 6 | 2 | Data3 | uint16, little-endian |
| 8 | 8 | Data4 | uint8[8] |
The total size of a GUID MUST be exactly 16 bytes with no padding.
Data1, Data2, and Data3 MUST be stored in little-endian byte order.
Data4 is a raw byte array with no endianness interpretation.
2.1.1 Nil GUID #
The nil GUID is the GUID with all 16 bytes set to zero.
The nil GUID is a valid GUID value. Specifications that require a non-nil GUID MUST state this requirement explicitly.
The nil GUID MUST NOT be produced by the generation algorithm defined in §2.4.
2.2 String Format
Peios / Advanced Peios / PCDS / GUID
2.2.1 Canonical form #
The canonical string representation of a GUID MUST use the following format:
{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
The string MUST be exactly 38 characters: an opening brace, 32 hex digits arranged in 8-4-4-4-12 groups separated by hyphens, and a closing brace.
The fields map to the string as follows:
| Group | Digits | Source |
|---|---|---|
| 1 | 8 | Data1, most significant nibble first |
| 2 | 4 | Data2, most significant nibble first |
| 3 | 4 | Data3, most significant nibble first |
| 4 | 4 | Data4[0] and Data4[1], in byte order |
| 5 | 12 | Data4[2] through Data4[7], in byte order |
For Data1, Data2, and Data3, the hex representation is of the numeric value (most significant nibble first), not of the stored byte order. For Data4, each byte is encoded in sequence with the high nibble before the low nibble.
2.2.2 Case #
Canonical output MUST use lowercase hex digits (a–f).
2.2.3 Parsing #
Parsers MUST accept both uppercase and lowercase hex digits.
Parsers MUST accept the braced form {...} and SHOULD accept the
unbraced form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
Parsers MUST reject strings that do not have exactly the right number of hex digits in each group.
2.3 Comparison
Peios / Advanced Peios / PCDS / GUID
2.3.1 Equality #
Two GUIDs are equal if and only if their 16-byte binary representations are identical.
GUID comparison MUST be performed on the binary representation, not on string representations.
2.3.2 Ordering #
This document does not define a total ordering for GUIDs. Specifications that require an ordered GUID collection MUST define their ordering convention explicitly.
2.4 Generation
Peios / Advanced Peios / PCDS / GUID
GUIDs generated by Peios MUST be version 4 (random) as defined in RFC 4122 §4.4.
2.4.1 Algorithm #
To generate a version 4 GUID:
- Fill all 16 bytes with cryptographically random data.
- Set the four most significant bits of Data3 to
0100(version 4). - Set the two most significant bits of Data4[0] to
10(RFC 4122 variant).
The resulting GUID has 122 random bits, 4 version bits, and 2 variant bits.
2.4.2 Randomness source #
The random data MUST be obtained from a cryptographically secure source.
In the kernel, this MUST be get_random_bytes() or equivalent.
In userspace, this MUST be getrandom(2) with no flags (blocking
until the entropy pool is initialised) or equivalent.
GUIDs MUST NOT be generated using a pseudorandom number generator seeded from a predictable source.
Appendix 2.A Prior Art
Peios / Advanced Peios / PCDS / GUID
2.A.1 MS-DTYP #
The GUID type defined in this document derives from the Microsoft Data Types specification (MS-DTYP), §2.3.4. The Peios GUID binary format is identical to the MS-DTYP GUID. The Peios GUID string format follows the same hyphenated hex convention but normalises to lowercase hex digits on output, where Microsoft implementations typically produce uppercase.
2.A.2 RFC 4122 #
RFC 4122 ("A Universally Unique IDentifier (UUID) URN Namespace") defines the UUID format and generation algorithms. The GUID binary layout used by Microsoft and adopted by Peios is the mixed-endian variant of the RFC 4122 UUID: the first three fields are little-endian integers and the last field is a raw byte array. This differs from the RFC 4122 network byte order representation where all fields are big-endian.
Peios generates version 4 (random) GUIDs as defined in RFC 4122 §4.4.
2.A.3 DCE RPC #
The GUID structure originates from the DCE 1.1 RPC specification,
which defined the uuid_t type with the same field layout. The
mixed-endian encoding reflects the DCE convention of encoding integer
fields in the sender's native byte order (little-endian on x86).
3.1 Binary Format
Peios / Advanced Peios / PCDS / LUID
An LUID (Locally Unique Identifier) is a 64-bit identifier with boot-scoped local uniqueness, used to identify transient entities such as logon sessions and privilege instances that do not persist across reboots. The LUID with all 64 bits set to zero is the nil LUID, a sentinel value meaning "no LUID" or "unset."
An LUID is a 64-bit (8-byte) value with the following binary layout:
| Offset | Size | Field | Type |
|---|---|---|---|
| 0 | 4 | LowPart | uint32, little-endian |
| 4 | 4 | HighPart | uint32, little-endian |
The total size of an LUID MUST be exactly 8 bytes with no padding.
Both fields MUST be stored in little-endian byte order.
3.1.1 Nil LUID #
The nil LUID is the LUID with all 8 bytes set to zero (LowPart = 0, HighPart = 0).
The nil LUID is a valid LUID value. The nil LUID MUST NOT be assigned by the allocation algorithm defined in §3.3.
Specifications that require a non-nil LUID MUST state this requirement explicitly.
3.2 Comparison
Peios / Advanced Peios / PCDS / LUID
3.2.1 Equality #
Two LUIDs are equal if and only if both their LowPart and HighPart fields are identical.
3.2.2 Ordering #
This document does not define a total ordering for LUIDs. Although LUIDs are allocated monotonically within a boot session (see §3.3), consumers MUST NOT rely on numeric ordering to infer temporal relationships between LUIDs obtained from different contexts.
3.3 Allocation
Peios / Advanced Peios / PCDS / LUID
LUIDs MUST be allocated by the kernel.
3.3.1 Uniqueness scope #
Each LUID MUST be unique within a single boot session.
LUID values MUST NOT be assumed unique across reboots. A value allocated in one boot session MAY be reused in a subsequent boot session.
3.3.2 Monotonicity #
The kernel MUST allocate LUIDs in strictly monotonically increasing order within a boot session, treating the two fields as a single unsigned 64-bit integer (HighPart << 32 | LowPart).
The starting value of the allocation sequence after each boot is implementation-defined.
3.3.3 Fabrication prohibition #
Userspace code MUST NOT fabricate LUID values. All LUIDs MUST be obtained through the kernel allocation interface or from well-known constants defined in a Peios specification.
Appendix 3.A Prior Art
Peios / Advanced Peios / PCDS / LUID
3.A.1 MS-DTYP #
The LUID type defined in this document derives from the Microsoft Data Types specification (MS-DTYP), §2.3.7.
The Peios LUID binary format diverges from MS-DTYP in one detail: HighPart is an unsigned 32-bit integer (uint32) rather than the signed 32-bit integer (LONG) used in MS-DTYP. The signed type in MS-DTYP is a Win32 API convention with no semantic purpose — LUID values are never negative. Making the field unsigned simplifies comparison and eliminates a class of sign-extension bugs.
4.1 Binary Format
Peios / Advanced Peios / PCDS / SID
A SID (Security Identifier) is a variable-length binary value that uniquely identifies a principal — a user, group, service, machine, or well-known entity. SIDs are the fundamental identity primitive of the Peios security model: they appear in tokens as identity, in security descriptors as access rules, and as references throughout the system.
A SID is encoded as a contiguous binary structure with the following layout:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | Revision | MUST be 1. |
| 1 | 1 | SubAuthorityCount | Number of sub-authorities. MUST be between 0 and 15 inclusive. |
| 2 | 6 | IdentifierAuthority | A 6-byte big-endian value identifying the authority that issued the SID. |
| 8 | 4 × SubAuthorityCount | SubAuthority[] | Array of 32-bit unsigned integers in little-endian byte order. |
The total size of a SID in bytes is 8 + (4 × SubAuthorityCount).
The minimum size is 8 bytes (zero sub-authorities). The maximum size
is 68 bytes (15 sub-authorities).
The last sub-authority in a SID is the Relative Identifier (RID) — the portion that distinguishes individual principals within a domain.
4.2 String Format
Peios / Advanced Peios / PCDS / SID
SIDs are represented in string form as:
S-1-{authority}-{sub1}-{sub2}-...-{subN}
Where:
Sis a literal prefix.1is the revision number.{authority}is the IdentifierAuthority. If the upper 2 bytes are zero, this is the decimal representation of the lower 4 bytes. Otherwise, it is the lowercase hexadecimal representation of all 6 bytes, zero-padded to 12 hex digits and prefixed with0x.- Each
{subN}is the decimal representation of the corresponding 32-bit sub-authority.
4.3 Comparison
Peios / Advanced Peios / PCDS / SID
4.3.1 Equality #
Two SIDs are equal if and only if their binary representations are byte-for-byte identical.
SID comparison MUST be performed on the binary encoding, not the string form. There is no case sensitivity, normalisation, or equivalence relation — equality is exact binary match.
4.3.2 Ordering #
This document does not define a total ordering for SIDs. Specifications that require an ordered SID collection MUST define their ordering convention explicitly.
4.4 Well-Known SIDs
Peios / Advanced Peios / PCDS / SID
The following SIDs have fixed values and well-defined meanings. An implementation MUST recognise these SIDs and apply their defined semantics wherever they are referenced. The access-check behaviour attached to them is described in the Peios Kernel TRM §3.8.
4.4.1 Universal authorities #
| SID | Name | Description |
|---|---|---|
| S-1-0-0 | Nobody | The null SID. No principal. |
| S-1-1-0 | Everyone | Matches all principals, including anonymous. |
| S-1-2-0 | Local | Principals that log on locally (physically). |
| S-1-2-1 | Console Logon | Principals that log on via the physical console. |
4.4.2 Creator authorities #
| SID | Name | Description |
|---|---|---|
| S-1-3-0 | Creator Owner | Placeholder in inheritable ACEs. Replaced with the creating principal's SID during inheritance. |
| S-1-3-1 | Creator Group | Placeholder in inheritable ACEs. Replaced with the creating principal's primary group SID during inheritance. |
| S-1-3-4 | Owner Rights | When present in a DACL, overrides the owner's implicit READ_CONTROL and WRITE_DAC grants. AccessCheck treats this SID as matching the object's owner. |
4.4.3 NT Authority (S-1-5) #
| SID | Name | Description |
|---|---|---|
| S-1-5-2 | Network | Principals that authenticated over the network. |
| S-1-5-4 | Interactive | Principals that logged on interactively. |
| S-1-5-6 | Service | Principals that authenticated as a service. |
| S-1-5-7 | Anonymous | The anonymous identity. Carried by tokens at Anonymous impersonation level. |
| S-1-5-10 | Principal Self | Placeholder in ACEs on directory objects. Matches the caller when the caller's identity corresponds to the object's associated principal. Resolved via the self_sid parameter to AccessCheck. |
| S-1-5-11 | Authenticated Users | All principals that have been authenticated (excludes Anonymous). |
| S-1-5-18 | Local System (SYSTEM) | The operating system's own identity. Highest privilege level. |
| S-1-5-19 | Local Service | A built-in service account with reduced privileges. |
| S-1-5-20 | Network Service | A built-in service account that can authenticate to remote services. |
4.4.4 Logon SIDs #
| SID | Name | Description |
|---|---|---|
| S-1-5-5-X-Y | Logon SID | A per-authentication-event SID generated at LogonSession creation. X and Y are unique values. Injected into the token's groups with SE_GROUP_LOGON_ID. |
4.4.5 BUILTIN domain (S-1-5-32) #
| SID | Name | Description |
|---|---|---|
| S-1-5-32-544 | BUILTIN\Administrators | The built-in administrators group. |
| S-1-5-32-545 | BUILTIN\Users | The built-in users group. |
| S-1-5-32-546 | BUILTIN\Guests | The built-in guests group. |
| S-1-5-32-551 | BUILTIN\Backup Operators | Members can bypass file security for backup and restore. |
4.4.6 Domain SIDs (S-1-5-21) #
Domain-specific SIDs follow the pattern
S-1-5-21-{DA1}-{DA2}-{DA3}-{RID}, where the three domain authority
sub-authorities identify the domain and the RID identifies the
principal within that domain.
| RID | Name | Description |
|---|---|---|
| 500 | Domain Administrator | The built-in administrator account. |
| 501 | Domain Guest | The built-in guest account. |
| 512 | Domain Admins | The domain administrators group. |
| 513 | Domain Users | The domain users group. |
| 514 | Domain Guests | The domain guests group. |
| 515 | Domain Computers | Computer accounts in the domain. |
4.4.7 Mandatory integrity labels (S-1-16) #
| SID | Name | Numeric level | Description |
|---|---|---|---|
| S-1-16-0 | Untrusted | 0 | Lowest trust. Sandboxed or experimental code. |
| S-1-16-4096 | Low | 4096 | Reduced trust. Services handling untrusted input. |
| S-1-16-8192 | Medium | 8192 | Standard trust. Default for interactive logons and most services. |
| S-1-16-12288 | High | 12288 | Elevated administrative logons. |
| S-1-16-16384 | System | 16384 | The kernel, peinit, and TCB services. |
The five levels above are the standard, well-known integrity levels;
in practice they behave like an enum. Technically the level is the
SID's single sub-authority as an unsigned integer: any S-1-16-<n>
with exactly one sub-authority is a valid level, and MIC compares
levels numerically. Non-standard values occur in Windows-interop SDs
— e.g. S-1-16-8448 (medium-plus) or S-1-16-20480 (protected). A
mandatory-label SID with a different identifier authority or more
than one sub-authority is malformed and rejected. The standard order
is System > High > Medium > Low > Untrusted.
4.4.8 Process trust labels (S-1-19) #
| SID | Name | Description |
|---|---|---|
| S-1-19-0-0 | None / No trust | Default for unsigned processes. |
| S-1-19-512-1024 | Protected, Authenticode | Third-party signed binaries. |
| S-1-19-512-1536 | Protected, AntiMalware | Security tooling. |
| S-1-19-512-2048 | Protected, App | Peios-distributed applications. |
| S-1-19-512-4096 | Protected, Peios | Core Peios components. |
| S-1-19-512-8192 | Protected, PeiosTcb | Peios Trusted Computing Base. |
| S-1-19-1024-8192 | Isolated, PeiosTcb | Maximum isolation and trust. |
Trust labels encode two dimensions in the SID: the first sub-authority is the PIP type axis and the second is the trust axis (higher = more trusted). Dominance requires both dimensions to be greater than or equal.
KACS currently standardises these PIP type values:
0= None512= Protected1024= Isolated
These values are standardised labels, not a closed enum for AccessCheck. Other numeric type values remain valid and are compared numerically by the same dominance rule.
4.4.9 Confinement SIDs (S-1-15) #
| SID | Name | Description |
|---|---|---|
| S-1-15-2-hash | Confinement SID | Identifies a confined application. The sub-authorities are derived from the application identity. |
| S-1-15-2-1 | ALL_APPLICATION_PACKAGES | Matches all confined applications in normal confinement mode. |
| S-1-15-2-2 | ALL_RESTRICTED_APPLICATION_PACKAGES | Matches confined applications in both normal and strict confinement modes. Strict confinement is the mode where ALL_APPLICATION_PACKAGES is omitted from the capabilities. |
4.4.10 Capability SIDs (S-1-15-3) #
| SID | Name | Description |
|---|---|---|
| S-1-15-3-1 | internetClient | Outbound internet access. |
| S-1-15-3-2 | internetClientServer | Inbound and outbound internet access. |
| S-1-15-3-3 | privateNetworkClientServer | LAN/private network access. |
| S-1-15-3-8 | enterpriseAuthentication | Domain credential access. |
| S-1-15-3-9 | sharedUserCertificates | Certificate store access. |
| S-1-15-3-10 | removableStorage | Removable media access. |
Capability SIDs 4–7 (picturesLibrary, videosLibrary, musicLibrary, documentsLibrary) are reserved. Their SID values MUST NOT be redefined.
Derived capabilities use 8 sub-authorities computed from the SHA-256
hash of the capability name:
S-1-15-3-{h0}-{h1}-{h2}-{h3}-{h4}-{h5}-{h6}-{h7}. The same name
always produces the same SID.
4.4.11 Service SIDs #
Service SIDs follow the pattern SERVICE\{service_name} (e.g.,
SERVICE\jellyfin, SERVICE\loregd) and are added as a group in
the service's token. The token's primary user SID is the account the
service runs as (typically SYSTEM, LocalService, or NetworkService);
the service SID enables per-service access control — a file's DACL
can grant access to SERVICE\jellyfin specifically, rather than to
the broad account the service runs under.
The SID value is derived from the service name using a SHA-1 hash:
the UTF-16LE encoding of the uppercased service name is hashed, and
the 20-byte digest is split into five little-endian 32-bit
sub-authorities: S-1-5-80-{h0}-{h1}-{h2}-{h3}-{h4}. The same
service name always produces the same SID. This matches the Windows
service SID derivation (MS-DTYP compatible).
Appendix 4.A Prior Art
Peios / Advanced Peios / PCDS / SID
4.A.1 MS-DTYP #
The SID type defined in this document derives from the Microsoft
Data Types specification (MS-DTYP), §2.4.2. The Peios binary
encoding is identical to the MS-DTYP packet representation, and the
string form follows the same S-R-I-S convention. The structure
itself originates in Windows NT, where it has been the principal
identity primitive since NT 3.1.
5.1 SD Structure
Peios / Advanced Peios / PCDS / Security Descriptor
A Security Descriptor (SD) defines the complete security policy for a protected object. Every protected object in Peios — every file, registry key, IPC endpoint, service, token, and process — MUST have a Security Descriptor.
An SD has four components and a set of control flags:
-
Owner SID — the principal that owns the object. The owner has implicit rights (READ_CONTROL and WRITE_DAC) unless suppressed by an OWNER RIGHTS ACE.
-
Group SID — an optional primary group associated with the object. When present, it is stored and returned on query and used during CREATOR GROUP substitution during inheritance. When absent, no primary group is available for those metadata operations. No access control decision depends on the group SID directly. If an inheritance or ACL-rewrite operation must materialize a CREATOR GROUP SID and the source object SD has no group SID, KACS MUST fail closed rather than substitute another SID or silently drop the ACE.
-
DACL (Discretionary Access Control List) — an ordered list of ACEs that define who is allowed or denied access. The object's owner controls the DACL (via WRITE_DAC).
-
SACL (System Access Control List) — an ordered list of ACEs that define system-level policy. Despite the name, the SACL carries several distinct ACE types:
- Audit ACEs — which access attempts to log.
- Mandatory label ACE — the object's integrity level for MIC.
- Resource attribute ACEs — name-value attributes for conditional ACE evaluation.
- Scoped policy ID ACEs — references to central access policies.
- Process trust label ACE — the object's PIP trust level.
Modifying the SACL requires ACCESS_SYSTEM_SECURITY. ACCESS_SYSTEM_SECURITY is privilege-controlled: it is normally granted by SeSecurityPrivilege, and may also be granted by restore-intent SeRestorePrivilege in the
kacs_set_sdcases described in the Peios Kernel TRM §3.9.6.
Both the DACL and SACL use the standard binary ACL format defined in §5.2.
5.1.1 Binary format #
SDs use the self-relative binary format defined in MS-DTYP §2.4.6. The format is a 20-byte header followed by the owner SID, optional group SID, SACL, and DACL at offsets specified in the header.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | Revision | MUST be 1. |
| 1 | 1 | Sbz1 | Reserved. Preserved for format compatibility but not interpreted by KACS. When SE_RM_CONTROL_VALID is set in the control flags, this byte carries resource manager control bits defined by the originating system. |
| 2 | 2 | Control | Control flags, little-endian. |
| 4 | 4 | OffsetOwner | Offset to the owner SID, little-endian. 0 if absent. |
| 8 | 4 | OffsetGroup | Offset to the group SID, little-endian. 0 if absent. |
| 12 | 4 | OffsetSacl | Offset to the SACL, little-endian. 0 if absent. |
| 16 | 4 | OffsetDacl | Offset to the DACL, little-endian. 0 if absent. |
Field names are those of MS-DTYP §2.4.6.
The self-relative format packs everything into a contiguous byte buffer with no pointers. This makes it suitable for storage (xattrs, database fields) and wire transmission (IPC, SMB).
KACS MUST use the self-relative format exclusively.
5.1.2 Control flags #
The SD's 16-bit Control field records metadata about the descriptor:
| Flag | Bit | Value | Description |
|---|---|---|---|
| SE_OWNER_DEFAULTED (OD) | 0 | 0x0001 | The owner was established by default means. |
| SE_GROUP_DEFAULTED (GD) | 1 | 0x0002 | The group was established by default means. |
| SE_DACL_PRESENT (DP) | 2 | 0x0004 | A DACL is present. If clear, AccessCheck treats the DACL as null (grants all access). |
| SE_DACL_DEFAULTED (DD) | 3 | 0x0008 | The DACL was established by default means. |
| SE_SACL_PRESENT (SP) | 4 | 0x0010 | A SACL is present. |
| SE_SACL_DEFAULTED (SD) | 5 | 0x0020 | The SACL was established by default means. |
| SE_DACL_TRUSTED (DT) | 6 | 0x0040 | Reserved metadata. Preserved during round-trip serialisation. No operational semantics. |
| SE_SERVER_SECURITY (SS) | 7 | 0x0080 | Create a server ACL based on the input ACL. |
| SE_DACL_AUTO_INHERIT_REQ (AR) | 8 | 0x0100 | Requests that the DACL be auto-inherited from the parent. When clear on a creator SD, parent inheritance is suppressed even if SE_DACL_PROTECTED is not set. |
| SE_SACL_AUTO_INHERIT_REQ | 9 | 0x0200 | Requests that the SACL be auto-inherited from the parent. Same semantics as bit 8 for the SACL. |
| SE_DACL_AUTO_INHERITED (DI) | 10 | 0x0400 | The DACL was created through automatic inheritance. |
| SE_SACL_AUTO_INHERITED (SI) | 11 | 0x0800 | The SACL was created through automatic inheritance. |
| SE_DACL_PROTECTED (PD) | 12 | 0x1000 | The DACL is protected from inheritance. Inheritable ACEs from parent objects MUST NOT be merged. |
| SE_SACL_PROTECTED (PS) | 13 | 0x2000 | The SACL is protected from inheritance. |
| SE_RM_CONTROL_VALID (RM) | 14 | 0x4000 | The Sbz1 byte is interpreted as resource manager control bits. |
| SE_SELF_RELATIVE (SR) | 15 | 0x8000 | The SD is in self-relative format. MUST always be set for stored SDs. |
The architectural maximum SD size is 65,535 bytes. KACS MUST reject any parsed or computed SD whose serialised self-relative byte length exceeds 65,535 bytes.
The DEFAULTED flags are metadata. During object-SD creation, KACS MUST set the corresponding DEFAULTED flag when it supplies that component from a default source rather than from an explicit creator SD component or inherited ACL. KACS MUST NOT grant or deny access based solely on a DEFAULTED flag.
This document records the SE_SERVER_SECURITY flag value but does not
define the server-ACL construction algorithm. An implementation MUST
fail closed when a creator SD attempts to use SE_SERVER_SECURITY.
The PROTECTED flags are operationally significant. Setting SE_DACL_PROTECTED prevents inheritance from parent objects — the object keeps its current ACEs and stops accepting new inheritable ACEs from above.
5.1.3 Null DACL vs empty DACL #
The SE_DACL_PRESENT flag distinguishes two states with very different security consequences:
-
Null DACL (SE_DACL_PRESENT not set) — no discretionary access control. AccessCheck grants all requested access to every caller. This SHOULD almost never be used.
-
Empty DACL (SE_DACL_PRESENT set, zero ACEs) — AccessCheck grants no access to any caller (except the owner's implicit rights). An explicit statement that no principal has discretionary access.
Objects SHOULD always have a DACL. This is a preferred-object-shape recommendation, not a fail-closed requirement. If object creation has no explicit DACL, no inherited DACL, and the creator token has no default DACL, the resulting object SD has a null DACL: SE_DACL_PRESENT is clear and the DACL offset is zero.
5.2 ACL Format
Peios / Advanced Peios / PCDS / Security Descriptor
An Access Control List (ACL) is the binary container for ACEs. DACLs, SACLs, and CAAP policy ACL blobs all use the same standard binary ACL format.
5.2.1 Binary format #
An ACL begins with an 8-byte header followed by AceCount ACEs packed contiguously:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | AclRevision | ACL revision number. Determines which ACE families the ACL formally permits. |
| 1 | 1 | Sbz1 | Reserved. Preserved for compatibility but not interpreted by KACS. |
| 2 | 2 | AclSize | Total size of the ACL in bytes, including the 8-byte header. Little-endian. |
| 4 | 2 | AceCount | Number of ACEs in the ACL. Little-endian. |
| 6 | 2 | Sbz2 | Reserved. Preserved for compatibility but not interpreted by KACS. |
The ACE array begins immediately at offset 8. Each ACE is self-delimiting via its AceSize field. The parser walks the ACL by iterating exactly AceCount ACEs within the AclSize boundary.
5.2.2 Parsing rules #
AclSizeMUST be at least 8 bytes.AclSizeMUST NOT exceed the containing buffer.- The ACL body MUST contain exactly
AceCountACEs within the declaredAclSize. - Truncated ACEs, ACE overruns, or leftover bytes within
AclSizeare malformed. - The architectural maximum ACL size is 64 KB because
AclSizeis a 16-bit field.
ACE structure, ACE-type definitions, and revision-versus-ACE-family rules are specified in §5.4.
5.3 Access Masks
Peios / Advanced Peios / PCDS / Security Descriptor
Every ACE carries an access mask — a 32-bit integer where each bit represents a specific right. The same 32-bit layout is used in three contexts: the ACE's mask (what the rule grants or denies), the requested access (what the caller asks for), and the granted access (what AccessCheck returns).
5.3.1 Bit layout #
The 32 bits are divided into four regions:
5.3.1.1 Object-specific rights (bits 0–15) #
Defined by the object type. Different object types assign different meanings to these bits. A file uses bits for read, write, append, execute; a registry key uses bits for query value, set value, create subkey; a token uses bits for query, duplicate, impersonate. Each subsystem defines its own mapping.
5.3.1.2 Standard rights (bits 16–20) #
Common to all object types:
| Bit | Name | Value | Meaning |
|---|---|---|---|
| 16 | DELETE | 0x00010000 | Delete the object. |
| 17 | READ_CONTROL | 0x00020000 | Read the object's SD (excluding SACL). |
| 18 | WRITE_DAC | 0x00040000 | Modify the object's DACL. |
| 19 | WRITE_OWNER | 0x00080000 | Change the object's owner. |
| 20 | SYNCHRONIZE | 0x00100000 | Wait on the object. |
5.3.1.3 Special rights (bits 24–25) #
| Bit | Name | Value | Meaning |
|---|---|---|---|
| 24 | ACCESS_SYSTEM_SECURITY | 0x01000000 | Read or write the SACL. Requires SeSecurityPrivilege. |
| 25 | MAXIMUM_ALLOWED | 0x02000000 | Not a real right. Request flag that tells AccessCheck to compute and return the maximum set of rights the caller would be granted. MUST NOT appear in an ACE. |
5.3.1.4 Generic rights (bits 28–31) #
Abstract rights mapped to object-specific rights before evaluation:
| Bit | Name | Value |
|---|---|---|
| 28 | GENERIC_ALL | 0x10000000 |
| 29 | GENERIC_EXECUTE | 0x20000000 |
| 30 | GENERIC_WRITE | 0x40000000 |
| 31 | GENERIC_READ | 0x80000000 |
5.3.1.5 Reserved bits #
Bits 21–23 and 26–27 are reserved and MUST NOT be used. An access mask setting any of them is rejected: a desired access mask carrying one fails the request, and an ACE mask carrying one makes the containing SD unparseable.
5.3.2 Generic mapping #
Generic rights exist because SDs need to be portable across object types. A central access policy might say "allow GENERIC_READ on all objects" — and GENERIC_READ means different specific bits for files versus registry keys.
Each object type defines a GenericMapping table:
| Field | Description |
|---|---|
read | Specific + standard bits that GENERIC_READ maps to. |
write | Specific + standard bits that GENERIC_WRITE maps to. |
execute | Specific + standard bits that GENERIC_EXECUTE maps to. |
all | Specific + standard bits that GENERIC_ALL maps to. |
Generic mapping happens once, at request time. AccessCheck MUST map any generic bits in the desired mask to object-specific bits using the object type's GenericMapping table, then clear the generic bits. The DACL walk operates exclusively on specific and standard bits.
5.4 ACE Types
Peios / Advanced Peios / PCDS / Security Descriptor
An Access Control Entry (ACE) is a single rule in an ACL. Each ACE has a header, an access mask, and a principal SID, with optional extensions for object-type and conditional ACEs.
5.4.1 ACE header #
Every ACE begins with a 4-byte header:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | AceType | Identifies the ACE type. |
| 1 | 1 | AceFlags | Inheritance and audit flags. |
| 2 | 2 | AceSize | Total size of the ACE in bytes, including the header. MUST be a multiple of 4. |
5.4.2 ACE body layouts #
The ACE header is followed by a type-specific body. Every multibyte integer in the body is little-endian.
5.4.2.1 Single-SID ACE family #
The following ACE types share the same binary layout:
ACCESS_ALLOWED_ACEACCESS_DENIED_ACESYSTEM_AUDIT_ACESYSTEM_ALARM_ACESYSTEM_MANDATORY_LABEL_ACESYSTEM_SCOPED_POLICY_ID_ACESYSTEM_PROCESS_TRUST_LABEL_ACE
Layout:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | AceHeader | Standard ACE header. |
| 4 | 4 | Mask | Access mask. |
| 8 | variable | Sid | Principal SID. Consumes the remainder of the ACE. |
Parsing rules:
AceSizeMUST be at least 16 bytes (header + mask + minimum SID).- The SID MUST consume the remainder of the ACE exactly.
5.4.2.2 Object ACE family #
The following ACE types share the object-ACE binary layout:
ACCESS_ALLOWED_OBJECT_ACEACCESS_DENIED_OBJECT_ACESYSTEM_AUDIT_OBJECT_ACESYSTEM_ALARM_OBJECT_ACE
Layout:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | AceHeader | Standard ACE header. |
| 4 | 4 | Mask | Access mask. |
| 8 | 4 | Flags | Bitfield describing which GUIDs are present. |
| 12 | 0 or 16 | ObjectType | Present when ACE_OBJECT_TYPE_PRESENT is set. |
| 12 or 28 | 0 or 16 | InheritedObjectType | Present when ACE_INHERITED_OBJECT_TYPE_PRESENT is set. |
| variable | variable | Sid | Principal SID. Begins immediately after the optional GUID fields and consumes the remainder of the ACE. |
Object ACE flags:
| Flag | Value | Description |
|---|---|---|
ACE_OBJECT_TYPE_PRESENT | 0x00000001 | ObjectType GUID is present. |
ACE_INHERITED_OBJECT_TYPE_PRESENT | 0x00000002 | InheritedObjectType GUID is present. |
Parsing rules:
AceSizeMUST be large enough to contain the header, mask, flags, all GUIDs selected byFlags, and a complete SID.- Unknown bits in
FlagsMUST be ignored. - If neither GUID-presence bit is set, the ACE has no GUID fields and behaves like the corresponding basic ACE.
- GUID fields are opaque 16-byte values at this layer. Their interpretation is described in the Peios Kernel TRM §3.8.5.
5.4.2.3 Callback ACE family #
The following ACE types extend the corresponding non-callback ACE layout by
appending ApplicationData at the end of the ACE:
ACCESS_ALLOWED_CALLBACK_ACEACCESS_DENIED_CALLBACK_ACESYSTEM_AUDIT_CALLBACK_ACESYSTEM_ALARM_CALLBACK_ACEACCESS_ALLOWED_CALLBACK_OBJECT_ACEACCESS_DENIED_CALLBACK_OBJECT_ACESYSTEM_AUDIT_CALLBACK_OBJECT_ACESYSTEM_ALARM_CALLBACK_OBJECT_ACE
For non-object callback ACEs, the body layout is:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | AceHeader | Standard ACE header. |
| 4 | 4 | Mask | Access mask. |
| 8 | variable | Sid | Principal SID. |
| variable | variable | ApplicationData | Trailing type-specific bytes. Consumes the remainder of the ACE. |
For callback object ACEs, the body layout is:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | AceHeader | Standard ACE header. |
| 4 | 4 | Mask | Access mask. |
| 8 | 4 | Flags | Object ACE flags. |
| 12 | 0 or 16 | ObjectType | Present when ACE_OBJECT_TYPE_PRESENT is set. |
| 12 or 28 | 0 or 16 | InheritedObjectType | Present when ACE_INHERITED_OBJECT_TYPE_PRESENT is set. |
| variable | variable | Sid | Principal SID. |
| variable | variable | ApplicationData | Trailing type-specific bytes. Consumes the remainder of the ACE. |
Parsing rules:
- The SID begins after the fixed fields and any optional GUIDs, exactly as in the corresponding non-callback ACE family.
ApplicationDataMAY be empty. Semantics for empty or malformed callback payloads are defined by the relevant subsystem.- For conditional ACEs,
ApplicationDatacarries the conditional expression bytecode defined in the Conditional ACE Bytecode Reference.
5.4.2.4 Resource attribute ACE #
SYSTEM_RESOURCE_ATTRIBUTE_ACE uses the single-SID ACE prefix followed by
trailing application data:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | AceHeader | Standard ACE header. |
| 4 | 4 | Mask | Reserved for compatibility. Not used for access decisions. |
| 8 | variable | Sid | MUST be Everyone (S-1-1-0). |
| variable | variable | ApplicationData | One claim entry using §5.9. Consumes the remainder of the ACE. |
Parsing rules:
- The SID MUST be Everyone.
ApplicationDataMUST contain exactly one claim entry using §5.9.
5.4.3 DACL ACE types #
5.4.3.1 Basic ACEs #
| Structure | Value | Effect |
|---|---|---|
| ACCESS_ALLOWED_ACE | 0x00 | Grants the specified rights to the SID. |
| ACCESS_DENIED_ACE | 0x01 | Denies the specified rights to the SID. |
5.4.3.2 Object-type ACEs #
Extend basic ACEs with one or two GUIDs that scope the rule to a specific property or object class. Used for Active Directory access control.
| Structure | Value | Effect |
|---|---|---|
| ACCESS_ALLOWED_OBJECT_ACE | 0x05 | Grants rights scoped to a property/class GUID. |
| ACCESS_DENIED_OBJECT_ACE | 0x06 | Denies rights scoped to a property/class GUID. |
The ObjectType GUID identifies the property or property set the ACE applies to. The InheritedObjectType GUID restricts inheritance to child objects of a specific class. Either or both GUIDs MAY be absent (indicated by a flags field), in which case the ACE behaves like a basic ACE for that dimension.
5.4.3.3 Conditional ACEs #
Extend basic and object-type ACEs with a conditional expression. The ACE only takes effect if the expression evaluates to TRUE against the caller's token attributes and the object's resource attributes.
| Structure | Value | Effect |
|---|---|---|
| ACCESS_ALLOWED_CALLBACK_ACE | 0x09 | Conditional allow. |
| ACCESS_DENIED_CALLBACK_ACE | 0x0A | Conditional deny. |
| ACCESS_ALLOWED_CALLBACK_OBJECT_ACE | 0x0B | Conditional allow, scoped to GUID. |
| ACCESS_DENIED_CALLBACK_OBJECT_ACE | 0x0C | Conditional deny, scoped to GUID. |
5.4.4 SACL ACE types #
5.4.4.1 Audit ACEs #
Trigger audit log entries when matching access attempts occur. The AceFlags field carries SUCCESSFUL_ACCESS_ACE_FLAG (0x40) and/or FAILED_ACCESS_ACE_FLAG (0x80).
| Structure | Value | Effect |
|---|---|---|
| SYSTEM_AUDIT_ACE | 0x02 | Audit access matching the SID and mask. |
| SYSTEM_AUDIT_OBJECT_ACE | 0x07 | Audit access scoped to a GUID. |
| SYSTEM_AUDIT_CALLBACK_ACE | 0x0D | Conditional audit. |
| SYSTEM_AUDIT_CALLBACK_OBJECT_ACE | 0x0F | Conditional audit, scoped to GUID. |
5.4.4.2 Alarm ACEs (continuous auditing) #
| Structure | Value | Effect |
|---|---|---|
| SYSTEM_ALARM_ACE | 0x03 | Continuous audit for matching SID and mask. |
| SYSTEM_ALARM_OBJECT_ACE | 0x08 | Continuous audit scoped to a GUID. |
| SYSTEM_ALARM_CALLBACK_ACE | 0x0E | Conditional continuous audit. |
| SYSTEM_ALARM_CALLBACK_OBJECT_ACE | 0x10 | Conditional continuous audit, scoped to GUID. |
5.4.4.3 Mandatory label ACE #
Defines the object's integrity level for MIC. Conforming producers SHOULD emit at most one non-inherit-only mandatory-label ACE per SACL. Imported or existing SACLs MAY contain multiple mandatory-label ACEs; MIC uses the first non-inherit-only mandatory-label ACE as described in the Peios Kernel TRM §3.8.3. Inherit-only mandatory-label ACEs do not apply to the current object. The SID encodes the integrity level. The access mask encodes the MIC policy (which operations are blocked for non-dominant callers).
| Structure | Value | Effect |
|---|---|---|
| SYSTEM_MANDATORY_LABEL_ACE | 0x11 | Sets the object's integrity level and MIC policy. |
5.4.4.4 Resource attribute ACE #
Attaches name-value attributes to the object for conditional ACE evaluation. The ACE's SID is always Everyone (S-1-1-0).
| Structure | Value | Effect |
|---|---|---|
| SYSTEM_RESOURCE_ATTRIBUTE_ACE | 0x12 | Defines a resource attribute on the object. |
5.4.4.5 Scoped policy ID ACE #
References a central access policy by SID. During AccessCheck, the referenced policy's rules are evaluated in addition to the object's own DACL.
| Structure | Value | Effect |
|---|---|---|
| SYSTEM_SCOPED_POLICY_ID_ACE | 0x13 | References a central access policy. |
5.4.4.6 Process trust label ACE #
Defines the object's PIP trust level. The SID encodes the PIP type and trust level. The access mask specifies the exact rights that non-dominant callers are allowed.
| Structure | Value | Effect |
|---|---|---|
| SYSTEM_PROCESS_TRUST_LABEL_ACE | 0x14 | Sets the object's PIP trust level. |
5.4.5 Allocated but unimplemented ACE types #
Two values in the range have a name but no KACS behaviour.
| Structure | Value | Notes |
|---|---|---|
| ACCESS_ALLOWED_COMPOUND_ACE | 0x04 | Never implemented. Reserved. |
| SYSTEM_ACCESS_FILTER_ACE | 0x15 | Defined by MS-DTYP. Named by the KACS ABI for format parity; no KACS semantics. |
ACCESS_ALLOWED_COMPOUND_ACE was specified and then abandoned before any
conforming system implemented it. Nothing produces it.
SYSTEM_ACCESS_FILTER_ACE is different: it is a live MS-DTYP ACE type
that KACS has not implemented. The kernel ABI defines a constant for it
(KACS_ACE_TYPE_SYSTEM_ACCESS_FILTER) so that a decoder can put a name
to the byte, but no section of this specification assigns it meaning. A 0x15 ACE therefore takes the
unrecognised-ACE path described at the end of this section: skipped
during evaluation, preserved byte-for-byte on round-trip. An
implementation MUST NOT grant, deny, audit, or filter access on the
basis of a 0x15 ACE.
Values above 0x15 are unallocated.
5.4.6 AceType constants #
The tables above name each ACE structure. The AceType header field
carries a constant with its own name, which is what a reader decoding a
descriptor by hand will be holding. Both spellings, in value order:
| Value | AceType constant | Structure |
|---|---|---|
| 0x00 | ACCESS_ALLOWED_ACE_TYPE | ACCESS_ALLOWED_ACE |
| 0x01 | ACCESS_DENIED_ACE_TYPE | ACCESS_DENIED_ACE |
| 0x02 | SYSTEM_AUDIT_ACE_TYPE | SYSTEM_AUDIT_ACE |
| 0x03 | SYSTEM_ALARM_ACE_TYPE | SYSTEM_ALARM_ACE |
| 0x04 | ACCESS_ALLOWED_COMPOUND_ACE_TYPE | ACCESS_ALLOWED_COMPOUND_ACE |
| 0x05 | ACCESS_ALLOWED_OBJECT_ACE_TYPE | ACCESS_ALLOWED_OBJECT_ACE |
| 0x06 | ACCESS_DENIED_OBJECT_ACE_TYPE | ACCESS_DENIED_OBJECT_ACE |
| 0x07 | SYSTEM_AUDIT_OBJECT_ACE_TYPE | SYSTEM_AUDIT_OBJECT_ACE |
| 0x08 | SYSTEM_ALARM_OBJECT_ACE_TYPE | SYSTEM_ALARM_OBJECT_ACE |
| 0x09 | ACCESS_ALLOWED_CALLBACK_ACE_TYPE | ACCESS_ALLOWED_CALLBACK_ACE |
| 0x0A | ACCESS_DENIED_CALLBACK_ACE_TYPE | ACCESS_DENIED_CALLBACK_ACE |
| 0x0B | ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE | ACCESS_ALLOWED_CALLBACK_OBJECT_ACE |
| 0x0C | ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE | ACCESS_DENIED_CALLBACK_OBJECT_ACE |
| 0x0D | SYSTEM_AUDIT_CALLBACK_ACE_TYPE | SYSTEM_AUDIT_CALLBACK_ACE |
| 0x0E | SYSTEM_ALARM_CALLBACK_ACE_TYPE | SYSTEM_ALARM_CALLBACK_ACE |
| 0x0F | SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE | SYSTEM_AUDIT_CALLBACK_OBJECT_ACE |
| 0x10 | SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE | SYSTEM_ALARM_CALLBACK_OBJECT_ACE |
| 0x11 | SYSTEM_MANDATORY_LABEL_ACE_TYPE | SYSTEM_MANDATORY_LABEL_ACE |
| 0x12 | SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE | SYSTEM_RESOURCE_ATTRIBUTE_ACE |
| 0x13 | SYSTEM_SCOPED_POLICY_ID_ACE_TYPE | SYSTEM_SCOPED_POLICY_ID_ACE |
| 0x14 | SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE | SYSTEM_PROCESS_TRUST_LABEL_ACE |
| 0x15 | SYSTEM_ACCESS_FILTER_ACE_TYPE | SYSTEM_ACCESS_FILTER_ACE |
5.4.7 ACL revision #
ACLs carry a revision number that constrains which ACE types MAY appear:
- ACL_REVISION (0x02) — basic ACE types (0x00, 0x01, 0x02, 0x03), mandatory label (0x11), resource attribute (0x12), scoped policy (0x13), and process trust label (0x14).
- ACL_REVISION_DS (0x04) — additionally permits object-type ACEs (0x05–0x08), callback ACEs (0x09–0x0C, 0x0D–0x10). Required for Active Directory access control.
When creating new ACLs containing only recognised ACE types, the revision MUST be set to the minimum required by the ACE types present. When rewriting an existing ACL while preserving one or more unrecognised ACE types, KACS MUST set the revision to the greater of the minimum required by recognised ACE types present and the source ACL revision. When parsing ACLs, KACS MUST NOT reject an ACL based on revision-vs-ACE-type mismatch — accept permissively, write correctly.
Unrecognised ACE types — every value not given semantics above, which
today means 0x04, 0x15, and everything from 0x16 up — MUST be silently
skipped during evaluation and preserved byte-for-byte during round-trip serialisation. The ACE's raw bytes (from AceType through AceType + AceSize) are stored opaquely and written back unchanged. ACEs with AceSize not a multiple of 4 MUST be rejected (the containing ACL is malformed).
5.5 ACE Ordering
Peios / Advanced Peios / PCDS / Security Descriptor
The order of ACEs in a DACL determines the outcome of AccessCheck. AccessCheck walks the DACL from first ACE to last, and the first-writer-wins principle means each bit is decided at most once. ACE ordering is semantically load-bearing.
5.5.1 Canonical ordering #
Tools that author SDs SHOULD produce canonically-ordered DACLs. KACS MUST NOT reject non-canonical DACLs — it evaluates whatever order it receives — but non-canonical ordering can produce results that contradict administrative intent.
The canonical order is:
- Explicit deny ACEs — deny rules placed directly on this object (not inherited).
- Explicit allow ACEs — allow rules placed directly on this object.
- Inherited deny ACEs — deny rules inherited from parent objects (nearest parent first).
- Inherited allow ACEs — allow rules inherited from parent objects.
Within each category, object-type ACEs (those scoped to a specific property GUID) SHOULD be ordered after whole-object ACEs.
This ordering guarantees: explicit rules override inherited rules, denials override allows at the same level, and whole-object rules override property-scoped rules.
KACS kernel paths that evaluate, store, query, or set caller-supplied DACLs MUST preserve the caller-supplied ACE order. KACS MUST NOT canonicalise those DACLs by reordering deny ACEs, allow ACEs, whole-object ACEs, or object-type ACEs.
When KACS constructs a file DACL by combining an explicit source DACL with an inherited source DACL, it MUST emit the explicit ACE sequence before the inherited ACE sequence. Within each sequence, KACS MUST preserve the source-relative ACE order. This recombination rule is not a general canonical sorting step.
5.5.2 SACL ordering #
SACLs do not have a canonical ordering requirement. Audit ACEs are evaluated independently (each matching ACE generates its own audit event). The mandatory label ACE, resource attribute ACEs, and scoped policy ID ACEs are located by type scan, not by position.
5.6 SD Inheritance
Peios / Advanced Peios / PCDS / Security Descriptor
Security Descriptors propagate structurally. When a file is created in a directory, the directory's inheritable ACEs flow down to the new file's SD. This automatic propagation is inheritance.
Inheritance applies to objects with a container/child relationship: directories contain files and subdirectories, registry keys contain subkeys and values. Objects without a container parent (standalone IPC endpoints, tokens, processes) do not inherit.
5.6.1 Inheritance flags #
Four flags in the ACE header's AceFlags field control propagation:
| Flag | Value | Description |
|---|---|---|
| OBJECT_INHERIT_ACE (OI) | 0x01 | Inherited by non-container children (files). For container children (subdirectories), inherited as inherit-only unless NP is also set. |
| CONTAINER_INHERIT_ACE (CI) | 0x02 | Inherited by container children (subdirectories). The inherited ACE remains inheritable (propagates to grandchildren) unless NP is also set. |
| NO_PROPAGATE_INHERIT_ACE (NP) | 0x04 | When inherited, OI and CI flags are cleared on the copy. One-level inheritance. |
| INHERIT_ONLY_ACE (IO) | 0x08 | Does not apply to the object it is attached to. Exists only to be inherited by children. |
A fifth flag records provenance:
| Flag | Value | Description |
|---|---|---|
| INHERITED_ACE | 0x10 | Set on ACEs created through inheritance (not explicitly placed). Determines ordering in canonical form. |
5.6.2 Common flag combinations #
| Flags | Meaning |
|---|---|
| CI | OI | Inherit to everything — containers and non-containers, recursively. |
| CI | Inherit to containers only, recursively. |
| OI | Inherit to non-containers only. Containers receive it as inherit-only. |
| CI | OI | IO | Inherit to everything, but do not apply to this object. |
| CI | OI | NP | Inherit to immediate children only. |
| CI | NP | Inherit to immediate child containers only. |
| (none) | No inheritance. Applies only to this object. |
5.6.3 CREATOR OWNER and CREATOR GROUP #
Two well-known SIDs receive special treatment during inheritance:
-
CREATOR OWNER (
S-1-3-0) — when an ACE with this SID is inherited by a child object, the SID is replaced with the owner SID of the new object (as determined by the owner computation above). -
CREATOR GROUP (
S-1-3-1) — replaced with the primary group SID of the creating principal.
Substitution happens at inheritance time. The resulting ACE on the child contains the resolved SID, not the placeholder.
5.6.4 Inheritance algorithm #
When a new object is created, its SD is computed from up to three sources:
- Parent SD — provides inheritable ACEs.
- Creator SD — an explicit SD provided by the caller (if any).
- Creator token — provides the default owner, primary group, and default DACL.
A creator SD with SE_SERVER_SECURITY set is rejected; see §5.1.
5.6.4.1 Owner #
If the creator SD specifies an owner, use it. Otherwise, use the token's owner SID.
5.6.4.2 Group #
If the creator SD specifies a group, use it. Otherwise, use the token's primary group SID.
5.6.4.3 DACL #
The DACL is computed by merging explicit ACEs from the creator SD with inheritable ACEs from the parent SD:
-
If no creator SD is supplied and the parent has inheritable ACEs: the new object's DACL consists entirely of inherited ACEs from the parent.
-
If no creator SD is supplied and the parent has no inheritable ACEs: the new object's DACL is the token's default DACL. If the token has no default DACL, the new object's DACL is null: SE_DACL_PRESENT is clear and the DACL offset is zero.
-
If a creator SD is supplied but has no DACL (SE_DACL_PRESENT not set): the new object's DACL is computed as if no creator SD was supplied (inherit from parent, or fall back to the token's default DACL, or null DACL if the token has no default DACL).
-
If a creator SD is supplied with a DACL (SE_DACL_PRESENT set):
- Explicit ACEs from the creator SD are preserved.
- If the creator SD's DACL is not protected (SE_DACL_PROTECTED not set) and SE_DACL_AUTO_INHERIT_REQ is set on the creator SD: inheritable ACEs from the parent are appended after the explicit ACEs. If SE_DACL_AUTO_INHERIT_REQ is not set, only the creator's explicit ACEs are used (no parent inheritance).
- If the creator SD's DACL is protected: parent inheritance is blocked. Only the creator's explicit ACEs are used.
In all cases, the resulting DACL is post-processed:
- CREATOR OWNER / CREATOR GROUP SIDs are substituted with the actual owner and group. This substitution applies to the ACE's SID field only. ApplicationData — conditional expression bytecode — is copied verbatim: no SID substitution, no generic mapping, no offset adjustment. An implementation MUST NOT scan ApplicationData for CREATOR OWNER or CREATOR GROUP SIDs.
- Generic rights in all ACEs (both explicit and inherited) are mapped to object-specific rights via the object type's GenericMapping. This ensures no unresolved generic bits persist on stored ACEs. Generic rights appearing inside ApplicationData are not mapped.
- The INHERITED_ACE flag is set on all ACEs that came from the parent.
- If any ACE was inherited from the parent, SE_DACL_AUTO_INHERITED is set on the new SD's control flags; if the DACL came from the token's default DACL instead, SE_DACL_DEFAULTED is set. The equivalent applies to SE_SACL_AUTO_INHERITED for the SACL.
An ACE of an unrecognised type is carried to the child unchanged apart from its AceFlags byte (§5.4). Its mask is not mapped and its SID is not substituted, because neither can be located within an opaque ACE.
5.6.4.4 SACL #
Computed identically to the DACL, substituting SACL for DACL throughout. The token has no "default SACL" — if no creator SACL is supplied and the parent has no inheritable SACL ACEs, the new object has no SACL.
5.6.5 Eager evaluation #
Inheritance is eager. The new object's SD is fully computed at creation time. There is no lazy inheritance — the kernel MUST NOT walk up the directory tree at access time to find inheritable ACEs.
A consequence of eager evaluation: modifying an inheritable ACE on a parent object does not automatically update existing children. Existing children retain the SD they were created with. Propagating the change to descendants is an explicit operation outside the scope of this document. Children with SE_DACL_PROTECTED or SE_SACL_PROTECTED set MUST be skipped during any re-propagation.
5.7 Ownership
Peios / Advanced Peios / PCDS / Security Descriptor
Every SD has an owner. Ownership confers two implicit rights that AccessCheck grants regardless of what the DACL says:
- READ_CONTROL — the owner can always read the object's SD.
- WRITE_DAC — the owner can always modify the object's DACL.
These implicit grants are the "you can't lock yourself out" guarantee. Even if the DACL grants the owner nothing, the owner can read and rewrite the DACL to restore access.
Ownership is determined by SID equality against the caller's user SID or token group SIDs. Deny-only flags affect ACE matching, but they do not change the ownership relation itself.
5.7.1 OWNER RIGHTS (S-1-3-4) #
The implicit READ_CONTROL and WRITE_DAC grants MAY be suppressed or modified by including an ACE for the OWNER RIGHTS SID (S-1-3-4) in the DACL.
When AccessCheck detects any non-inherit-only access-control ACE targeting the OWNER RIGHTS SID in the DACL (via a pre-scan before the DACL walk), the implicit grant is suppressed. The owner's access then comes entirely from the DACL walk — through ACEs matching their user SID, group SIDs, and any ACEs targeting S-1-3-4.
This enables three patterns:
- Suppress owner rights — a deny ACE for S-1-3-4 with READ_CONTROL | WRITE_DAC.
- Expand owner rights — an allow ACE for S-1-3-4 with additional rights beyond the default.
- Restrict owner rights — an allow ACE for S-1-3-4 with only READ_CONTROL (no WRITE_DAC).
The OWNER RIGHTS pre-scan checks only for the presence of any non-inherit-only access-control ACE targeting the OWNER RIGHTS SID in the DACL, not whether any condition on the ACE evaluates to TRUE. A conditional ACE targeting OWNER RIGHTS suppresses the implicit grant even if the condition later evaluates to FALSE.
During the DACL walk, S-1-3-4 is treated as a normal SID. If the caller is the owner, ACEs targeting S-1-3-4 match the caller. The suppression only removes the automatic implicit grant; it does not isolate the owner from the rest of the DACL.
5.7.2 Ownership transfer #
Changing an object's owner requires WRITE_OWNER on the object. Without SeTakeOwnershipPrivilege, the new owner MUST be the caller's own SID or a group on the caller's token with SE_GROUP_OWNER (flag value 0x00000008; see the Peios Kernel TRM §3.2.2).
SeTakeOwnershipPrivilege grants WRITE_OWNER on any object regardless of the DACL (deny-proof, but subject to MIC/PIP). SeRestorePrivilege bypasses the ownership SID constraint entirely — the kacs_set_sd syscall checks for SeRestorePrivilege and, when present, skips the "own SID or SE_GROUP_OWNER group" validation, allowing the caller to set ownership to any arbitrary SID.
5.8 Conditional ACEs
Peios / Advanced Peios / PCDS / Security Descriptor
Standard ACEs match on SID alone. Conditional ACEs add a boolean expression that MUST also evaluate to TRUE for the rule to take effect. This enables attribute-based access control (ABAC).
A conditional ACE is structurally identical to its non-conditional counterpart with a conditional expression appended after the SID. The expression is stored in a binary format defined by MS-DTYP §2.4.4.17.
5.8.1 Three-valued evaluation #
Conditional expressions produce one of three results:
- TRUE — the condition is satisfied.
- FALSE — the condition is not satisfied.
- UNKNOWN — the condition could not be determined (missing attribute, type mismatch, malformed expression).
How the result affects the ACE depends on the ACE type:
| ACE type | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| Allow | ACE takes effect | ACE skipped | ACE skipped |
| Deny | ACE takes effect | ACE skipped | ACE takes effect |
| Audit | Event emitted | Event skipped | Event emitted |
This asymmetry is the fail-safe principle: uncertainty about whether to grant results in no grant; uncertainty about whether to deny results in denial.
5.8.2 Expression language #
The expression supports:
- Relational operators:
==,!=,<,<=,>,>= - Set operators:
Contains,Any_of,Not_Contains,Not_Any_of - Membership operators:
Member_of,Member_of_Any,Not_Member_of,Not_Member_of_Any,Device_Member_of,Device_Member_of_Any,Not_Device_Member_of,Not_Device_Member_of_Any - Logical operators: AND, OR, NOT
- Existence tests:
Exists,Not_Exists - Literal values: integers, strings, SIDs, octet strings, composites
5.8.3 Three-valued logic #
| AND | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | FALSE | UNKNOWN |
| FALSE | FALSE | FALSE | FALSE |
| UNKNOWN | UNKNOWN | FALSE | UNKNOWN |
| OR | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE |
| FALSE | TRUE | FALSE | UNKNOWN |
| UNKNOWN | TRUE | UNKNOWN | UNKNOWN |
NOT: TRUE↔FALSE; UNKNOWN→UNKNOWN.
Boolean coercion for logical operands: integer nonzero → TRUE, zero → FALSE. String non-empty → TRUE, empty → FALSE. NULL → UNKNOWN. SID, octet, composite → UNKNOWN. Literal-origin values (values pushed directly from the bytecode, not obtained via attribute lookup) used as operands in AND/OR/NOT → UNKNOWN for the entire expression.
5.8.4 Attribute sources #
Four attribute namespaces exist, resolved via bytecode opcodes:
| Opcode | Prefix | Source | Description |
|---|---|---|---|
| 0xf9 | @User. | token.user_claims | Token-level claims set by authd at creation. |
| 0xfb | @Device. | token.device_claims | Device-level claims from the device token. |
| 0xfa | @Resource. | SD's SACL resource attribute ACEs | Per-object attributes, extracted in Pre-SACL walk. |
| 0xf8 | @Local. | local_claims parameter to AccessCheck | Per-call contextual attributes passed by the caller. Structured as a KACS claim array of length-prefixed claim entries, using §5.9. |
Attribute names are matched case-insensitively. @User.Clearance, @User.clearance, and @User.CLEARANCE all resolve the same attribute.
5.8.5 Claim flags #
Claim flags apply to token claims (@User., @Device.) and resource attributes (@Resource.). @Local. claims also carry flags.
- DISABLED (0x0010) — the attribute is invisible to all conditions. Resolves as absent.
- USE_FOR_DENY_ONLY (0x0004) — the attribute participates only in deny-side conditional evaluation. Deny-side conditional evaluation includes deny ACE conditions and audit/alarm ACE conditions. For allow ACE conditions, it resolves as absent.
Empty attributes (zero values) are normalised to absent (NULL) at resolution time (when the expression evaluator reads the attribute value during AccessCheck).
Comparisons involving absent attributes evaluate to UNKNOWN. This includes
comparing two absent attributes with == or !=:
@User.Missing == @Device.AlsoMissing is UNKNOWN, not TRUE.
5.8.6 SID matching in expressions #
The Member_of family evaluates group membership on the token. These operators are polarity-aware: deny-only groups do not satisfy allow-ACE conditions. Deny ACE conditions and audit/alarm ACE conditions use deny-side membership polarity, so enabled groups and deny-only groups both participate.
An empty SID operand set uses normal set semantics: Member_of({}) and
Device_Member_of({}) return TRUE, while Member_of_Any({}) and
Device_Member_of_Any({}) return FALSE. The Not_* forms are the logical
inverse of those results.
5.8.7 Binary format #
Conditional expressions are encoded as a stack-based bytecode program in reverse Polish notation. The binary format is defined by MS-DTYP §2.4.4.17.4 and MUST be byte-compatible.
The expression bytecode begins with a 4-byte magic: 0x61 0x72 0x74 0x78 ("artx"). If the magic is absent or the expression is shorter than 4 bytes, evaluation MUST return UNKNOWN.
Evaluation succeeds only if the final stack contains exactly one tri-state result. If evaluation ends with zero entries, more than one entry, or a raw non-boolean value still on the stack, the expression MUST evaluate to UNKNOWN.
The full operator bytecodes and literal encodings are specified in §5.11. KACS implementations MUST be byte-compatible with MS-DTYP §2.4.4.17.4.
5.8.8 Limits #
Implementations SHOULD enforce a maximum evaluation stack depth (recommended: 1024) and SHOULD return UNKNOWN for expressions that exceed it. Any bounds violation during parsing (reading beyond the expression buffer, underflowing the stack, integer overflow) MUST return UNKNOWN.
5.9 Claim Attribute Format
Peios / Advanced Peios / PCDS / Security Descriptor
KACS v0.20 uses a Windows-compatible CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1
entry format for:
- resource attributes in
SYSTEM_RESOURCE_ATTRIBUTE_ACE - token
user_claims - token
device_claims local_claimspassed to AccessCheck
The claim entry format itself is shared across all four surfaces. When multiple
entries are carried in one buffer (token claims or local_claims), KACS wraps
the Windows-compatible entry format in a simple length-prefixed sequence so the
buffer can be parsed deterministically without external metadata.
5.9.1 Supported types #
KACS v0.20 supports these claim value types:
| Type | Value | Notes |
|---|---|---|
INT64 | 0x0001 | Signed 64-bit integer. |
UINT64 | 0x0002 | Unsigned 64-bit integer. |
STRING | 0x0003 | UTF-16LE string. |
SID | 0x0005 | Binary SID. |
BOOLEAN | 0x0006 | Stored as u64; normalised to true/false at resolution time. |
OCTET | 0x0010 | Byte array. |
FQBN (0x0004) is reserved and not supported in KACS v0.20. Any unsupported
claim type makes the containing claim entry invalid.
5.9.2 Entry layout #
All multibyte integers are little-endian. All offsets are relative to the start of the claim entry.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | NameOffset | Offset to the UTF-16LE null-terminated attribute name. |
| 4 | 2 | ValueType | One of the supported claim value types above. |
| 6 | 2 | Reserved | Reserved. Ignored by AccessCheck. Producers SHOULD set to 0. |
| 8 | 4 | Flags | Claim flags. |
| 12 | 4 | ValueCount | Number of values. May be 0. |
| 16 | 4 * ValueCount | ValueOffsets[] | One relative offset per value. Interpretation depends on ValueType. |
Claim flags use the same meanings everywhere this format appears:
| Flag | Value | Meaning |
|---|---|---|
CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE | 0x0002 | String/octet comparisons using this attribute are case-sensitive. |
CLAIM_SECURITY_ATTRIBUTE_USE_FOR_DENY_ONLY | 0x0004 | The attribute is visible only to deny-side conditional evaluation: deny ACE conditions and audit/alarm ACE conditions. |
CLAIM_SECURITY_ATTRIBUTE_DISABLED | 0x0010 | The attribute is invisible to conditional evaluation. |
CLAIM_SECURITY_ATTRIBUTE_MANDATORY | 0x0020 | The attribute MUST NOT be removed or modified by unprivileged callers. kacs_set_sd rejects attempts to remove or modify a MANDATORY attribute unless the caller has SeTcbPrivilege. |
Unknown flag bits are preserved but have no defined semantics.
5.9.3 Value encodings #
5.9.3.1 INT64 / UINT64 / BOOLEAN #
For INT64, UINT64, and BOOLEAN, each ValueOffsets[i] points directly to
an 8-byte scalar:
INT64: signed 64-bit integerUINT64: unsigned 64-bit integerBOOLEAN: unsigned 64-bit integer, normalised at resolution time:0= false- any non-zero value = true
5.9.3.2 STRING #
For STRING, each ValueOffsets[i] points to a 4-byte u32 named
StringOffset. StringOffset then points to the actual UTF-16LE
null-terminated string.
Strings are stored without a separate length field. The terminating UTF-16
null (0x0000) MUST appear within the containing claim entry.
5.9.3.3 SID #
For SID, each ValueOffsets[i] points to a 4-byte u32 named SidOffset.
SidOffset then points to a binary SID in the standard SID wire format.
5.9.3.4 OCTET #
For OCTET, each ValueOffsets[i] points to a 4-byte u32 named
OctetOffset. OctetOffset then points to:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Length | Byte length of the octet string. |
| 4 | Length | Data | Raw bytes. |
5.9.4 Single-entry containers #
SYSTEM_RESOURCE_ATTRIBUTE_ACE.ApplicationData contains exactly one claim
entry and consumes the remainder of the ACE.
5.9.5 Multi-entry containers #
Token claim buffers (user_claims, device_claims) and local_claims use a
KACS claim-array wrapper:
repeat until buffer exhausted:
[entry_len:u32le]
[entry_bytes: entry_len bytes]
Rules:
entry_lenMUST be non-zero.entry_lenMUST fit entirely within the containing buffer.entry_bytesis one complete claim entry using the layout above.- The parser consumes entries sequentially until the containing buffer length is exhausted exactly.
5.9.6 Validation rules #
- The fixed header and
ValueOffsets[]array MUST fit within the entry. - Every offset and nested offset MUST remain within the entry bounds.
- Every string name and string value MUST terminate within the entry.
- Every referenced SID MUST be structurally valid.
- A malformed claim entry invalidates the containing surface:
- malformed resource attribute ACE payload -> malformed SD for AccessCheck
- malformed token claim buffer -> invalid token spec
- malformed
local_claimsbuffer -> invalid AccessCheck input
ValueCount = 0 is valid. Empty attributes normalise to absent at resolution
time, as defined in §5.8.
5.10 Resource Attributes
Peios / Advanced Peios / PCDS / Security Descriptor
A Security Descriptor MAY carry metadata about the object it protects — descriptive properties rather than access rules. These are resource attributes: name-value pairs stored as SYSTEM_RESOURCE_ATTRIBUTE_ACEs in the SACL.
Resource attributes do not grant or deny access. They exist so that conditional ACEs in the DACL can reference properties of the object during evaluation. A conditional allow ACE might say "grant read access if @User.clearance >= @Resource.confidentiality."
Each resource attribute ACE encodes a single named, typed, multi-valued attribute. The name is a string. Values MAY be integers, strings, booleans, SIDs, or byte arrays. The attribute data uses the claim entry format defined in §5.9.
Multiple resource attribute ACEs MAY appear in the same SACL, each carrying a different attribute. If two ACEs carry the same attribute name, the first one wins — duplicates are silently ignored. Name comparison is case-insensitive, matching the conditional expression evaluator's attribute name matching.
An inherit-only SYSTEM_RESOURCE_ATTRIBUTE_ACE does not apply to the object it
is attached to and MUST be ignored during resource-attribute extraction.
Resource attributes are extracted from the SACL before the DACL walk begins, so they are available when conditional expressions need them.
A resource attribute marked CLAIM_SECURITY_ATTRIBUTE_MANDATORY is protected
metadata. Set-security operations MUST preserve each mandatory resource
attribute unless the caller has SeTcbPrivilege, as described in the Peios
Kernel TRM §3.4.2.
5.10.1 Claim types #
| Type | Value | Description |
|---|---|---|
| INT64 | 0x0001 | Signed 64-bit integer. |
| UINT64 | 0x0002 | Unsigned 64-bit integer. |
| STRING | 0x0003 | Unicode string. |
| FQBN | 0x0004 | Fully Qualified Binary Name. Reserved — not supported in KACS v0.20. |
| SID | 0x0005 | Security identifier. |
| BOOLEAN | 0x0006 | Boolean value. |
| OCTET | 0x0010 | Byte array. |
Boolean values MUST be normalised to 1 (true) or 0 (false) at resolution time (when the conditional expression evaluator reads the attribute value), regardless of the wire encoding.
5.11 Conditional ACE Bytecode Reference
Peios / Advanced Peios / PCDS / Security Descriptor
This section specifies the binary encoding for conditional ACE expressions. The format is byte-compatible with MS-DTYP §2.4.4.17.4. Conditional expressions are stored in the ApplicationData member of CALLBACK ACE types, encoded in postfix (reverse Polish) notation.
5.11.1 Magic signature #
A CALLBACK ACE contains a conditional expression if the ApplicationData begins with 0x61 0x72 0x74 0x78 (the string "artx"). If the signature is absent or the expression is shorter than 4 bytes, evaluation MUST return UNKNOWN.
5.11.2 Token formats #
Each token begins with a single byte-code identifying the token type. All multibyte integers, including Unicode characters, are stored least-significant byte first (little-endian). Expressions end at the ACE boundary; any bytes needed for DWORD alignment MUST be set to 0x00.
5.11.3 Literal tokens #
| Token type | Byte-code | Token data encoding |
|---|---|---|
| Padding | 0x00 | No data. Used for DWORD alignment padding at end of expression. |
| Signed int8 | 0x01 | 1 QWORD (8 bytes LE) for the value (2's complement, range -128 to +127). 1 byte for sign. 1 byte for base. Total: 10 bytes. |
| Signed int16 | 0x02 | 1 QWORD (8 bytes LE) for the value (2's complement, range -32768 to +32767). 1 byte for sign. 1 byte for base. Total: 10 bytes. |
| Signed int32 | 0x03 | 1 QWORD (8 bytes LE) for the value (2's complement). 1 byte for sign. 1 byte for base. Total: 10 bytes. |
| Signed int64 | 0x04 | 1 QWORD (8 bytes LE) for the value (2's complement). 1 byte for sign. 1 byte for base. Total: 10 bytes. |
| Unicode string | 0x10 | 1 DWORD (4 bytes LE) for length in bytes. Then UTF-16LE code units (2 bytes each, LSB first). Not null-terminated. |
| Octet string | 0x18 | 1 DWORD (4 bytes LE) for length in bytes. Then raw bytes. |
| Composite | 0x50 | 1 DWORD (4 bytes LE) for total length in bytes of all contained elements. Then elements stored contiguously, each encoded per its own type rules. May be heterogeneous. |
| SID | 0x51 | 1 DWORD (4 bytes LE) for length in bytes. Then SID in binary representation (revision, sub-authority count, identifier authority, sub-authorities). |
5.11.3.1 Sign codes #
Integer literals include a sign byte after the QWORD value:
| Sign | Code | Description |
|---|---|---|
| + | 0x01 | Explicit positive sign. |
| - | 0x02 | Negative. |
| None | 0x03 | No sign. Relational operators treat as positive. |
During relational evaluation, the sign byte determines the literal sign. Positive (0x01) and no-sign (0x03) literals MUST be evaluated as the positive magnitude of the QWORD value. Negative (0x02) literals MUST be evaluated as the negative magnitude of the QWORD value. If the resulting signed value does not fit the declared signed-width token, evaluation MUST return UNKNOWN.
5.11.3.2 Base codes #
Integer literals include a base byte after the sign byte. The base is for display purposes only — the value is always stored as binary 2's complement regardless of base:
| Base | Code | Description |
|---|---|---|
| Octal | 0x01 | Display as octal. |
| Decimal | 0x02 | Display as decimal. |
| Hexadecimal | 0x03 | Display as hexadecimal. |
5.11.3.3 Integer encoding example #
The decimal value -1 encoded as a signed int64:
0x04 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x02 0x02
^ ^-------- QWORD (2's complement) --------^ ^ ^
| | base=decimal
byte-code=int64 sign=negative
5.11.4 Relational operator tokens #
5.11.4.1 Binary relational operators #
LHS is the second element on the stack, RHS is the top. If LHS and RHS are different types, the entire conditional expression evaluates to UNKNOWN — with the exception of INT64 and UINT64 which are promoted for comparison (see §5.8 for promotion rules). If either operand is UNKNOWN, the operation returns UNKNOWN.
| Token type | Byte-code | Processing |
|---|---|---|
| == | 0x80 | TRUE if RHS equals LHS (single or set value); FALSE otherwise. |
| != | 0x81 | FALSE if RHS equals LHS; TRUE otherwise. |
| < | 0x82 | TRUE if LHS < RHS; FALSE otherwise. |
| <= | 0x83 | TRUE if LHS <= RHS; FALSE otherwise. |
| > | 0x84 | TRUE if LHS > RHS; FALSE otherwise. |
| >= | 0x85 | TRUE if LHS >= RHS; FALSE otherwise. |
| Contains | 0x86 | TRUE if LHS value(s) include all of RHS value(s); FALSE otherwise. |
| Any_of | 0x88 | TRUE if RHS includes any of LHS value(s); FALSE otherwise. |
| Not_Contains | 0x8e | Logical inverse of Contains. |
| Not_Any_of | 0x8f | Logical inverse of Any_of. |
String and octet string comparisons are byte-by-byte, case-insensitive by default. If the CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE flag (0x0002) is set on either operand's attribute, comparison is case-sensitive.
5.11.4.2 Unary relational operators (SID membership) #
The operand is the top of the stack and MUST be a SID literal or a composite of SID literals.
| Token type | Byte-code | Processing |
|---|---|---|
| Member_of | 0x89 | TRUE if the token's group SIDs contain all SIDs in the operand. |
| Device_Member_of | 0x8a | TRUE if the token's device group SIDs contain all SIDs in the operand. |
| Member_of_Any | 0x8b | TRUE if the token's group SIDs contain any SID in the operand. |
| Device_Member_of_Any | 0x8c | TRUE if the token's device group SIDs contain any SID in the operand. |
| Not_Member_of | 0x90 | Logical inverse of Member_of. |
| Not_Device_Member_of | 0x91 | Logical inverse of Device_Member_of. |
| Not_Member_of_Any | 0x92 | Logical inverse of Member_of_Any. |
| Not_Device_Member_of_Any | 0x93 | Logical inverse of Device_Member_of_Any. |
For an empty SID operand set:
Member_of({})andDevice_Member_of({})return TRUE (vacuous truth).Member_of_Any({})andDevice_Member_of_Any({})return FALSE.- The
Not_*forms are the logical inverse of those results.
5.11.5 Logical operator tokens #
Logical operators test the logical value of operands and produce TRUE, FALSE, or UNKNOWN. The logical value of an operand is determined by:
- Literal-origin value → error (entire expression returns UNKNOWN)
- Attribute with null value → UNKNOWN
- Attribute with integer value → TRUE if nonzero, FALSE if zero
- Attribute with string value → TRUE if non-empty, FALSE if empty
- Result value → the result's tri-state value
5.11.5.1 Unary logical operators #
| Token type | Byte-code | Processing |
|---|---|---|
| Exists | 0x87 | TRUE if the operand is an attribute (@Local., @Resource., @User., or @Device.) with a non-null value. FALSE if the attribute is absent or null. Returns error (→ UNKNOWN) for literal operands. KACS divergence: extends Exists to all four namespaces (MS-DTYP restricts to Local/Resource only). |
| Not_Exists | 0x8d | Logical inverse of Exists. |
| NOT (!) | 0xa2 | TRUE→FALSE, FALSE→TRUE, UNKNOWN→UNKNOWN. |
5.11.5.2 Binary logical operators #
LHS is the second element on the stack, RHS is the top.
| Token type | Byte-code | Processing |
|---|---|---|
| AND (&&) | 0xa0 | If either operand is FALSE, return FALSE. Else if either is UNKNOWN, return UNKNOWN. Else return TRUE. |
| OR (||) | 0xa1 | If either operand is TRUE, return TRUE. Else if either is UNKNOWN, return UNKNOWN. Else return FALSE. |
5.11.6 Attribute reference tokens #
Attribute names are encoded as Unicode strings (same format as the 0x10 literal: DWORD length + UTF-16LE code units). The byte-code determines which namespace to look up the attribute in.
Attribute lookup is case-insensitive: the encoded name is matched against the namespace's attribute names without regard to case.
| Token type | Byte-code | Namespace |
|---|---|---|
| @Local. | 0xf8 | Local claims (passed as AccessCheck parameter). |
| @User. | 0xf9 | User claims (from token.user_claims). |
| @Resource. | 0xfa | Resource attributes (from SACL resource attribute ACEs). |
| @Device. | 0xfb | Device claims (from token.device_claims). |
5.11.7 Complete byte-code summary #
For quick reference, all byte-codes in numeric order:
| Byte-code | Token |
|---|---|
| 0x00 | Padding |
| 0x01 | Signed int8 literal |
| 0x02 | Signed int16 literal |
| 0x03 | Signed int32 literal |
| 0x04 | Signed int64 literal |
| 0x10 | Unicode string literal |
| 0x18 | Octet string literal |
| 0x50 | Composite literal |
| 0x51 | SID literal |
| 0x80 | == |
| 0x81 | != |
| 0x82 | < |
| 0x83 | <= |
| 0x84 | > |
| 0x85 | >= |
| 0x86 | Contains |
| 0x87 | Exists |
| 0x88 | Any_of |
| 0x89 | Member_of |
| 0x8a | Device_Member_of |
| 0x8b | Member_of_Any |
| 0x8c | Device_Member_of_Any |
| 0x8d | Not_Exists |
| 0x8e | Not_Contains |
| 0x8f | Not_Any_of |
| 0x90 | Not_Member_of |
| 0x91 | Not_Device_Member_of |
| 0x92 | Not_Member_of_Any |
| 0x93 | Not_Device_Member_of_Any |
| 0xa0 | AND (&&) |
| 0xa1 | OR (||) |
| 0xa2 | NOT (!) |
| 0xf8 | @Local. attribute |
| 0xf9 | @User. attribute |
| 0xfa | @Resource. attribute |
| 0xfb | @Device. attribute |
KACS implementations MUST be byte-compatible with these encodings.
Appendix 5.A Prior Art
Peios / Advanced Peios / PCDS / Security Descriptor
5.A.1 MS-DTYP #
The security descriptor family defined in this chapter derives from the Microsoft Data Types specification (MS-DTYP): the self-relative security descriptor (§2.4.6), the ACL and ACE binary formats, the CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 claim entry, and the conditional expression bytecode (§2.4.4.17). Peios keeps the binary formats byte-compatible so security descriptors round-trip with Windows systems — over the wire and on NTFS volumes.
Deliberate divergences from the reference model are flagged inline as notes where they occur: permissive ACL-revision parsing, the repurposing of alarm ACEs for continuous auditing, evaluation-time generic mapping, the extension of Exists to all four attribute namespaces, INT64/UINT64 promotion in relational operators, and virtual-group visibility in Member_of.