Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Peios

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:

OffsetSizeFieldType
04Data1uint32, little-endian
42Data2uint16, little-endian
62Data3uint16, little-endian
88Data4uint8[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.

Note

This is the mixed-endian layout inherited from DCE RPC and MS-DTYP. The first three fields follow the platform's native byte order (little-endian on x86), while Data4 is a byte sequence with no integer 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:

GroupDigitsSource
18Data1, most significant nibble first
24Data2, most significant nibble first
34Data3, most significant nibble first
44Data4[0] and Data4[1], in byte order
512Data4[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.

Note

Example. Given the 16 bytes (in storage order):

04 03 02 01  06 05  08 07  09 0a  0b 0c 0d 0e 0f 10

Data1 = 0x01020304 (little-endian), Data2 = 0x0506, Data3 = 0x0708. The string representation is:

{01020304-0506-0708-090a-0b0c0d0e0f10}

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.

Note

Byte-for-byte comparison of the binary form avoids case sensitivity and brace-presence issues that would arise from comparing 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:

  1. Fill all 16 bytes with cryptographically random data.
  2. Set the four most significant bits of Data3 to 0100 (version 4).
  3. 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.

Note

In terms of bit manipulation on the binary layout:

Data3 = (Data3 & 0x0fff) | 0x4000
Data4[0] = (Data4[0] & 0x3f) | 0x80

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:

OffsetSizeFieldType
04LowPartuint32, little-endian
44HighPartuint32, 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.

Note

MS-DTYP defines HighPart as a signed 32-bit integer (LONG). Peios uses unsigned uint32 for both fields. The signed type in MS-DTYP is a Win32 API convention with no semantic purpose — LUID values are never negative. See §3.A for the full divergence rationale.

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.

Note

Subsystems that define well-known LUID values (such as privilege identifiers) typically reserve values below the allocation starting point. The starting value should be chosen to leave room for current and future well-known values.

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:

OffsetSizeFieldDescription
01RevisionMUST be 1.
11SubAuthorityCountNumber of sub-authorities. MUST be between 0 and 15 inclusive.
26IdentifierAuthorityA 6-byte big-endian value identifying the authority that issued the SID.
84 × SubAuthorityCountSubAuthority[]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.

Note

The IdentifierAuthority is the one big-endian field among the structures in this document — an MS-DTYP inheritance. The sub-authorities that follow it are ordinary little-endian integers.

4.2 String Format

Peios / Advanced Peios / PCDS / SID

SIDs are represented in string form as:

S-1-{authority}-{sub1}-{sub2}-...-{subN}

Where:

  • S is a literal prefix.
  • 1 is 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 with 0x.
  • Each {subN} is the decimal representation of the corresponding 32-bit sub-authority.

Note

Example SIDs: S-1-5-18 (Local System), S-1-5-32-544 (BUILTIN\Administrators), S-1-5-21-3623811015-3361044348-30300820-1013 (a domain user). The meanings of well-known SID values are catalogued in §4.4.

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 #

SIDNameDescription
S-1-0-0NobodyThe null SID. No principal.
S-1-1-0EveryoneMatches all principals, including anonymous.
S-1-2-0LocalPrincipals that log on locally (physically).
S-1-2-1Console LogonPrincipals that log on via the physical console.

4.4.2 Creator authorities #

SIDNameDescription
S-1-3-0Creator OwnerPlaceholder in inheritable ACEs. Replaced with the creating principal's SID during inheritance.
S-1-3-1Creator GroupPlaceholder in inheritable ACEs. Replaced with the creating principal's primary group SID during inheritance.
S-1-3-4Owner RightsWhen 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) #

SIDNameDescription
S-1-5-2NetworkPrincipals that authenticated over the network.
S-1-5-4InteractivePrincipals that logged on interactively.
S-1-5-6ServicePrincipals that authenticated as a service.
S-1-5-7AnonymousThe anonymous identity. Carried by tokens at Anonymous impersonation level.
S-1-5-10Principal SelfPlaceholder 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-11Authenticated UsersAll principals that have been authenticated (excludes Anonymous).
S-1-5-18Local System (SYSTEM)The operating system's own identity. Highest privilege level.
S-1-5-19Local ServiceA built-in service account with reduced privileges.
S-1-5-20Network ServiceA built-in service account that can authenticate to remote services.

4.4.4 Logon SIDs #

SIDNameDescription
S-1-5-5-X-YLogon SIDA 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) #

