Skip to content

soc: apple: sep: key-store-only initrd attach, and per-user key bag unlock there (keystore_only=1/2) - #1471

Merged
RyanTheTide merged 5 commits into
sepfde/fde-sealfrom
seproot/root-unlock
Nov 11, 2026
Merged

RyanTheTide merged 5 commits into
sepfde/fde-sealfrom
seproot/root-unlock

Conversation

@RyanTheTide

Copy link
Copy Markdown
Contributor

Stacked on #1339.

  • keystore_only=1: the initrd attaches the SEP key store for trusted keys only. Touch ID and the host-state store wait until the real root writes complete_attach.
  • keystore_only=2: as 1, and /dev/sep-bio-rearm also serves the password unlock of a per-user key bag in the initrd: state, import, unlock, lock and FDE unseal.
    • Create, change, export and FDE create/seal return EAGAIN until complete_attach.
    • xART follows xart_writes in this mode only while the xART guard is off. With a guard, the bag requests fail with EROFS, and the volume falls back to its passphrase or recovery key.
  • Used by aurora-sep-userspace#23: dracut and mkinitcpio pick mode 2 for password-sealed volumes, root included.

Tests:

  • KUnit drivers/soc/apple/sep.kunitconfig: covers the initrd request subset and the initrd xART write rule. On this branch merged onto 8b7c2af: 469 tests, 467 pass, 0 fail, 2 skipped (not SEP tests; all 22 apple_sep suites pass).
  • Build of the same merge with the Fedora asahi config: modinfo -F parm apple-sep.ko lists keystore_only with both modes.

🤖 Generated with Claude Code

Keep host state, identity-index loading and biometric device publication off
until the real root completes the original attach. Open xART read-only in the
initrd, suppress keybag and ref-key provisioning, and cache trusted-key material
without retaining initrd file handles. Refuse an unseal when persistent-state
writes were denied, while preserving the real root's boot-policy window.

Add the root-only complete_attach attribute and let load-driver finish an
initrd-loaded module without another SEP registration. Cover completion
rejection, retries and idempotence in KUnit, plus production cache, refusal and
loader paths in buildbox host tests.

Signed-off-by: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com>
Assisted-by: LLM
SYNC_FILE selects the hidden DMA_SHARED_BUFFER symbol required by SEP.
Setting the hidden symbol alone is ignored by Kconfig.

Signed-off-by: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com>
Assisted-by: LLM
Signed-off-by: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com>
Assisted-by: LLM
…y=2)

A root volume sealed to a user's password (SEP_BIO_FDE_*) has to open
before the real root is mounted, the way FileVault asks for the password
once after a cold boot. keystore_only=1 attaches the key store for trusted
keys only and keeps xART read-only, so a saved user bag can neither be
loaded nor unlocked there.

keystore_only=2 adds that unlock to the initrd phase:

- /dev/sep-bio-rearm is published as soon as the key store is open. Until
  complete_attach it serves only what a password unlock needs: state,
  import, unlock, lock and FDE unseal. Create, change, export and the FDE
  create/seal requests return EAGAIN; enrolment stays on the real root.
- Loading and unlocking a bag needs enclave writes to the gigalocker, so
  xART follows xart_writes in this mode, but only while the xART guard is
  off: the guard's record list lives in host files on the real root, which
  the initrd must not write. With a guard, xART stays read-only, the bag
  requests fail with EROFS and the volume falls back to its passphrase.
- Nothing changes for keystore_only=0 or 1. complete_attach is unchanged
  and publishes the rest of the node's requests.

load-driver completes the attach for either initrd mode.

KUnit: the initrd request subset and the initrd xART write rule.

Signed-off-by: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com>
Assisted-by: LLM
@RyanTheTide

Copy link
Copy Markdown
Contributor Author

Hardware rows, J700 (Neo), real Secure Enclave, kernel 7.3.0-aurora-seproot2 (the #1471 branch merged onto 8b7c2af), userspace a8f9562. The tests ran on LUKS2 image files only. SELinux was enforcing, with 0 AVCs on each boot.

Boot 1 (images with 256 KiB metadata): 43 of 44 rows pass.

  • Enrol and ownership:
    • alice enrols (2 s) and becomes an owner. alice adds bob (wheel), who becomes an owner, and carol (standard), who is unlock-only.
    • alice, bob and carol each unlock with their own password. dave (not enrolled) is refused, and a wrong password is refused.
    • The enclave counts failed attempts (4) and a right password clears the count.
  • Refusals: carol cannot add a user, remove an owner or reset bob. An owner's wrong password authorizes nothing.
  • Ownership changes: bob makes carol an owner and alice takes it back.
  • Password change and reset:
    • alice's bag is reused on a second volume.
    • change-password re-wraps alice's bag on both volumes: the new password opens both and the old one is refused on both.
    • PAM (pam_aurora_sep_fde through pam_chauthtok): bob's new password opens both volumes and his old one is refused on both.
    • alice (owner) resets carol's password: the new password opens and the forgotten one is refused. Because the enclave still held carol's bag, her new bag moved to generation 1 (uid 1061252). She stays unlock-only.
  • Recovery and passphrase:
    • Recovery key with offline slot: it opens through the SEP, and its offline slot opens without the SEP.
    • With recovery = sep-only: no offline slot is made, the key does not open without the SEP, and it opens through the SEP.
    • The installer passphrase is removed.
  • FAIL gate: after the lock, alice's bag stayed in lock phase 2 ("locking") for more than 120 s and still unsealed. By then the bag had been through change-password and repeated unlocks. On a fresh bag (soc: apple: sep: seal a disk-encryption secret to a user's key bag, per OS install, unlockable from the initrd #1339 rows) phase 1 came after 41 s.

Boot 2 (after a real reboot, no bags in the enclave): 10 of 10 rows pass.

  • The enclave holds no bags after the reboot.
  • On volume A, alice's, bob's and carol's current passwords open and their old passwords are refused.
  • On volume B, alice's and bob's new passwords open and their old ones are refused.
  • The recovery key opens.
  • The attempt count restarts after the reboot (3 to 1), because the bag reloads from the token.
  • An old header with the old password was refused, but only because the current bag was already loaded this boot. Importing an older wrapped bag is still not rollback-protected.

Metadata size: the first run used the default 16 KiB LUKS2 metadata area. The third user and the recovery key failed with ENOSPC, because a real wrapped bag is 1520 B and a hex token about 4 KB. Fixed in a8f9562 (base64 fields, and a clear error). Even with base64, a 16 KiB area takes 3 password tokens plus the recovery offline slot.

Encrypted-root entry (dracut initramfs with keystore_only=2, the token plugin and Plymouth) is installed non-default with the os_uuid pin. The rows at the Plymouth prompt need a person typing and have not run yet.

@RyanTheTide
RyanTheTide merged commit 1b002b8 into sepfde/fde-seal Nov 11, 2026
6 checks passed
@RyanTheTide
RyanTheTide deleted the seproot/root-unlock branch November 11, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant