diff --git a/.github/workflows/sep-source.yml b/.github/workflows/sep-source.yml index 51ce8828d55ab..6c90e5675c51f 100644 --- a/.github/workflows/sep-source.yml +++ b/.github/workflows/sep-source.yml @@ -41,6 +41,7 @@ jobs: python3 -m unittest tools/aurora-sep/tests/test_orphan_delete.py -v python3 -m unittest tools/aurora-sep/tests/test_enrol_admission.py -v python3 -m unittest tools/aurora-sep/tests/test_owner_preparation.py -v + python3 -m unittest tools/aurora-sep/tests/test_policy_window.py -v python3 -m unittest discover -s tools/aurora-sep/tests -p test_packaging.py -v rust-build: diff --git a/drivers/soc/apple/sbio.rs b/drivers/soc/apple/sbio.rs index 8d977de6a1cda..2c40415481287 100644 --- a/drivers/soc/apple/sbio.rs +++ b/drivers/soc/apple/sbio.rs @@ -2388,7 +2388,13 @@ impl SepData { EIO })?; diag::keybag(diag::Keybag::Present); - self.sks_designate_user_keybag(handle, stored.secret()); + // When Touch ID comes up before a trusted key is unsealed, this is + // the keybag's first use this boot, and after a boot policy change + // the enclave rewrites one session record here; see + // crate::xart_guard. + crate::xart_guard::policy_window(|| { + self.sks_designate_user_keybag(handle, stored.secret()) + }); self.sks_machine_refkey(handle, stored.secret()); let prepared = self.cold_match_continue(handle, uuid, stored.uuid_provenance()); self.ensure_restored_after(prepared); diff --git a/drivers/soc/apple/xart_guard.rs b/drivers/soc/apple/xart_guard.rs index 5fe73719e3e33..f14a40e150ca2 100644 --- a/drivers/soc/apple/xart_guard.rs +++ b/drivers/soc/apple/xart_guard.rs @@ -37,14 +37,15 @@ //! //! A boot policy change (a new Stage 1, a different boot object) makes the //! enclave rewrite one existing session record the first time Linux's -//! identity keybag is unlocked under the new policy. Linux did not create -//! that record. On a boot under an unchanged policy that unlock rewrites no -//! session record at all, so a rewrite inside it is the sign of a policy +//! identity keybag is used under the new policy: its first unlock, or +//! Touch ID's `DESIGNATE_KEYBAG` when that comes first. Linux did not create +//! that record. On a boot under an unchanged policy neither rewrites an +//! existing session record, so a rewrite inside them is the sign of a policy //! change. The guard lets exactly one kind-4 rewrite through in that window -//! ([`policy_window`]): the first unlock of an identity keybag that already -//! existed, at most once per boot, never a delete, and only once the -//! record's previous value is saved in a host file ([`PREVIOUS_PATH`]). -//! The record is not added to Linux's list. +//! ([`policy_window`]): the first unlock or designate of an identity keybag +//! that already existed, at most once per boot, never a delete, and only +//! once the record's previous value is saved in a host file +//! ([`PREVIOUS_PATH`]). The record is not added to Linux's list. //! //! For a policy change the window did not cover, `aurora-sep-policy-change` //! backs up the gigalocker and leaves a one-time allowance in a host file @@ -122,9 +123,9 @@ fn with_window(why: Rewrite, f: impl FnOnce() -> T) -> T { result } -/// Runs `unlock`, the unlock of an identity keybag that already existed, -/// with the boot policy window open if it is this boot's first. Later -/// unlocks run with it closed. +/// Runs `unlock`, the unlock or Touch ID designate of an identity keybag +/// that already existed, with the boot policy window open if it is this +/// boot's first. Later ones run with it closed. pub(crate) fn policy_window(unlock: impl FnOnce() -> T) -> T { with_window(Rewrite::PolicyChange, unlock) } @@ -326,8 +327,9 @@ pub(crate) enum Allowance { Record(Key), /// The first existing record of this kind the enclave asks to rewrite. /// An installer that changes the boot policy cannot know the record's - /// session id beforehand; the first identity unlock under the new policy - /// comes before anything else can rewrite a session record. + /// session id beforehand; the keybag's first use under the new policy + /// (an unlock or Touch ID's designate) comes before anything else can + /// rewrite a session record. FirstOfKind(u8), } diff --git a/tools/aurora-sep/XART-SAFETY.md b/tools/aurora-sep/XART-SAFETY.md index d500fe86d0027..362612e0dff79 100644 --- a/tools/aurora-sep/XART-SAFETY.md +++ b/tools/aurora-sep/XART-SAFETY.md @@ -135,13 +135,36 @@ The same kernel, under the old policy, unlocked the keybag without any session record write. Linux had never created a record there: the guard ran from Linux's first boot and its list stayed empty. -Under an unchanged policy that first unlock rewrites no session record at -all (every J616s boot before the change). So the driver treats a rewrite -inside it as the policy change and lets exactly one through, by itself, on -the first boot after the change: - -- only during the first unlock of an identity keybag that already existed, - once per boot; a keybag or ref-key created this boot closes the window; +Touch ID can use the keybag first instead. When fprintd opens the sensor +before any trusted key is unsealed (a login screen that offers Touch ID, +for example), the first use of the keybag under the new policy is the +`DESIGNATE_KEYBAG` in `activate_touchid` (`sbio.rs`), and the enclave asks +for the rewrite there, after LOAD_KEYBAG has answered. The guard refuses +it, and the designate is never answered: + + xART guard: refused to rewrite kind-4 record , which this Linux install did not create + sks: DESIGNATE_KEYBAG got no reply after 2037 ms + sks: DESIGNATE_KEYBAG did not complete; enrolment will refuse + hwrng: read produced nothing (ETIMEDOUT) + matching unavailable this boot: ... + +Touch ID and the enclave's RNG stay down for that boot, and the refusal +repeats on every boot until the record is allowed. This was seen on the +MacBook Air M3 (T8122, J613) on 2026-11-07, after a change from a raw +m1n1 Stage 1 to a Mach-O one, and on a J616s after every Recovery +enrolment of a Stage 1 that day (https://github.com/aurora-silicon/linux/issues/1190). Kernels from before that +designate ran in the window below need `aurora-sep-policy-change` for this +case. + +Under an unchanged policy neither that first unlock nor that designate +rewrites an existing session record (every J616s boot before the change, +and every guarded Touch ID boot on the J613 and J616s). So the driver +treats a rewrite inside them as the policy change and lets exactly one +through, by itself, on the first boot after the change: + +- only during the first unlock or Touch ID designate of an identity keybag + that already existed, whichever comes first, once per boot; a keybag or + ref-key created this boot closes the window; - only one kind-4 session record, only a rewrite, never a delete or a shared root, and the record is not added to Linux's list; - only once the record's previous value is durable in @@ -160,8 +183,12 @@ boot: run it with no arguments. It takes the session id from the refusal. - Before a planned change, run it with `--expect`. The record cannot be named in advance, so the allowance covers the first existing kind-4 - record the enclave asks to rewrite. The identity unlock is the first - thing that asks. + record the enclave asks to rewrite. The keybag's first use, Touch ID's + designate or the first unlock, is the first thing that asks. See + "Changing Stage 1 on purpose" below. +- On a kernel whose refusal line names no session id (one from before the + allowance code), the tool with no arguments finds nothing to allow. Use + `--expect` there, and boot a kernel with the allowance code next. Either way it first copies the whole 6 MiB gigalocker extent, the driver's host files and `/var/lib/fprint` to @@ -174,6 +201,30 @@ log names the record either way. `--status` shows what is armed and what this boot refused; `--cancel` removes an allowance that was not used yet. +#### Changing Stage 1 on purpose + +Arm the allowance before the change, so the first boot under the new +policy keeps Touch ID: + +1. On the Linux install, run `aurora-sep-policy-change --expect` as root. + The running kernel only has to load the driver, which logs where the + gigalocker is; it need not have the allowance code. +2. Change Stage 1 (for example, enrol it from 1TR). +3. Make the first boot afterwards a kernel with the allowance code, the + one that reads `/var/lib/aurora-sep-xart-allow-once.bin`. It empties + the file at probe and logs "this boot may rewrite the first existing + kind-4 record the enclave asks for, once", then "allowed one rewrite of + kind-4 record ... for a boot policy change" when the enclave asks. + A kernel without that code does not read the file. With the guard on, + it refuses the rewrite as above and leaves the allowance for a later + boot; with the guard off, it lets the rewrite through without saving + the previous value. +4. Later boots under the same policy need nothing, on either kernel. + +If the first boot was the wrong kernel, Touch ID is down for that boot +only: boot one with the allowance code next, and it uses the allowance +still in the file. + `aurora-sep-load` recognises a refusal and stops at once with a message that points here, instead of retrying for two minutes. @@ -182,6 +233,15 @@ rewrote one kind-4 record and one shared root. The other six kind-4 records and the kind-3 record kept their revisions, Ryan's enrolled finger matched afterwards, and it kept matching across later reboots. +On the J613 (2026-11-07) the tool with no arguments armed the record the +designate refusal named. The next boot logged "allowed one rewrite of +kind-4 record ... for a boot policy change", restored the Catacomb and the +enrolment ("Touch ID: restored 1 enrolled identity/identities; enrolment +survived reboot"), and a kernel without the allowance code brought Touch +ID up on later boots. A second Stage 1 change on that Mac (2026-11-08), +armed beforehand as in "Changing Stage 1 on purpose", kept Touch ID on +the first boot with no refusal. + The copy of the gigalocker is evidence and a last resort, not an undo: writing an old copy back does not roll back the enclave's anti-replay counters, and the enclave may refuse it. diff --git a/tools/aurora-sep/aurora-sep-policy-change b/tools/aurora-sep/aurora-sep-policy-change index be53a352d92fd..fc9aab126e0d6 100755 --- a/tools/aurora-sep/aurora-sep-policy-change +++ b/tools/aurora-sep/aurora-sep-policy-change @@ -4,11 +4,12 @@ A new Stage 1, another boot object or a boot mode conversion changes the Mac's boot policy for Linux. The first time Linux's identity keybag is -unlocked under the new policy, the enclave rewrites one session record in -the shared xART gigalocker. Linux did not create that record, so the -driver's xART guard refuses the write, the machine ref-key stays down and -Touch ID is unavailable. The keybag, the ref-key and the enrolled -fingerprints are all intact: the refused write changed nothing. +used under the new policy (unlocked, or designated for Touch ID), the +enclave rewrites one session record in the shared xART gigalocker. Linux +did not create that record, so the driver's xART guard refuses the write, +the enclave stops answering and Touch ID is unavailable. The keybag, the +ref-key and the enrolled fingerprints are all intact: the refused write +changed nothing. This lets the enclave make that one write on the next boot: @@ -20,6 +21,11 @@ This lets the enclave make that one write on the next boot: aurora-sep-policy-change --status what is armed, and this boot's refusals aurora-sep-policy-change --cancel remove an allowance not used yet +Only a kernel with the xART guard's allowance code reads the allowance. +After a planned change, make the first boot such a kernel; any other +kernel leaves the allowance for a later boot. See "Changing Stage 1 on +purpose" in XART-SAFETY.md. + Before it arms anything it backs up the whole gigalocker extent, the driver's host files and the fingerprint store to /var/lib/aurora-rollback/sep-policy-change-