SIDNameDescription
S-1-5-32-544BUILTIN\AdministratorsThe built-in administrators group.
S-1-5-32-545BUILTIN\UsersThe built-in users group.
S-1-5-32-546BUILTIN\GuestsThe built-in guests group.
S-1-5-32-551BUILTIN\Backup OperatorsMembers can bypass file security for backup and restore.

Note

Additional BUILTIN SIDs (S-1-5-32-547 through S-1-5-32-583) are defined by Active Directory. KACS does not assign special semantics to these SIDs — they participate in normal ACE matching like any other group SID. Their meaning is an administrative convention, not a kernel enforcement property.

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.

RIDNameDescription
500Domain AdministratorThe built-in administrator account.
501Domain GuestThe built-in guest account.
512Domain AdminsThe domain administrators group.
513Domain UsersThe domain users group.
514Domain GuestsThe domain guests group.
515Domain ComputersComputer accounts in the domain.

Note

Domain SIDs are assigned by the domain controller and replicated through Active Directory. KACS does not create or manage domain SIDs — it evaluates them as opaque binary values during AccessCheck.

4.4.7 Mandatory integrity labels (S-1-16) #

SIDNameNumeric levelDescription
S-1-16-0Untrusted0Lowest trust. Sandboxed or experimental code.
S-1-16-4096Low4096Reduced trust. Services handling untrusted input.
S-1-16-8192Medium8192Standard trust. Default for interactive logons and most services.
S-1-16-12288High12288Elevated administrative logons.
S-1-16-16384System16384The 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) #

SIDNameDescription
S-1-19-0-0None / No trustDefault for unsigned processes.
S-1-19-512-1024Protected, AuthenticodeThird-party signed binaries.
S-1-19-512-1536Protected, AntiMalwareSecurity tooling.
S-1-19-512-2048Protected, AppPeios-distributed applications.
S-1-19-512-4096Protected, PeiosCore Peios components.
S-1-19-512-8192Protected, PeiosTcbPeios Trusted Computing Base.
S-1-19-1024-8192Isolated, PeiosTcbMaximum 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 = None
  • 512 = Protected
  • 1024 = 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) #

SIDNameDescription
S-1-15-2-hashConfinement SIDIdentifies a confined application. The sub-authorities are derived from the application identity.
S-1-15-2-1ALL_APPLICATION_PACKAGESMatches all confined applications in normal confinement mode.
S-1-15-2-2ALL_RESTRICTED_APPLICATION_PACKAGESMatches 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) #

SIDNameDescription
S-1-15-3-1internetClientOutbound internet access.
S-1-15-3-2internetClientServerInbound and outbound internet access.
S-1-15-3-3privateNetworkClientServerLAN/private network access.
S-1-15-3-8enterpriseAuthenticationDomain credential access.
S-1-15-3-9sharedUserCertificatesCertificate store access.
S-1-15-3-10removableStorageRemovable 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_sd cases 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.

OffsetSizeFieldDescription
01RevisionMUST be 1.
11Sbz1Reserved. 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.
22ControlControl flags, little-endian.
44OffsetOwnerOffset to the owner SID, little-endian. 0 if absent.
84OffsetGroupOffset to the group SID, little-endian. 0 if absent.
124OffsetSaclOffset to the SACL, little-endian. 0 if absent.
164OffsetDaclOffset 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:

