On this page
- §3.1.1 Access control flow
- §3.1.2 Access rights
- §3.1.2.1 Specific rights (bits 0--15)
- §3.1.2.2 Standard rights (bits 16--23)
- §3.1.2.3 System rights
- §3.1.2.4 Generic-to-specific mapping
- §3.1.2.5 Access mask validation
- §3.1.3 SD inheritance
- §3.1.4 Hive root SDs
- §3.1.5 Registry-specific considerations
- §3.1.6 LCS audit events
Access Control
The registry's security model is a direct application of KACS. LCS does not define its own access control mechanism -- it uses the same AccessCheck, SDs, tokens, and SIDs that every other Peios subsystem uses. This section describes how KACS primitives integrate with registry operations.
§3.1.1 Access control flow
Every registry operation that opens a key follows the same path:
-
Token capture. The userspace thread makes a registry syscall. LCS captures the thread's effective token -- the impersonation token if one is set, otherwise the primary token. This is the same token capture KACS uses for all syscalls.
-
Path resolution. LCS walks the registry path via RSI Lookup operations, resolving through the layer stack. Symlinks are followed. No access checking occurs during the walk -- intermediate keys are not checked. Only the final key matters.
-
AccessCheck. LCS calls the KACS AccessCheck function with:
- The caller's token (captured in step 1)
- The final key's SD (returned by the source in the Lookup response)
- The desired access mask (from the syscall arguments)
All requested rights MUST be granted or the open fails with EACCES. There is no partial grant -- the caller gets exactly what they asked for, or nothing.
The special value MAXIMUM_ALLOWED requests whatever the SD grants. AccessCheck computes the full set of allowed rights and uses that as the granted mask.
-
Granted mask storage. The granted access mask is stored on the key fd. It does not change after open.
-
Per-ioctl access check. Each ioctl has a required access right. Before processing, LCS verifies that the fd's granted mask includes the required right. This is a bitmask check, not an AccessCheck re-evaluation. If the right is not present, EACCES is returned without contacting the source.
§3.1.2 Access rights
Registry access rights are bit positions within the desired_access and granted_access uint32 masks. The values match the Windows registry access rights for registry.pol and Samba compatibility.
§3.1.2.1 Specific rights (bits 0--15)
| Right | Value | Description |
|---|---|---|
| KEY_QUERY_VALUE | 0x0001 | Read values. |
| KEY_SET_VALUE | 0x0002 | Write and delete values. |
| KEY_CREATE_SUB_KEY | 0x0004 | Create child keys. |
| KEY_ENUMERATE_SUB_KEYS | 0x0008 | Enumerate child keys. |
| KEY_NOTIFY | 0x0010 | Subscribe to watches. |
| KEY_CREATE_LINK | 0x0020 | Create symlink keys. |
§3.1.2.2 Standard rights (bits 16--23)
| Right | Value | Description |
|---|---|---|
| DELETE | 0x00010000 | Delete the key. Hide the key. |
| READ_CONTROL | 0x00020000 | Read the SD and key metadata. |
| WRITE_DAC | 0x00040000 | Modify the DACL. |
| WRITE_OWNER | 0x00080000 | Change the owner SID. |
§3.1.2.3 System rights
| Right | Value | Description |
|---|---|---|
| ACCESS_SYSTEM_SECURITY | 0x01000000 | Read or modify the SACL. Gated by SeSecurityPrivilege (PSD-004 §7). |
§3.1.2.4 Generic-to-specific mapping
| Generic right | Maps to |
|---|---|
| KEY_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | READ_CONTROL |
| KEY_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | READ_CONTROL |
| KEY_ALL_ACCESS | All specific rights | all standard rights |
KEY_READ, KEY_WRITE, and KEY_ALL_ACCESS are concrete registry convenience masks. LCS also accepts the raw KACS generic access bits GENERIC_READ, GENERIC_WRITE, GENERIC_EXECUTE, and GENERIC_ALL in caller-supplied desired_access and in registry SD ACE masks. Those raw generic bits are mapped through the registry GenericMapping before AccessCheck according to PSD-004 §10.10. GENERIC_EXECUTE maps to 0 because registry keys have no execute right.
§3.1.2.5 Access mask validation
LCS validates caller-supplied desired_access before path resolution or AccessCheck. The valid caller mask is:
- the six specific registry rights listed above;
- DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER;
- ACCESS_SYSTEM_SECURITY;
- MAXIMUM_ALLOWED;
- GENERIC_READ, GENERIC_WRITE, GENERIC_EXECUTE, and GENERIC_ALL.
If desired_access is 0, LCS MUST fail with EINVAL. If desired_access contains any bit outside the valid caller mask, LCS MUST fail with EINVAL. MAXIMUM_ALLOWED MAY be used alone or combined with specific, standard, system, or raw generic rights, following PSD-004 §10.2.
Registry keys do not support SYNCHRONIZE in v0.21. A caller request containing SYNCHRONIZE is therefore an unknown-bit request and fails with EINVAL.
LCS also validates registry SDs returned by sources before using them for AccessCheck. ACE masks MAY contain registry concrete rights and raw KACS generic bits. ACE masks MUST NOT contain MAXIMUM_ALLOWED. After generic mapping, an ACE mask MUST be a subset of concrete registry rights plus ACCESS_SYSTEM_SECURITY. A source SD that violates these rules is malformed source data: LCS fails the operation closed with EIO and emits the source-validation audit event defined in §3.1.
§3.1.3 SD inheritance
When a key is created via reg_create_key, its initial SD is computed from the parent key's SD using the KACS inheritance algorithm (PSD-004 §3.6).
Registry-specific notes:
-
Inheritance is static -- computed once at creation time. Changes to the parent's SD do not automatically propagate to existing children. Re-propagation is an explicit admin action (a client-side tree walk), not a kernel operation.
-
OBJECT_INHERIT_ACE is not relevant for registry keys. All registry objects are containers (keys contain subkeys and values). Values are not independent security objects -- they inherit their key's access control. Only CONTAINER_INHERIT_ACE applies.
-
If the parent has no inheritable ACEs, the child's DACL is set from the creating token's default DACL.
-
LCS delegates the inheritance computation to KACS and passes the resulting SD to the source for storage. LCS does not implement its own inheritance logic.
§3.1.4 Hive root SDs
Hive root keys are the top of the inheritance chain -- they have no parent to inherit from. Their SDs are set by the source at first boot and serve as the seeds for all descendant key permissions.
Machine\ root:
| Principal | Rights | Inheritance |
|---|---|---|
| SYSTEM | KEY_ALL_ACCESS | Container-inherit |
| Administrators | KEY_ALL_ACCESS | Container-inherit |
| Authenticated Users | KEY_READ | Container-inherit |
Users\<SID>\ root (per-user hive root):
| Principal | Rights | Inheritance |
|---|---|---|
| <User SID> | KEY_ALL_ACCESS | Container-inherit |
| SYSTEM | KEY_ALL_ACCESS | Container-inherit |
| Administrators | KEY_ALL_ACCESS | Container-inherit |
These defaults match Windows HKLM and HKU semantics. Subsystems
that need tighter access (e.g., Machine\Security\) set explicit
SDs on their subtree roots at creation time, overriding the
inherited defaults.
These SDs are not hardcoded in LCS. They are set by the source (loregd) when creating hive roots. LCS enforces whatever SD the source stores.
§3.1.5 Registry-specific considerations
No traverse checking. LCS does not check access on intermediate
path components during path resolution. Only the final key's SD is
evaluated. This matches the Windows registry model. A process can
access Machine\System\Services\Jellyfin without needing any
access to Machine\System\Services or Machine\System.
Symlink security. Opening a symlink follows the target path. LCS evaluates AccessCheck on the target key, not the symlink key (unless REG_OPEN_LINK is specified). Symlink creation is privileged (KEY_CREATE_LINK + SeTcbPrivilege or Administrator membership).
Watch access. Subscribing to watches requires KEY_NOTIFY on the key fd. This prevents processes from monitoring keys they cannot read. Subtree watches expose structural changes (key creation, deletion) in descendants without per-descendant access checks -- the watcher learns that a descendant was created but cannot read its values without opening it separately. This matches Windows RegNotifyChangeKeyValue semantics. Structure visibility is intentionally weaker than content visibility.
SACL evaluation and audit. When LCS performs AccessCheck at key open time and the key's SD contains a SACL with matching audit ACEs, LCS MUST emit audit events via the kernel audit pipeline. SACL evaluation follows the KACS AccessCheck algorithm — the SACL is evaluated alongside the DACL. Audit events include the caller's token, the key GUID, the requested access, and the access decision. Accessing or modifying the SACL itself requires ACCESS_SYSTEM_SECURITY, which is gated by SeSecurityPrivilege (see PSD-004 §7).
For v0.21, the LCS kernel audit pipeline is KMES (PSD-003). KMES is an in-kernel PKM facility. LCS audit emission is synchronous at the audit point in the sense that LCS constructs the audit payload and attempts to enqueue it before continuing past that audit point. LCS MUST NOT wait for a userspace KMES consumer to observe or retain the event.
For a key-open SACL audit, LCS evaluates AccessCheck, determines the access decision and granted mask, constructs a valid KMES audit payload, attempts to enqueue the KMES audit event, and only then publishes the resulting key fd to userspace. If LCS cannot construct a valid audit payload because of malformed internal state, corruption, allocation failure, or any other LCS-side payload-construction failure, LCS fails the open with EIO and does not return a key fd. If KMES cannot retain the event after LCS has constructed a valid payload (including KMES unavailability, ring drops, capacity pressure, or consumer absence), LCS does not alter the AccessCheck decision or key fd publication result. KMES loss accounting is handled by KMES.
SD changes and existing fds. SD modifications take effect for future opens. Existing fds retain their granted access mask from open time (§2.1 semantic rule 5). The admin's recourse for revoking access is to restart the service holding the fd.
SD modification access rights:
| Operation | Required right |
|---|---|
| Read owner, group, DACL | READ_CONTROL |
| Modify DACL | WRITE_DAC |
| Change owner | WRITE_OWNER |
| Read SACL | ACCESS_SYSTEM_SECURITY |
| Modify SACL | ACCESS_SYSTEM_SECURITY |
SD modifications are NOT layer-qualified. They are direct mutations on the key object (§2.1 semantic rule 4). See §2.5 for the distinction between layered data and non-layered properties.
§3.1.6 LCS audit events
LCS emits the following KMES audit events.
| Event | When emitted | Required fields |
|---|---|---|
| LCS_KEY_OPEN_AUDIT | A key open has a matching SACL audit ACE. | caller token summary, key GUID, requested access, granted access, decision, SACL match flags |
| LCS_BACKUP_START | Before REG_IOC_BACKUP starts reading subtree data. | caller token summary, key GUID, output fd number |
| LCS_BACKUP_COMPLETE | After REG_IOC_BACKUP completes or fails after start. | caller token summary, key GUID, result errno |
| LCS_RESTORE_START | Before REG_IOC_RESTORE starts modifying source state. | caller token summary, key GUID, input fd number |
| LCS_RESTORE_COMPLETE | After REG_IOC_RESTORE completes or fails after start. | caller token summary, key GUID, result errno |
| LCS_SOURCE_VALIDATION_FAILURE | LCS rejects malformed source data. | source slot identifier, hive name if known, request ID if known, operation code if known, key GUID if known, validation class |
| LCS_SELF_CONFIG_INVALID | LCS rejects an invalid self-configuration value. | configuration path, expected type/range, received type, received value summary, retained value summary |
caller token summary is a KMES-safe summary of the effective token
used for the operation. It must include enough identity information
to correlate the event with the caller without exposing unbounded
token internals in the event record.
For v0.21, every LCS audit payload MUST be a single msgpack map with string keys. Map order is not semantically significant, but kernel emitters SHOULD encode fields in the table order below for stable diagnostics. GUID fields are 16-byte Microsoft GUID binary values. SID fields are binary KACS SID encodings.
The caller field used below is itself a msgpack map with the
following bounded fields:
| Field | Type | Description |
|---|---|---|
effective_token_guid |
bin(16) |
GUID of the effective token captured for the operation. |
true_token_guid |
bin(16) |
GUID of the process primary token captured for the operation. |
process_guid |
bin(16) |
GUID of the calling process. |
user_sid |
bin |
User SID from the effective token. |
authentication_id |
uint64 |
Effective token authentication ID. |
token_id |
uint64 |
Effective token ID. |
token_type |
uint32 |
KACS token type value. |
impersonation_level |
uint32 |
KACS impersonation level value, or 0 for primary tokens. |
integrity_level |
uint32 |
Effective token integrity level. |
The caller summary MUST NOT include group lists, privilege arrays, claims, default DACLs, or other unbounded token internals.
LCS_KEY_OPEN_AUDIT payload:
| Field | Type | Description |
|---|---|---|
caller |
map | Caller token summary. |
key_guid |
bin(16) |
Open target key GUID. |
requested_access |
uint32 |
Caller requested access after registry generic mapping rules are applied. |
granted_access |
uint32 |
Granted mask computed by AccessCheck; 0 on denied opens. |
decision |
string | Either allowed or denied. |
sacl_match_flags |
uint32 |
Non-zero bitmask of matching SACL audit classes. Bit 0 (0x1) means success-audit match. Bit 1 (0x2) means failure-audit match. All other bits are reserved and MUST NOT be set. |
Backup and restore start/complete payloads:
| Event | Fields |
|---|---|
LCS_BACKUP_START |
caller map, key_guid bin(16), fd int32 output fd number. |
LCS_BACKUP_COMPLETE |
caller map, key_guid bin(16), result_errno uint32, where 0 means success and non-zero values are Linux errno numbers. |
LCS_RESTORE_START |
caller map, key_guid bin(16), fd int32 input fd number. |
LCS_RESTORE_COMPLETE |
caller map, key_guid bin(16), result_errno uint32, where 0 means success and non-zero values are Linux errno numbers. |
LCS_SOURCE_VALIDATION_FAILURE payload:
| Field | Type | Description |
|---|---|---|
source_slot |
uint32 |
LCS source slot identifier. |
hive_name |
string or nil | Hive name if known. |
request_id |
uint64 or nil |
RSI request ID if known. |
op_code |
uint16 or nil |
RSI operation code if known. |
key_guid |
bin(16) or nil |
Key GUID associated with the failed validation if known. |
validation_class |
string | One of malformed_security_descriptor, malformed_layer_name, malformed_key_name, malformed_value_name, malformed_response_payload, malformed_key_metadata, malformed_value_payload, malformed_delete_layer_orphan_list, unknown_rsi_status_code, future_sequence_number, duplicate_winning_sequence_tie, or malformed_layer_metadata_security_descriptor. |
Name validation classes are field-specific: malformed_layer_name
identifies source-returned layer-name fields, malformed_key_name
identifies source-returned key component or child-name fields, and
malformed_value_name identifies source-returned value-name fields.
Other source-validation classes identify structural source data failures:
malformed_response_payload identifies otherwise matched responses whose
operation-specific success payload has the wrong shape, trailing
operation-specific bytes, invalid path-target encoding, or another
operation-specific payload encoding error. malformed_key_metadata identifies
lookup or enum-children metadata closure failures, including missing,
duplicate, unreferenced, or nil metadata GUIDs. malformed_value_payload
identifies query-values value type/data failures, including invalid value
types, tombstone/data mismatches, and oversized value data.
malformed_delete_layer_orphan_list identifies invalid RSI_DELETE_LAYER
orphan GUID arrays, including nil or duplicate orphan GUIDs.
LCS_SELF_CONFIG_INVALID payload:
| Field | Type | Description |
|---|---|---|
configuration_parent_path |
string | Parent key path containing the invalid configuration value. |
configuration_name |
string | Configuration value name. |
expected_type |
uint32 |
Expected registry value type. |
expected_min |
uint32 |
Minimum accepted value for numeric ranges. |
expected_max |
uint32 |
Maximum accepted value for numeric ranges. |
received_kind |
string | One of missing, wrong_type, or dword_out_of_range. |
received_type |
uint32 or nil |
Actual registry type for wrong_type, otherwise nil. |
received_u32 |
uint32 or nil |
Actual numeric value for dword_out_of_range, otherwise nil. |
retained_value |
uint32 |
Previous known-good value retained by LCS. |
Audit emission failure policy:
- For LCS_KEY_OPEN_AUDIT, payload-construction failure returns EIO and the key fd is not published. KMES enqueue, retention, or consumer failure after valid payload construction does not alter the key-open result.
- For LCS_BACKUP_START and LCS_RESTORE_START, failure to emit returns EIO and the backup or restore does not start.
- For LCS_BACKUP_COMPLETE and LCS_RESTORE_COMPLETE, the operation has already completed or failed. LCS attempts emission; if emission fails, it does not alter the already determined result.
- For LCS_SOURCE_VALIDATION_FAILURE, the triggering operation already fails with EIO. LCS attempts emission; if emission fails, the operation still returns EIO.
- For LCS_SELF_CONFIG_INVALID, the invalid value is ignored and the previous known-good value is retained. LCS attempts emission; if emission fails, the retained configuration remains in force.