FlagBitValueDescription
SE_OWNER_DEFAULTED (OD)00x0001The owner was established by default means.
SE_GROUP_DEFAULTED (GD)10x0002The group was established by default means.
SE_DACL_PRESENT (DP)20x0004A DACL is present. If clear, AccessCheck treats the DACL as null (grants all access).
SE_DACL_DEFAULTED (DD)30x0008The DACL was established by default means.
SE_SACL_PRESENT (SP)40x0010A SACL is present.
SE_SACL_DEFAULTED (SD)50x0020The SACL was established by default means.
SE_DACL_TRUSTED (DT)60x0040Reserved metadata. Preserved during round-trip serialisation. No operational semantics.
SE_SERVER_SECURITY (SS)70x0080Create a server ACL based on the input ACL.
SE_DACL_AUTO_INHERIT_REQ (AR)80x0100Requests 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_REQ90x0200Requests that the SACL be auto-inherited from the parent. Same semantics as bit 8 for the SACL.
SE_DACL_AUTO_INHERITED (DI)100x0400The DACL was created through automatic inheritance.
SE_SACL_AUTO_INHERITED (SI)110x0800The SACL was created through automatic inheritance.
SE_DACL_PROTECTED (PD)120x1000The DACL is protected from inheritance. Inheritable ACEs from parent objects MUST NOT be merged.
SE_SACL_PROTECTED (PS)130x2000The SACL is protected from inheritance.
SE_RM_CONTROL_VALID (RM)140x4000The Sbz1 byte is interpreted as resource manager control bits.
SE_SELF_RELATIVE (SR)150x8000The 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:

OffsetSizeFieldDescription
01AclRevisionACL revision number. Determines which ACE families the ACL formally permits.
11Sbz1Reserved. Preserved for compatibility but not interpreted by KACS.
22AclSizeTotal size of the ACL in bytes, including the 8-byte header. Little-endian.
42AceCountNumber of ACEs in the ACL. Little-endian.
62Sbz2Reserved. 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 #

  • AclSize MUST be at least 8 bytes.
  • AclSize MUST NOT exceed the containing buffer.
  • The ACL body MUST contain exactly AceCount ACEs within the declared AclSize.
  • Truncated ACEs, ACE overruns, or leftover bytes within AclSize are malformed.
  • The architectural maximum ACL size is 64 KB because AclSize is 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:

BitNameValueMeaning
16DELETE0x00010000Delete the object.
17READ_CONTROL0x00020000Read the object's SD (excluding SACL).
18WRITE_DAC0x00040000Modify the object's DACL.
19WRITE_OWNER0x00080000Change the object's owner.
20SYNCHRONIZE0x00100000Wait on the object.

5.3.1.3 Special rights (bits 24–25) #

BitNameValueMeaning
24ACCESS_SYSTEM_SECURITY0x01000000Read or write the SACL. Requires SeSecurityPrivilege.
25MAXIMUM_ALLOWED0x02000000Not 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:

BitNameValue
28GENERIC_ALL0x10000000
29GENERIC_EXECUTE0x20000000
30GENERIC_WRITE0x40000000
31GENERIC_READ0x80000000

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:

FieldDescription
readSpecific + standard bits that GENERIC_READ maps to.
writeSpecific + standard bits that GENERIC_WRITE maps to.
executeSpecific + standard bits that GENERIC_EXECUTE maps to.
allSpecific + 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.

Note

KACS maps ACE masks via GenericMapping at evaluation time (using a local variable — the ACE itself is never mutated). This is an intentional divergence: the reference model expects ACE masks to be pre-mapped at SD construction time. Defensive mapping at evaluation time handles two cases: (1) imported/external SDs that may contain unresolved generic bits, and (2) central access policy recovery ACEs that use GENERIC_ALL. Note: SD inheritance also maps generic bits on all ACEs when computing a child SD (see §5.6), so newly created SDs will not contain generic bits. The evaluation-time mapping is defense-in-depth for SDs from other sources.

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:

OffsetSizeFieldDescription
01AceTypeIdentifies the ACE type.
11AceFlagsInheritance and audit flags.
22AceSizeTotal 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_ACE
  • ACCESS_DENIED_ACE
  • SYSTEM_AUDIT_ACE
  • SYSTEM_ALARM_ACE
  • SYSTEM_MANDATORY_LABEL_ACE
  • SYSTEM_SCOPED_POLICY_ID_ACE
  • SYSTEM_PROCESS_TRUST_LABEL_ACE

Layout:

OffsetSizeFieldDescription
04AceHeaderStandard ACE header.
44MaskAccess mask.
8variableSidPrincipal SID. Consumes the remainder of the ACE.

Parsing rules:

  • AceSize MUST 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_ACE
  • ACCESS_DENIED_OBJECT_ACE
  • SYSTEM_AUDIT_OBJECT_ACE
  • SYSTEM_ALARM_OBJECT_ACE

Layout:

OffsetSizeFieldDescription
04AceHeaderStandard ACE header.
44MaskAccess mask.
84FlagsBitfield describing which GUIDs are present.
120 or 16ObjectTypePresent when ACE_OBJECT_TYPE_PRESENT is set.
12 or 280 or 16InheritedObjectTypePresent when ACE_INHERITED_OBJECT_TYPE_PRESENT is set.
variablevariableSidPrincipal SID. Begins immediately after the optional GUID fields and consumes the remainder of the ACE.

Object ACE flags:

FlagValueDescription
ACE_OBJECT_TYPE_PRESENT0x00000001ObjectType GUID is present.
ACE_INHERITED_OBJECT_TYPE_PRESENT0x00000002InheritedObjectType GUID is present.

Parsing rules:

  • AceSize MUST be large enough to contain the header, mask, flags, all GUIDs selected by Flags, and a complete SID.
  • Unknown bits in Flags MUST 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_ACE
  • ACCESS_DENIED_CALLBACK_ACE
  • SYSTEM_AUDIT_CALLBACK_ACE
  • SYSTEM_ALARM_CALLBACK_ACE
  • ACCESS_ALLOWED_CALLBACK_OBJECT_ACE
  • ACCESS_DENIED_CALLBACK_OBJECT_ACE
  • SYSTEM_AUDIT_CALLBACK_OBJECT_ACE
  • SYSTEM_ALARM_CALLBACK_OBJECT_ACE

For non-object callback ACEs, the body layout is:

OffsetSizeFieldDescription
04AceHeaderStandard ACE header.
44MaskAccess mask.
8variableSidPrincipal SID.
variablevariableApplicationDataTrailing type-specific bytes. Consumes the remainder of the ACE.

For callback object ACEs, the body layout is:

OffsetSizeFieldDescription
04AceHeaderStandard ACE header.
44MaskAccess mask.
84FlagsObject ACE flags.
120 or 16ObjectTypePresent when ACE_OBJECT_TYPE_PRESENT is set.
12 or 280 or 16InheritedObjectTypePresent when ACE_INHERITED_OBJECT_TYPE_PRESENT is set.
variablevariableSidPrincipal SID.
variablevariableApplicationDataTrailing 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.
  • ApplicationData MAY be empty. Semantics for empty or malformed callback payloads are defined by the relevant subsystem.
  • For conditional ACEs, ApplicationData carries 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:

OffsetSizeFieldDescription
04AceHeaderStandard ACE header.
44MaskReserved for compatibility. Not used for access decisions.
8variableSidMUST be Everyone (S-1-1-0).
variablevariableApplicationDataOne claim entry using §5.9. Consumes the remainder of the ACE.

Parsing rules:

  • The SID MUST be Everyone.
  • ApplicationData MUST contain exactly one claim entry using §5.9.

5.4.3 DACL ACE types #

5.4.3.1 Basic ACEs #

StructureValueEffect
ACCESS_ALLOWED_ACE0x00Grants the specified rights to the SID.
ACCESS_DENIED_ACE0x01Denies 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.

StructureValueEffect
ACCESS_ALLOWED_OBJECT_ACE0x05Grants rights scoped to a property/class GUID.
ACCESS_DENIED_OBJECT_ACE0x06Denies 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.

StructureValueEffect
ACCESS_ALLOWED_CALLBACK_ACE0x09Conditional allow.
ACCESS_DENIED_CALLBACK_ACE0x0AConditional deny.
ACCESS_ALLOWED_CALLBACK_OBJECT_ACE0x0BConditional allow, scoped to GUID.
ACCESS_DENIED_CALLBACK_OBJECT_ACE0x0CConditional deny, scoped to GUID.

Note

The term "callback" is historical. KACS evaluates conditional expressions inline during AccessCheck. The name is preserved for binary format compatibility. UI and UX layers MAY refer to callback ACEs exclusively as "conditional ACEs" for simplicity.

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).

StructureValueEffect
SYSTEM_AUDIT_ACE0x02Audit access matching the SID and mask.
SYSTEM_AUDIT_OBJECT_ACE0x07Audit access scoped to a GUID.
SYSTEM_AUDIT_CALLBACK_ACE0x0DConditional audit.
SYSTEM_AUDIT_CALLBACK_OBJECT_ACE0x0FConditional audit, scoped to GUID.

5.4.4.2 Alarm ACEs (continuous auditing) #

Note

These ACE types are reserved but unimplemented in the reference model. KACS repurposes them for continuous per-operation auditing: unlike standard audit ACEs (which emit a single event at handle creation), alarm ACEs configure per-operation audit masks that persist on the open handle. This creates no interoperability conflict because external sources never contain alarm ACEs.

StructureValueEffect
SYSTEM_ALARM_ACE0x03Continuous audit for matching SID and mask.
SYSTEM_ALARM_OBJECT_ACE0x08Continuous audit scoped to a GUID.
SYSTEM_ALARM_CALLBACK_ACE0x0EConditional continuous audit.
SYSTEM_ALARM_CALLBACK_OBJECT_ACE0x10Conditional 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).

StructureValueEffect
SYSTEM_MANDATORY_LABEL_ACE0x11Sets 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).

StructureValueEffect
SYSTEM_RESOURCE_ATTRIBUTE_ACE0x12Defines 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.

StructureValueEffect
SYSTEM_SCOPED_POLICY_ID_ACE0x13References 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.

StructureValueEffect
SYSTEM_PROCESS_TRUST_LABEL_ACE0x14Sets 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.

StructureValueNotes
ACCESS_ALLOWED_COMPOUND_ACE0x04Never implemented. Reserved.
SYSTEM_ACCESS_FILTER_ACE0x15Defined 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:

ValueAceType constantStructure
0x00ACCESS_ALLOWED_ACE_TYPEACCESS_ALLOWED_ACE
0x01ACCESS_DENIED_ACE_TYPEACCESS_DENIED_ACE
0x02SYSTEM_AUDIT_ACE_TYPESYSTEM_AUDIT_ACE
0x03SYSTEM_ALARM_ACE_TYPESYSTEM_ALARM_ACE
0x04ACCESS_ALLOWED_COMPOUND_ACE_TYPEACCESS_ALLOWED_COMPOUND_ACE
0x05ACCESS_ALLOWED_OBJECT_ACE_TYPEACCESS_ALLOWED_OBJECT_ACE
0x06ACCESS_DENIED_OBJECT_ACE_TYPEACCESS_DENIED_OBJECT_ACE
0x07SYSTEM_AUDIT_OBJECT_ACE_TYPESYSTEM_AUDIT_OBJECT_ACE
0x08SYSTEM_ALARM_OBJECT_ACE_TYPESYSTEM_ALARM_OBJECT_ACE
0x09ACCESS_ALLOWED_CALLBACK_ACE_TYPEACCESS_ALLOWED_CALLBACK_ACE
0x0AACCESS_DENIED_CALLBACK_ACE_TYPEACCESS_DENIED_CALLBACK_ACE
0x0BACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPEACCESS_ALLOWED_CALLBACK_OBJECT_ACE
0x0CACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPEACCESS_DENIED_CALLBACK_OBJECT_ACE
0x0DSYSTEM_AUDIT_CALLBACK_ACE_TYPESYSTEM_AUDIT_CALLBACK_ACE
0x0ESYSTEM_ALARM_CALLBACK_ACE_TYPESYSTEM_ALARM_CALLBACK_ACE
0x0FSYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPESYSTEM_AUDIT_CALLBACK_OBJECT_ACE
0x10SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPESYSTEM_ALARM_CALLBACK_OBJECT_ACE
0x11SYSTEM_MANDATORY_LABEL_ACE_TYPESYSTEM_MANDATORY_LABEL_ACE
0x12SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPESYSTEM_RESOURCE_ATTRIBUTE_ACE
0x13SYSTEM_SCOPED_POLICY_ID_ACE_TYPESYSTEM_SCOPED_POLICY_ID_ACE
0x14SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPESYSTEM_PROCESS_TRUST_LABEL_ACE
0x15SYSTEM_ACCESS_FILTER_ACE_TYPESYSTEM_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.

Note

This diverges from strict MS-DTYP interpretation, where ACL_REVISION (0x02) does not permit SACL types 0x11-0x14. KACS accepts them under either revision to handle SDs that may have been constructed with a less strict tool. The evaluator handles all ACE types correctly regardless of ACL revision.

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:

  1. Explicit deny ACEs — deny rules placed directly on this object (not inherited).
  2. Explicit allow ACEs — allow rules placed directly on this object.
  3. Inherited deny ACEs — deny rules inherited from parent objects (nearest parent first).
  4. 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:

FlagValueDescription
OBJECT_INHERIT_ACE (OI)0x01Inherited by non-container children (files). For container children (subdirectories), inherited as inherit-only unless NP is also set.
CONTAINER_INHERIT_ACE (CI)0x02Inherited by container children (subdirectories). The inherited ACE remains inheritable (propagates to grandchildren) unless NP is also set.
NO_PROPAGATE_INHERIT_ACE (NP)0x04When inherited, OI and CI flags are cleared on the copy. One-level inheritance.
INHERIT_ONLY_ACE (IO)0x08Does not apply to the object it is attached to. Exists only to be inherited by children.

A fifth flag records provenance:

FlagValueDescription
INHERITED_ACE0x10Set on ACEs created through inheritance (not explicitly placed). Determines ordering in canonical form.

5.6.2 Common flag combinations #

FlagsMeaning
CI | OIInherit to everything — containers and non-containers, recursively.
CIInherit to containers only, recursively.
OIInherit to non-containers only. Containers receive it as inherit-only.
CI | OI | IOInherit to everything, but do not apply to this object.
CI | OI | NPInherit to immediate children only.
CI | NPInherit 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:

  1. Parent SD — provides inheritable ACEs.
  2. Creator SD — an explicit SD provided by the caller (if any).
  3. 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 typeTRUEFALSEUNKNOWN
AllowACE takes effectACE skippedACE skipped
DenyACE takes effectACE skippedACE takes effect
AuditEvent emittedEvent skippedEvent 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 #

ANDTRUEFALSEUNKNOWN
TRUETRUEFALSEUNKNOWN
FALSEFALSEFALSEFALSE
UNKNOWNUNKNOWNFALSEUNKNOWN
ORTRUEFALSEUNKNOWN
TRUETRUETRUETRUE
FALSETRUEFALSEUNKNOWN
UNKNOWNTRUEUNKNOWNUNKNOWN

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:

OpcodePrefixSourceDescription
0xf9@User.token.user_claimsToken-level claims set by authd at creation.
0xfb@Device.token.device_claimsDevice-level claims from the device token.
0xfa@Resource.SD's SACL resource attribute ACEsPer-object attributes, extracted in Pre-SACL walk.
0xf8@Local.local_claims parameter to AccessCheckPer-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.

Note

KACS makes virtual groups (S-1-3-4 for owner, S-1-5-10 for PRINCIPAL_SELF) visible to conditional expressions. Member_of({S-1-3-4}) returns TRUE when the active SID-matching view treats the caller as the owner. Normal evaluation, restricted evaluation, and confinement evaluation may supply different virtual-group views as defined in their respective sections. This is an intentional divergence for semantic consistency between the SID matcher and the expression evaluator.

Note

KACS promotes between INT64 and UINT64 for relational operators: negative INT64 is always less than any UINT64. Without promotion, UINT64 claims are unusable in conditions because the bytecode encodes integer literals as signed.

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_claims passed 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:

TypeValueNotes
INT640x0001Signed 64-bit integer.
UINT640x0002Unsigned 64-bit integer.
STRING0x0003UTF-16LE string.
SID0x0005Binary SID.
BOOLEAN0x0006Stored as u64; normalised to true/false at resolution time.
OCTET0x0010Byte 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.

OffsetSizeFieldDescription
04NameOffsetOffset to the UTF-16LE null-terminated attribute name.
42ValueTypeOne of the supported claim value types above.
62ReservedReserved. Ignored by AccessCheck. Producers SHOULD set to 0.
84FlagsClaim flags.
124ValueCountNumber of values. May be 0.
164 * ValueCountValueOffsets[]One relative offset per value. Interpretation depends on ValueType.

Claim flags use the same meanings everywhere this format appears:

FlagValueMeaning
CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE0x0002String/octet comparisons using this attribute are case-sensitive.
CLAIM_SECURITY_ATTRIBUTE_USE_FOR_DENY_ONLY0x0004The attribute is visible only to deny-side conditional evaluation: deny ACE conditions and audit/alarm ACE conditions.
CLAIM_SECURITY_ATTRIBUTE_DISABLED0x0010The attribute is invisible to conditional evaluation.
CLAIM_SECURITY_ATTRIBUTE_MANDATORY0x0020The 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 integer
  • UINT64: unsigned 64-bit integer
  • BOOLEAN: 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:

OffsetSizeFieldDescription
04LengthByte length of the octet string.
4LengthDataRaw 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_len MUST be non-zero.
  • entry_len MUST fit entirely within the containing buffer.
  • entry_bytes is 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_claims buffer -> 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 #

TypeValueDescription
INT640x0001Signed 64-bit integer.
UINT640x0002Unsigned 64-bit integer.
STRING0x0003Unicode string.
FQBN0x0004Fully Qualified Binary Name. Reserved — not supported in KACS v0.20.
SID0x0005Security identifier.
BOOLEAN0x0006Boolean value.
OCTET0x0010Byte 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 typeByte-codeToken data encoding
Padding0x00No data. Used for DWORD alignment padding at end of expression.
Signed int80x011 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 int160x021 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 int320x031 QWORD (8 bytes LE) for the value (2's complement). 1 byte for sign. 1 byte for base. Total: 10 bytes.
Signed int640x041 QWORD (8 bytes LE) for the value (2's complement). 1 byte for sign. 1 byte for base. Total: 10 bytes.
Unicode string0x101 DWORD (4 bytes LE) for length in bytes. Then UTF-16LE code units (2 bytes each, LSB first). Not null-terminated.
Octet string0x181 DWORD (4 bytes LE) for length in bytes. Then raw bytes.
Composite0x501 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.
SID0x511 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:

SignCodeDescription
+0x01Explicit positive sign.
-0x02Negative.
None0x03No 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:

BaseCodeDescription
Octal0x01Display as octal.
Decimal0x02Display as decimal.
Hexadecimal0x03Display 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 typeByte-codeProcessing
==0x80TRUE if RHS equals LHS (single or set value); FALSE otherwise.
!=0x81FALSE if RHS equals LHS; TRUE otherwise.
<0x82TRUE if LHS < RHS; FALSE otherwise.
<=0x83TRUE if LHS <= RHS; FALSE otherwise.
>0x84TRUE if LHS > RHS; FALSE otherwise.
>=0x85TRUE if LHS >= RHS; FALSE otherwise.
Contains0x86TRUE if LHS value(s) include all of RHS value(s); FALSE otherwise.
Any_of0x88TRUE if RHS includes any of LHS value(s); FALSE otherwise.
Not_Contains0x8eLogical inverse of Contains.
Not_Any_of0x8fLogical 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 typeByte-codeProcessing
Member_of0x89TRUE if the token's group SIDs contain all SIDs in the operand.
Device_Member_of0x8aTRUE if the token's device group SIDs contain all SIDs in the operand.
Member_of_Any0x8bTRUE if the token's group SIDs contain any SID in the operand.
Device_Member_of_Any0x8cTRUE if the token's device group SIDs contain any SID in the operand.
Not_Member_of0x90Logical inverse of Member_of.
Not_Device_Member_of0x91Logical inverse of Device_Member_of.
Not_Member_of_Any0x92Logical inverse of Member_of_Any.
Not_Device_Member_of_Any0x93Logical inverse of Device_Member_of_Any.

For an empty SID operand set:

  • Member_of({}) and Device_Member_of({}) return TRUE (vacuous truth).
  • Member_of_Any({}) and Device_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 typeByte-codeProcessing
Exists0x87TRUE 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_Exists0x8dLogical inverse of Exists.
NOT (!)0xa2TRUE→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 typeByte-codeProcessing
AND (&&)0xa0If either operand is FALSE, return FALSE. Else if either is UNKNOWN, return UNKNOWN. Else return TRUE.
OR (||)0xa1If 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 typeByte-codeNamespace
@Local.0xf8Local claims (passed as AccessCheck parameter).
@User.0xf9User claims (from token.user_claims).
@Resource.0xfaResource attributes (from SACL resource attribute ACEs).
@Device.0xfbDevice claims (from token.device_claims).

5.11.7 Complete byte-code summary #

For quick reference, all byte-codes in numeric order:

Byte-codeToken
0x00Padding
0x01Signed int8 literal
0x02Signed int16 literal
0x03Signed int32 literal
0x04Signed int64 literal
0x10Unicode string literal
0x18Octet string literal
0x50Composite literal
0x51SID literal
0x80==
0x81!=
0x82<
0x83<=
0x84>
0x85>=
0x86Contains
0x87Exists
0x88Any_of
0x89Member_of
0x8aDevice_Member_of
0x8bMember_of_Any
0x8cDevice_Member_of_Any
0x8dNot_Exists
0x8eNot_Contains
0x8fNot_Any_of
0x90Not_Member_of
0x91Not_Device_Member_of
0x92Not_Member_of_Any
0x93Not_Device_Member_of_Any
0xa0AND (&&)
0xa1OR (||)
0xa2NOT (!)
0xf8@Local. attribute
0xf9@User. attribute
0xfa@Resource. attribute
0xfb@Device. attribute

KACS implementations MUST be byte-compatible with these encodings.

Note

This byte-code table is reproduced from MS-DTYP §2.4.4.17.4 through §2.4.4.17.8 for specification self-containment. KACS-specific evaluation semantics (three-valued logic tables, INT64/UINT64 promotion, virtual group visibility in Member_of, polarity-aware SID matching) are defined in §5.8.

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.

Peios Learn — documentation for the Peios project.

Built with Trail.