From 7a4a84f9da79c9407d721a5a28d1a3956346204f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sat, 31 Oct 2026 09:25:02 +1100 Subject: [PATCH 1/2] soc: apple: sep: hold the v5 biometric ABI until its userspace ships The enrolment guard series (f70f1acd and follow-ups) moved the SEP biometric interface to version 5 and grew sep_bio_enrol_start from 136 to 144 bytes. Every packaged libfprint and aurora-sep still speaks version 4 and checks the version for equality, so on a kernel with the series aurora-sep-ready and fprintd skip themselves and Touch ID is gone on every machine. The matching libfprint change is still a draft (aurora-sep-userspace#3). Put the SEP driver and its UAPI header back to the version 4 interface that integration/aurora ships. The guard can return together with the version 5 libfprint and the aurora-sep sensor check. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/bio.rs | 132 ++--------- drivers/soc/apple/catacomb.rs | 18 -- drivers/soc/apple/sbio.rs | 413 ++------------------------------- drivers/soc/apple/sep.rs | 4 - drivers/soc/apple/shim.h | 1 - drivers/soc/apple/shim.rs | 10 - drivers/soc/apple/store.rs | 9 - drivers/soc/apple/store_shim.c | 8 - include/uapi/linux/apple-sep.h | 20 +- 9 files changed, 39 insertions(+), 576 deletions(-) diff --git a/drivers/soc/apple/bio.rs b/drivers/soc/apple/bio.rs index 573830b7973d0..6b4e1edbe2966 100644 --- a/drivers/soc/apple/bio.rs +++ b/drivers/soc/apple/bio.rs @@ -6,7 +6,7 @@ use kernel::ioctl::{_IO, _IOR, _IOW, _IOWR}; use kernel::prelude::*; use kernel::uaccess::{UserPtr, UserSlice}; -pub(crate) const IFACE_VERSION: u32 = 5; +pub(crate) const IFACE_VERSION: u32 = 4; pub(crate) const ENROL_USER_ID: i32 = 1000; @@ -100,35 +100,10 @@ struct List { #[repr(C)] struct EnrolStart { flags: u32, - account_uid: u32, - finger: u32, reserved: u32, label: [u8; LABEL_LEN], } -#[repr(C)] -pub(crate) struct EnclaveList { - pub(crate) count: u32, - pub(crate) reserved: u32, - pub(crate) uuid: [[u8; UUID_LEN]; MAX_IDENTITIES], -} - -impl EnclaveList { - pub(crate) fn new() -> Self { - Self { count: 0, reserved: 0, uuid: [[0; UUID_LEN]; MAX_IDENTITIES] } - } - - pub(crate) fn push(&mut self, uuid: [u8; UUID_LEN]) -> Result<()> { - let at = self.count as usize; - if at >= MAX_IDENTITIES { - return Err(EOVERFLOW); - } - self.uuid[at] = uuid; - self.count += 1; - Ok(()) - } -} - #[repr(C)] #[derive(Default)] struct EnrolPoll { @@ -161,8 +136,8 @@ struct VerifyPoll { } #[repr(C)] -pub(crate) struct Delete { - pub(crate) uuid: [u8; UUID_LEN], +struct Delete { + uuid: [u8; UUID_LEN], } pub(crate) const ATTEST_CHALLENGE_LEN: usize = 32; @@ -187,8 +162,7 @@ pub(crate) struct Attest { static_assert!(core::mem::size_of::() == 144); static_assert!(core::mem::size_of::() == 32); static_assert!(core::mem::size_of::() == 4616); -static_assert!(core::mem::size_of::() == 144); -static_assert!(core::mem::size_of::() == 520); +static_assert!(core::mem::size_of::() == 136); static_assert!(core::mem::size_of::() == 40); static_assert!(core::mem::size_of::() == 40); static_assert!(core::mem::size_of::() == 72); @@ -208,7 +182,7 @@ macro_rules! ioctl_pod { }; } ioctl_pod! { - to_user: Info, List, EnclaveList, EnrolPoll, VerifyPoll, Attest; + to_user: Info, List, EnrolPoll, VerifyPoll, Attest; from_user: EnrolStart, VerifyStart, Delete, Attest } @@ -224,9 +198,6 @@ const IOC_CANCEL: u32 = _IO(MAGIC, 0x07); const IOC_DELETE: u32 = _IOW::(MAGIC, 0x08); const IOC_DELETE_ALL: u32 = _IO(MAGIC, 0x09); pub(crate) const IOC_ATTEST: u32 = _IOWR::(MAGIC, 0x0a); -pub(crate) const IOC_LIST_ENCLAVE: u32 = _IOR::(MAGIC, 0x0b); -pub(crate) const IOC_DELETE_ORPHAN: u32 = _IOW::(MAGIC, 0x0c); -pub(crate) const IOC_PREPARE_ENROL: u32 = _IOW::(MAGIC, 0x0d); pub(crate) struct MatchEvidence { identity: [u8; UUID_LEN], @@ -367,10 +338,6 @@ impl IdentityIndex { self.save(store) } - pub(crate) fn can_persist_next(&self, store: &Store) -> bool { - store.can_write(&identity_key(), 8 + (self.entries.len() + 1) * (UUID_LEN + LABEL_LEN)) - } - pub(crate) fn identity_v1_for( &self, uuid: &[u8; UUID_LEN], @@ -514,7 +481,7 @@ pub(crate) struct Handled { pub(crate) delete_identities: KVec, } -pub(crate) fn ok() -> Result { +fn ok() -> Result { Ok(Handled { ret: 0, wake: false, @@ -525,7 +492,7 @@ pub(crate) fn ok() -> Result { }) } -pub(crate) fn require_admin() -> Result<()> { +fn require_admin() -> Result<()> { if crate::shim::capable_admin() { Ok(()) } else { @@ -533,12 +500,6 @@ pub(crate) fn require_admin() -> Result<()> { } } -pub(crate) fn orphan_delete(identity: crate::sbio::IdentityV1) -> Result { - let mut handled = ok()?; - handled.delete_identity = Some(identity); - Ok(handled) -} - fn require_sensor(present: bool) -> Result<()> { if present { Ok(()) @@ -606,85 +567,22 @@ fn list(ctx: &mut Context<'_>, user: UserPtr) -> Result { ok() } -fn validate_enrol_request(request: &EnrolStart) -> Result<()> { - // NSS account resolution occurs in libfprint. This ABI requires its - // resolved uid, the named finger and the generated user label. - if request.flags != 0 || request.reserved != 0 - || request.account_uid == u32::MAX || !(1..=10).contains(&request.finger) - || !request.label.starts_with(b"FP1-") - || request.label[12] != b'-' || request.label[14] != b'-' - || request.label[23] != b'-' || request.label[24] == 0 - || request.label[13] != b"123456789A"[request.finger as usize - 1] - || !request.label.contains(&0) - { - return Err(EINVAL); - } - Ok(()) -} - -pub(crate) fn validate_enrol_start_arg(arg: usize) -> Result<()> { - let user = UserPtr::from_addr(arg); - let request: EnrolStart = UserSlice::new(user, core::mem::size_of::()) - .reader().read()?; - validate_enrol_request(&request) -} - -#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] -#[::kernel::macros::kunit_tests(apple_sep_enrol_request)] -mod enrol_request_tests { - use super::*; - - fn valid() -> EnrolStart { - let mut request = EnrolStart { - flags: 0, account_uid: 1000, finger: 7, reserved: 0, - label: [0; LABEL_LEN], - }; - request.label[..28].copy_from_slice(b"FP1-20261027-7-12345678-user"); - request - } - - #[test] - fn resolved_account_and_known_finger() { - assert!(validate_enrol_request(&valid()).is_ok()); - } - - #[test] - fn missing_uid_is_refused() { - let mut request = valid(); - request.account_uid = u32::MAX; - assert!(validate_enrol_request(&request).is_err()); - } - - #[test] - fn unknown_or_mismatched_finger_is_refused() { - let mut request = valid(); - request.finger = 0; - assert!(validate_enrol_request(&request).is_err()); - request.finger = 6; - assert!(validate_enrol_request(&request).is_err()); - } - - #[test] - fn missing_username_is_refused() { - let mut request = valid(); - request.label[24] = 0; - assert!(validate_enrol_request(&request).is_err()); - } -} - fn enrol_start(ctx: &mut Context<'_>, user: UserPtr) -> Result { require_admin()?; let request: EnrolStart = UserSlice::new(user, core::mem::size_of::()) .reader() .read()?; - validate_enrol_request(&request)?; + if request.flags != 0 || request.reserved != 0 { + return Err(EINVAL); + } require_sensor(ctx.sensor_present)?; if ctx.session.worker_epoch.is_some() || !ctx.session.op.may_start() { return Err(EBUSY); } - let count = ctx.live_identity_count.ok_or(EIO)?; - if count >= SEP_USER_CAPACITY || ctx.index.total() >= MAX_IDENTITIES { + if matches!(ctx.live_identity_count, Some(count) if count >= SEP_USER_CAPACITY) + || ctx.index.total() >= MAX_IDENTITIES + { return Err(ENOSPC); } @@ -1062,10 +960,6 @@ pub(crate) fn starts_capture(cmd: u32) -> bool { cmd == IOC_ENROL_START || cmd == IOC_VERIFY_START } -pub(crate) fn idle_for_mutation(session: &Session) -> bool { - session.worker_epoch.is_none() && session.op.may_start() -} - fn delete(ctx: &mut Context<'_>, user: UserPtr) -> Result { require_admin()?; if !ctx.session.op.may_start() { diff --git a/drivers/soc/apple/catacomb.rs b/drivers/soc/apple/catacomb.rs index d71b415fcb321..8b18efb4ead77 100644 --- a/drivers/soc/apple/catacomb.rs +++ b/drivers/soc/apple/catacomb.rs @@ -28,24 +28,6 @@ pub(crate) fn is_kind(kind: u8) -> bool { path_for(kind).is_some() } -pub(crate) fn writable(kind: u8) -> bool { - path_for(kind) - .and_then(|path| shim::StoreFile::open_writable_existing(path).ok()) - .is_some() -} - -/// Reserve a missing Linux owner file before SELECT_CONTEXT changes SEP state. -/// A corrupt existing file is never truncated here: it is failure evidence. -pub(crate) fn prepare_missing_owner() -> Result<()> { - let path = path_for(crate::PRIVATE_TYPE_CATACOMB_OWNER).ok_or(EINVAL)?; - let (file, created) = shim::StoreFile::open_host(path)?; - if !created { - return Err(ENODATA); - } - file.sync()?; - file.sync_parent() -} - pub(crate) fn write(kind: u8, blob: &[u8]) -> Result<()> { let path = path_for(kind).ok_or(EINVAL)?; if blob.len() > MAX_CATACOMB { diff --git a/drivers/soc/apple/sbio.rs b/drivers/soc/apple/sbio.rs index a3917e11338c9..5a81a370dda38 100644 --- a/drivers/soc/apple/sbio.rs +++ b/drivers/soc/apple/sbio.rs @@ -10,128 +10,6 @@ use crate::sks::SKS_AUTH_TOKEN_LEN; use kernel::prelude::*; use kernel::soc::apple::mailbox::Message; -struct EnrolPrerequisites { - lane: bool, - live_count: Option, - bag_bound: bool, - catacomb_readable: [bool; 3], - catacomb_writable: [bool; 3], - keybag_writable: bool, - host_store_capacity: bool, -} - -fn admit_enrol(p: &EnrolPrerequisites) -> Result<()> { - if !p.lane { return Err(ENXIO); } - let count = p.live_count.ok_or(EIO)?; - if count >= 3 { return Err(ENOSPC); } - if !p.bag_bound || p.catacomb_readable.contains(&false) { - return Err(ENODATA); - } - if p.catacomb_writable.contains(&false) || !p.keybag_writable { - return Err(EROFS); - } - if !p.host_store_capacity { return Err(ENOSPC); } - Ok(()) -} - -fn catacomb_needed_for_enrol(kind: u8, live_count: Option) -> bool { - live_count != Some(0) || kind == PRIVATE_TYPE_CATACOMB_OWNER -} - -fn admissible_orphan_target( - listed_linux: &[[u8; bio::UUID_LEN]], - uuid: &[u8; bio::UUID_LEN], -) -> Result<()> { - if *uuid == [0; bio::UUID_LEN] { return Err(EINVAL); } - if !listed_linux.contains(uuid) { return Err(ENOENT); } - Ok(()) -} - -#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] -#[::kernel::macros::kunit_tests(apple_sep_enrol_admission)] -mod admission_tests { - use super::*; - - fn ready() -> EnrolPrerequisites { - EnrolPrerequisites { - lane: true, live_count: Some(1), bag_bound: true, - catacomb_readable: [true; 3], catacomb_writable: [true; 3], - keybag_writable: true, host_store_capacity: true, - } - } - - fn refused(p: &EnrolPrerequisites, expected: Error) { - assert_eq!(admit_enrol(p).unwrap_err().to_errno(), expected.to_errno()); - } - - #[test] - fn missing_os_identity() { - let mut p = ready(); p.lane = false; refused(&p, ENXIO); - } - - #[test] - fn unavailable_enclave_list() { - let mut p = ready(); p.live_count = None; refused(&p, EIO); - } - - #[test] - fn enclave_full() { - let mut p = ready(); p.live_count = Some(3); refused(&p, ENOSPC); - } - - #[test] - fn missing_identity_bag_binding() { - let mut p = ready(); p.bag_bound = false; refused(&p, ENODATA); - } - - #[test] - fn missing_catacomb() { - for i in 0..3 { - let mut p = ready(); p.catacomb_readable[i] = false; refused(&p, ENODATA); - } - } - - #[test] - fn unwritable_catacomb_or_keybag() { - for i in 0..3 { - let mut p = ready(); p.catacomb_writable[i] = false; refused(&p, EROFS); - } - let mut p = ready(); p.keybag_writable = false; refused(&p, EROFS); - } - - #[test] - fn host_store_full() { - let mut p = ready(); p.host_store_capacity = false; refused(&p, ENOSPC); - } - - #[test] - fn ready_binding_is_admitted() { - assert!(admit_enrol(&ready()).is_ok()); - } - - #[test] - fn clean_lane_requires_prepared_owner_before_first_capture() { - assert!(catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_OWNER, Some(0))); - assert!(!catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_USER, Some(0))); - assert!(!catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_MASTER, Some(0))); - assert!(catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_USER, Some(1))); - } - - #[test] - fn orphan_delete_requires_exact_linux_identity() { - let linux = [1u8; bio::UUID_LEN]; - let other_lane = [2u8; bio::UUID_LEN]; - assert!(admissible_orphan_target(&[linux], &linux).is_ok()); - refused_orphan(&[linux], &other_lane, ENOENT); - refused_orphan(&[linux], &[0; bio::UUID_LEN], EINVAL); - } - - fn refused_orphan(list: &[[u8; bio::UUID_LEN]], uuid: &[u8; bio::UUID_LEN], expected: Error) { - assert_eq!(admissible_orphan_target(list, uuid).unwrap_err().to_errno(), - expected.to_errno()); - } -} - impl SepData { fn sbio_enrol_step(&self, op: &crate::sbio::SbioOp) -> Option> { match self.sbio_call(op) { @@ -471,12 +349,6 @@ impl SepData { return self.enrol_into_existing_context(user); }; - // PREPARE_ENROL may already have selected the empty Linux context in - // this boot. A file left from an earlier boot is not that proof. - if self.empty_context_prepared.xchg(false, Relaxed) { - return self.enrol_into_existing_context(user); - } - self.open_fresh_context(user, &proof) } @@ -1905,7 +1777,6 @@ impl SepData { }; if !parsed.matches(user) { - dev_info!(self.dev, "verify: enclave found no identity in the Linux user context\n"); return bio::VerifyOutcome::NoMatch; } @@ -1913,12 +1784,6 @@ impl SepData { let identity = *parsed.identity_uuid(); let known = self.bio_index.lock().contains_uuid(&identity); - dev_info!( - self.dev, - "verify: enclave matched identity {} (host index {})\n", - Hex(&identity), if known { "present" } else { "absent" } - ); - if !known { dev_err!( self.dev, @@ -3539,133 +3404,6 @@ impl SepData { Some(bytes) } - fn require_linux_bio_lane(&self) -> Result<()> { - if !self.touchid_started.load(Relaxed) || self.xarm.lock().os_uuid.is_none() { - return Err(ENXIO); - } - if !self.keybag_designated.load(Relaxed) - || self.enrol_material.lock().is_none() - || !matches!(keybag::read(keybag::Slot::Identity), - Ok(keybag::State::Present(_))) { - return Err(ENODATA); - } - Ok(()) - } - - /// Refuse before ENROL_START schedules work or sends BEGIN_ENROL. A - /// userspace passwd lookup proves the host account; these are the durable - /// Linux-lane bindings that only the kernel can inspect. - fn preflight_enrol(&self, live_count: Option) -> Result<()> { - let lane = self.touchid_started.load(Relaxed) && self.xarm.lock().os_uuid.is_some(); - let bag_bound = self.keybag_designated.load(Relaxed) && self.enrol_material.lock() - .as_ref().is_some_and(|material| material.snapshot_identity.is_some()); - let kinds = [PRIVATE_TYPE_CATACOMB_OWNER, PRIVATE_TYPE_CATACOMB_USER, - PRIVATE_TYPE_CATACOMB_MASTER]; - let readable = kinds.map(|kind| !catacomb_needed_for_enrol(kind, live_count) - || crate::catacomb::read(kind).is_some()); - let writable = kinds.map(|kind| !catacomb_needed_for_enrol(kind, live_count) - || crate::catacomb::writable(kind)); - let index = self.bio_index.lock(); - let preconditions = EnrolPrerequisites { - lane, - live_count, - bag_bound, - catacomb_readable: readable, - catacomb_writable: writable, - keybag_writable: shim::StoreFile::open_writable_existing( - keybag::Slot::Identity.path()).is_ok(), - host_store_capacity: self.with_host_store(|store| index.can_persist_next(store)) - .unwrap_or(false), - }; - let result = admit_enrol(&preconditions); - if let Err(e) = &result { - dev_warn!(self.dev, "enrol: prerequisites failed before enclave capture: {:?}\n", e); - } - result - } - - fn bio_list_enclave(&self, arg: usize) -> Result { - bio::require_admin()?; - self.require_linux_bio_lane()?; - let ids = self.enclave_identities_for(SBIO_PROBE_USER_ID).ok_or(EIO)?; - let mut out = bio::EnclaveList::new(); - for uuid in ids.iter() { - out.push(*uuid)?; - } - kernel::uaccess::UserSlice::new( - kernel::uaccess::UserPtr::from_addr(arg), core::mem::size_of::() - ).writer().write(&out)?; - bio::ok() - } - - fn bio_delete_orphan(&self, arg: usize) -> Result { - bio::require_admin()?; - self.require_linux_bio_lane()?; - if !bio::idle_for_mutation(&self.bio_session.lock()) { return Err(EBUSY); } - let request: bio::Delete = kernel::uaccess::UserSlice::new( - kernel::uaccess::UserPtr::from_addr(arg), core::mem::size_of::() - ).reader().read()?; - // The exact SEP user and built-in selector are checked by the parser. - // The host index may still hold an orphan after fprintd lost its file, - // so that index cannot decide whether this exact-ID recovery is valid. - let ids = self.enclave_identities_for(SBIO_PROBE_USER_ID).ok_or(EIO)?; - admissible_orphan_target(&ids, &request.uuid)?; - bio::orphan_delete(crate::sbio::IdentityV1::from_index( - SBIO_PROBE_USER_ID, request.uuid)) - } - - fn bio_prepare_enrol(&self, arg: usize) -> Result { - bio::require_admin()?; - bio::validate_enrol_start_arg(arg)?; - self.require_linux_bio_lane()?; - if !bio::idle_for_mutation(&self.bio_session.lock()) { return Err(EBUSY); } - if !sensor::is_bound() { return Err(ENODEV); } - if !self.xart_writable() { return Err(EROFS); } - let ids = self.enclave_identities_for(SBIO_PROBE_USER_ID).ok_or(EIO)?; - if !ids.is_empty() { - // An existing identity must already have a durable owner file; - // selecting SYSTEM here could disturb its live context. - return if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_some() { - bio::ok() - } else { Err(ENODATA) }; - } - if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_some() { - return bio::ok(); - } - if !self.keybag_designated.load(Relaxed) - || !self.enrol_material.lock().as_ref() - .is_some_and(|material| material.snapshot_identity.is_some()) { - return Err(ENODATA); - } - if shim::StoreFile::open_writable_existing(keybag::Slot::Identity.path()).is_err() { - return Err(EROFS); - } - let index = self.bio_index.lock(); - if !self.with_host_store(|store| index.can_persist_next(store)).unwrap_or(false) { - return Err(ENOSPC); - } - drop(index); - let user = crate::sbio::UserId::new(SBIO_PROBE_USER_ID).ok_or(EINVAL)?; - // The host must be able to create and sync the owner file before a - // system-context write. A pre-existing invalid file is left intact. - crate::catacomb::prepare_missing_owner()?; - loop { - self.wait_until_awake()?; - let _writer = self.catacomb_writer.lock(); - if !self.capture_begin() { continue; } - let prepared = self.open_enrolment_context(user); - self.capture_end(); - if !prepared { return Err(EIO); } - break; - } - if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() - || !crate::catacomb::writable(PRIVATE_TYPE_CATACOMB_OWNER) { - return Err(EROFS); - } - self.empty_context_prepared.store(true, Relaxed); - bio::ok() - } - pub(crate) fn bio_ioctl(&self, cmd: u32, arg: usize) -> Result { if self.shutting_down.load(Relaxed) { return Err(ENODEV); @@ -3673,12 +3411,6 @@ impl SepData { if cmd == bio::IOC_ATTEST { return self.bio_attest(arg); } - if cmd == bio::IOC_LIST_ENCLAVE { - return self.bio_list_enclave(arg); - } - if cmd == bio::IOC_PREPARE_ENROL { - return self.bio_prepare_enrol(arg); - } // With xART writes off the enclave cannot persist what an enrolment or // a match changes. It saves anti-replay state after every match, and a // refused save has left it unresponsive until reboot. Refuse the @@ -3703,21 +3435,14 @@ impl SepData { let live_identity_count = if (cmd == bio::IOC_GET_INFO || cmd == bio::IOC_ENROL_START) && self.touchid_started.load(Relaxed) { - self.enclave_identities_for(SBIO_PROBE_USER_ID).map(|ids| ids.len()) + self.enclave_identity_count() } else { None }; - if cmd == bio::IOC_ENROL_START { - bio::require_admin()?; - bio::validate_enrol_start_arg(arg)?; - self.preflight_enrol(live_identity_count)?; - } // The SPI probe may have deferred until its SMC power supplier bound. // A saved attach-time snapshot would incorrectly reject later ioctls. let sensor_present = sensor::is_bound(); - let handled = if cmd == bio::IOC_DELETE_ORPHAN { - self.bio_delete_orphan(arg)? - } else { + let handled = { let mut session = self.bio_session.lock(); if bio::starts_capture(cmd) { self.wait_for_worker_exit(&mut session, false)?; @@ -3754,7 +3479,7 @@ impl SepData { if !self.capture_begin() { continue; } - let deleted = self.delete_and_persist(&handled, cmd == bio::IOC_DELETE_ORPHAN); + let deleted = self.delete_and_persist(&handled); self.capture_end(); break deleted; }; @@ -3997,7 +3722,7 @@ impl SepData { /// Called with `catacomb_writer` held. A host index entry goes only once /// its deletion is saved: if the save fails, the entry stays, userspace /// still lists the fingerprint, and a retried deletion saves again. - fn delete_and_persist(&self, handled: &bio::Handled, recovery: bool) -> Result<()> { + fn delete_and_persist(&self, handled: &bio::Handled) -> Result<()> { let retry = self.deletion_unsaved.load(Relaxed); let wanted = Iterator::chain(handled.delete_identity.iter(), handled.delete_identities.iter()); @@ -4005,13 +3730,10 @@ impl SepData { let mut gone: KVec<[u8; bio::UUID_LEN]> = KVec::with_capacity(wanted.clone().count(), GFP_KERNEL)?; let mut delete_err: Option = None; - let mut orphan_removed = false; for identity in wanted { - let orphan = recovery || !self.bio_index.lock().contains_uuid(identity.uuid()); - match self.delete_identity(identity, orphan) { + match self.delete_identity(identity) { Ok(true) => { let _ = gone.push_within_capacity(*identity.uuid()); - orphan_removed |= orphan; } Ok(false) => {} Err(e) => delete_err = delete_err.or(Some(e)), @@ -4019,7 +3741,7 @@ impl SepData { } // Save what was removed even if another deletion failed. if !gone.is_empty() { - match self.persist_deletion(retry, orphan_removed) { + match self.persist_deletion(retry) { Ok(()) => { self.deletion_unsaved.store(false, Relaxed); for uuid in gone.iter() { @@ -4044,7 +3766,7 @@ impl SepData { /// Returns whether the enclave no longer holds the identity and the host /// entry must wait for [`Self::persist_deletion`]. - fn delete_identity(&self, identity: &crate::sbio::IdentityV1, orphan: bool) -> Result { + fn delete_identity(&self, identity: &crate::sbio::IdentityV1) -> Result { if self.enclave_lists(identity.uuid()) == Some(false) { // Unless an earlier deletion's save failed, which leaves the // enclave without an identity the saved Catacomb still holds. @@ -4055,44 +3777,18 @@ impl SepData { return Ok(false); } - // 0x57 cannot be undone. Ensure the host can save the affected user - // and master Catacombs before sending it. For an explicit recovery the - // owner Catacomb provides the recovery path even when the bag snapshot - // has no binding; export that owner first if this install lacks a file. - for kind in [PRIVATE_TYPE_CATACOMB_USER, PRIVATE_TYPE_CATACOMB_MASTER] { - if crate::catacomb::read(kind).is_none() || !crate::catacomb::writable(kind) { - dev_err!( - self.dev, - "bio: not deleting: user/master Catacomb is unavailable or unwritable\n" - ); - return Err(EIO); - } - } - if orphan && crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() { - let Some(reply) = self.read_component_states() else { return Err(EIO); }; - let state = crate::sbio::ComponentStates::new(&reply) - .state_for(crate::sbio::CatacombUser::OWNER.value()); - if !state.is_some_and(|s| s & crate::sbio::COMPONENT_STATE_ACTIVE != 0) - || !self.save_catacomb(crate::sbio::CatacombUser::OWNER, - PRIVATE_TYPE_CATACOMB_OWNER, - c"orphan-recovery owner Catacomb") { - dev_err!(self.dev, "bio: not deleting: owner Catacomb export failed\n"); - return Err(EIO); - } - } + // 0x57 cannot be undone. Check first that its deletion can be saved. if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() - || !crate::catacomb::writable(PRIVATE_TYPE_CATACOMB_OWNER) + || !self + .enrol_material + .lock() + .as_ref() + .is_some_and(|material| material.snapshot_identity.is_some()) { - return Err(EIO); - } - if !orphan && !self.enrol_material.lock().as_ref() - .is_some_and(|material| material.snapshot_identity.is_some()) { - dev_err!(self.dev, "bio: not deleting: indexed identity has no identity-bag binding\n"); - return Err(EIO); - } - if orphan && !matches!(keybag::read(keybag::Slot::Identity), - Ok(keybag::State::Present(_))) { - dev_err!(self.dev, "bio: not deleting: Linux identity bag file is unavailable\n"); + dev_err!( + self.dev, + "bio: not deleting: there is no owner Catacomb file or identity-bag binding to save the deletion with; nothing was changed\n" + ); return Err(EIO); } @@ -4115,11 +3811,10 @@ impl SepData { /// the enclave marks save-pending (a SAVE_CATACOMB of one that is not has /// been refused), the user before the master because saving the user can /// change the master, state re-read before each, then the lockout and the - /// identity bag when a verified binding exists. An exact-ID orphan - /// recovery without that binding saves the Linux owner Catacomb instead. - /// Safe to repeat after a failure partway: a `retry` accepts a user - /// Catacomb an earlier attempt already saved. - fn persist_deletion(&self, retry: bool, orphan: bool) -> Result<()> { + /// identity bag, which must match the Catacombs. Safe to repeat after a + /// failure partway: a `retry` accepts a user Catacomb an earlier attempt + /// already saved. + fn persist_deletion(&self, retry: bool) -> Result<()> { let Some(user) = crate::sbio::UserId::new(SBIO_PROBE_USER_ID) else { return Err(EIO); }; @@ -4134,11 +3829,6 @@ impl SepData { PRIVATE_TYPE_CATACOMB_MASTER, c"master Catacomb after deletion", ), - ( - crate::sbio::CatacombUser::OWNER, - PRIVATE_TYPE_CATACOMB_OWNER, - c"owner Catacomb after deletion", - ), ] { let Some(reply) = self.read_component_states() else { dev_err!(self.dev, "bio: cannot read component state before saving the {}\n", what); @@ -4154,8 +3844,7 @@ impl SepData { dev_err!(self.dev, "bio: the {} is not active; the deletion cannot be saved\n", what); return Err(EIO); } - if state & crate::sbio::COMPONENT_STATE_SAVE_PENDING != 0 || - (orphan && who.value() == crate::sbio::CatacombUser::OWNER.value()) { + if state & crate::sbio::COMPONENT_STATE_SAVE_PENDING != 0 { if !self.save_catacomb(who, kind, what) { dev_err!(self.dev, "bio: saving the {} failed\n", what); return Err(EIO); @@ -4176,22 +3865,11 @@ impl SepData { dev_err!(self.dev, "bio: lockout persistence failed after the deletion\n"); return Err(EIO); } - let bag_bound = self.enrol_material.lock().as_ref() - .is_some_and(|material| material.snapshot_identity.is_some()); - if bag_bound { - if !self.resnapshot_identity_keybag() { - dev_err!( - self.dev, - "bio: Catacombs saved after the deletion but the identity-bag snapshot failed\n" - ); - return Err(EIO); - } - } else if orphan { - dev_info!( + if !self.resnapshot_identity_keybag() { + dev_err!( self.dev, - "bio: orphan deletion saved via Linux user/master/owner Catacombs without a bag snapshot binding\n" + "bio: Catacombs saved after the deletion but the identity-bag snapshot failed\n" ); - } else { return Err(EIO); } Ok(()) @@ -5000,47 +4678,6 @@ impl<'a> IdentityRecords<'a> { } } -#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] -#[::kernel::macros::kunit_tests(apple_sep_enclave_identity_scope)] -mod identity_scope_tests { - use super::*; - - #[test] - fn list_excludes_other_users_and_selectors() { - let mut reply = [0u8; 3 * IDENTITY_RECORD_LEN]; - for (i, (user, selector, byte)) in [ - (SBIO_PROBE_USER_ID, IR_SELECTOR_BUILTIN, 1u8), - (501, IR_SELECTOR_BUILTIN, 2u8), - (SBIO_PROBE_USER_ID, 2, 3u8), - ].iter().enumerate() { - let at = i * IDENTITY_RECORD_LEN; - reply[at..at + 4].copy_from_slice(&user.to_le_bytes()); - reply[at + 4..at + IDENTITY_V1_LEN].fill(*byte); - reply[at + IR_SELECTOR..at + IR_SELECTOR + 4] - .copy_from_slice(&selector.to_le_bytes()); - } - let records = IdentityRecords::new(&reply).unwrap(); - let mut seen = 0; - records.each_built_in_for(SBIO_PROBE_USER_ID, |identity| { - seen += 1; - assert!(*identity.uuid() == [1u8; IDENTITY_UUID_LEN]); - }); - assert_eq!(seen, 1); - } - - #[test] - fn match_result_carries_the_exact_enclave_identity() { - let mut reply = [0u8; SBIO_MATCH_RESULT_LEN]; - reply[MR_USER_ID..MR_USER_ID + 4] - .copy_from_slice(&SBIO_PROBE_USER_ID.to_le_bytes()); - reply[MR_IDENTITY..MR_IDENTITY + IDENTITY_UUID_LEN].fill(0xa5); - let result = MatchResult::parse(&reply).unwrap(); - let user = UserId::new(SBIO_PROBE_USER_ID).unwrap(); - assert!(result.matches(user)); - assert!(*result.identity_uuid() == [0xa5; IDENTITY_UUID_LEN]); - } -} - const FIC_DEVICE_BUILTIN: u32 = 1; pub(crate) const SBIO_FREE_COUNT_LEN: usize = 4 + 20; static_assert!(SBIO_FREE_COUNT_LEN == SBIO_SAVE_SELECTOR_LEN); diff --git a/drivers/soc/apple/sep.rs b/drivers/soc/apple/sep.rs index 5fd41c5bebf60..7d9f4f2af1869 100644 --- a/drivers/soc/apple/sep.rs +++ b/drivers/soc/apple/sep.rs @@ -784,9 +784,6 @@ struct SepData { // only under `catacomb_writer`. deletion_unsaved: Atomic, - // PREPARE_ENROL selected the empty Linux context during this boot. - empty_context_prepared: Atomic, - #[pin] rx_work: Work, @@ -986,7 +983,6 @@ impl SepData { worker_gone <- new_condvar!("SepData::worker_gone"), catacomb_writer <- new_mutex!(()), deletion_unsaved: Atomic::new(false), - empty_context_prepared: Atomic::new(false), rx_work <- new_work!("SepData::rx_work"), enrol_work <- new_work!("SepData::enrol_work"), verify_work <- new_work!("SepData::verify_work"), diff --git a/drivers/soc/apple/shim.h b/drivers/soc/apple/shim.h index ca0d3d8a797ee..99d2661e2cb1c 100644 --- a/drivers/soc/apple/shim.h +++ b/drivers/soc/apple/shim.h @@ -44,7 +44,6 @@ void sep_hwrng_unregister(void *mem); void *sep_store_open(const char *path); void *sep_store_open_host(const char *path, bool *created); void *sep_store_open_trunc(const char *path); -void *sep_store_open_writable_existing(const char *path); void *sep_store_open_ro(const char *path); void *sep_store_open_block(const char *path, int writable); void sep_store_close(void *handle); diff --git a/drivers/soc/apple/shim.rs b/drivers/soc/apple/shim.rs index 6d546a5603f37..8e30c3f54b817 100644 --- a/drivers/soc/apple/shim.rs +++ b/drivers/soc/apple/shim.rs @@ -9,7 +9,6 @@ extern "C" { fn sep_store_open(path: *const c_char) -> *mut c_void; fn sep_store_open_host(path: *const c_char, created: *mut bool) -> *mut c_void; fn sep_store_open_trunc(path: *const c_char) -> *mut c_void; - fn sep_store_open_writable_existing(path: *const c_char) -> *mut c_void; fn sep_store_open_ro(path: *const c_char) -> *mut c_void; fn sep_store_open_block(path: *const c_char, writable: c_int) -> *mut c_void; fn sep_store_close(handle: *mut c_void); @@ -81,15 +80,6 @@ impl StoreFile { Ok(StoreFile { handle }) } - pub(crate) fn open_writable_existing(path: &CStr) -> Result { - // SAFETY: `path` is NUL-terminated; this open cannot create or truncate. - let handle = unsafe { sep_store_open_writable_existing(path.as_char_ptr()) }; - if handle.is_null() { - return Err(EROFS); - } - Ok(StoreFile { handle }) - } - pub(crate) fn open_readonly(path: &CStr) -> Result { // SAFETY: `path` is NUL-terminated; the shim returns NULL on failure. let handle = unsafe { sep_store_open_ro(path.as_char_ptr()) }; diff --git a/drivers/soc/apple/store.rs b/drivers/soc/apple/store.rs index d9930332cab99..e3abe11c7d5c8 100644 --- a/drivers/soc/apple/store.rs +++ b/drivers/soc/apple/store.rs @@ -263,9 +263,6 @@ impl Store { pr_info!("created a new host-state store\n"); return Ok(store); } - if size < STORE_SIZE as u64 { - return Err(store.refuse(Damage::Size)); - } let mut sb = KVec::with_capacity(BLOCK_SIZE, GFP_KERNEL)?; sb.resize(BLOCK_SIZE, 0, GFP_KERNEL)?; @@ -362,12 +359,6 @@ impl Store { self.slots.iter().position(|s| !s.used) } - /// Admission check for the copy-on-write slot used by the next write. - /// Even replacing an existing key needs a free slot until commit. - pub(crate) fn can_write(&self, key: &Key, len: usize) -> bool { - valid_kind(key.kind) && len <= MAX_VALUE && self.find_free().is_some() - } - fn block_offset(slot: usize) -> u64 { ((slot + 1) * BLOCK_SIZE) as u64 } diff --git a/drivers/soc/apple/store_shim.c b/drivers/soc/apple/store_shim.c index 77319a4702164..d50270d717be5 100644 --- a/drivers/soc/apple/store_shim.c +++ b/drivers/soc/apple/store_shim.c @@ -83,14 +83,6 @@ void *sep_store_open_trunc(const char *path) return IS_ERR(f) ? NULL : f; } -/* Admission check only: never create or truncate an existing record. */ -void *sep_store_open_writable_existing(const char *path) -{ - struct file *f = open_as_kernel(path, O_WRONLY | O_LARGEFILE, 0); - - return IS_ERR(f) ? NULL : f; -} - /* * Opens an existing file read-only, NULL if absent. O_RDONLY and no O_CREAT so * the seed cannot be created, truncated or written by mistake. diff --git a/include/uapi/linux/apple-sep.h b/include/uapi/linux/apple-sep.h index fbae982a4c0ef..394b497ff7a59 100644 --- a/include/uapi/linux/apple-sep.h +++ b/include/uapi/linux/apple-sep.h @@ -14,7 +14,7 @@ #include #include -#define SEP_BIO_IFACE_VERSION 5 +#define SEP_BIO_IFACE_VERSION 4 #define SEP_BIO_UUID_LEN 16 #define SEP_BIO_LABEL_LEN 128 @@ -64,22 +64,10 @@ struct sep_bio_list { struct sep_bio_enrol_start { __u32 flags; - __u32 account_uid; /* resolved passwd uid; UINT32_MAX is invalid */ - __u32 finger; /* FP_FINGER_LEFT_THUMB..FP_FINGER_RIGHT_LITTLE (1..10) */ __u32 reserved; __u8 label[SEP_BIO_LABEL_LEN]; }; -/* Linux's built-in SEP user context only. This does not enumerate other SEP - * users or OS lanes. DELETE_ORPHAN uses an exact UUID; the kernel index may - * still hold it when fprintd has already lost its own record. - */ -struct sep_bio_enclave_list { - __u32 count; - __u32 reserved; - __u8 uuid[SEP_BIO_MAX_IDENTITIES][SEP_BIO_UUID_LEN]; -}; - #define SEP_BIO_GUIDANCE_NONE 0 #define SEP_BIO_GUIDANCE_PLACE 1 #define SEP_BIO_GUIDANCE_LIFT_AND_MOVE 2 @@ -141,11 +129,5 @@ struct sep_bio_attest { #define SEP_BIO_DELETE _IOW(SEP_BIO_IOC_MAGIC, 0x08, struct sep_bio_delete) #define SEP_BIO_DELETE_ALL _IO(SEP_BIO_IOC_MAGIC, 0x09) #define SEP_BIO_ATTEST _IOWR(SEP_BIO_IOC_MAGIC, 0x0a, struct sep_bio_attest) -#define SEP_BIO_LIST_ENCLAVE _IOR(SEP_BIO_IOC_MAGIC, 0x0b, struct sep_bio_enclave_list) -#define SEP_BIO_DELETE_ORPHAN _IOW(SEP_BIO_IOC_MAGIC, 0x0c, struct sep_bio_delete) -/* Creates the empty Linux owner context only when SEP lists zero Linux - * identities. ENROL_START itself never repairs a missing persistence binding. - */ -#define SEP_BIO_PREPARE_ENROL _IOW(SEP_BIO_IOC_MAGIC, 0x0d, struct sep_bio_enrol_start) #endif /* _UAPI_LINUX_APPLE_SEP_H */ From ab3a90c16d8710970889428e061f4dc680ca4f8e Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sat, 31 Oct 2026 09:52:58 +1100 Subject: [PATCH 2/2] pmdomain: apple: pmgr-pwrstate: hold the two-parent power-on until the J616s DT has it This reverts commit 3db9b1f2bbb7 for the fleet kernel. The commit lets DISPEXT1_SYS on the J616s register below both of its parents. Its own pull request (#628) says the Stage 2 DTB has to carry the dcpext1 power-domains first; without them genpd sees DISPEXT1_FE as unused and turns it off under the running dcpext1. The J616s still runs the old Stage 2 DTB, and a kernel with this commit never came back from two one-shot boots there (it fell back to the previous kernel both times). Keep the old behaviour, which only logs the -22 for power-controller@490, until the J616s Stage 2 DTB is refreshed. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/pmdomain/apple/pmgr-pwrstate.c | 60 -------------------------- 1 file changed, 60 deletions(-) diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c index 5ebd7a51172f3..bfcfb5ccbf1a4 100644 --- a/drivers/pmdomain/apple/pmgr-pwrstate.c +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -371,63 +370,12 @@ static int apple_pmgr_reset_xlate(struct reset_controller_dev *rcdev, return 0; } -/* - * genpd adds a power state that is on only below parents that are on. With a - * single parent the platform bus has already attached this device to it and - * powered it on before probe; with more than one it attaches to none, so a - * domain the boot loader left on (DISPEXT1_SYS on the J616s, below FAB7_SOC - * and its PMP request) could not be added. Power every parent on through a - * virtual device until the subdomain links hold them. - */ -static int apple_pmgr_ps_hold_parents(struct apple_pmgr_ps *ps, bool active, - struct dev_pm_domain_list **parents) -{ - static const struct dev_pm_domain_attach_data data = { - .pd_flags = PD_FLAG_NO_DEV_LINK, - }; - struct dev_pm_domain_list *list = NULL; - unsigned int i; - int ret; - - *parents = NULL; - if (!active || of_count_phandle_with_args(ps->dev->of_node, "power-domains", - "#power-domain-cells") < 2) - return 0; - - ret = dev_pm_domain_attach_list(ps->dev, &data, &list); - if (ret <= 0) - return ret; - for (i = 0; i < list->num_pds; i++) { - ret = pm_runtime_resume_and_get(list->pd_devs[i]); - if (ret < 0) { - while (i--) - pm_runtime_put(list->pd_devs[i]); - dev_pm_domain_detach_list(list); - return dev_err_probe(ps->dev, ret, "cannot power parent domain %u\n", i); - } - } - *parents = list; - return 0; -} - -static void apple_pmgr_ps_release_parents(struct dev_pm_domain_list *parents) -{ - unsigned int i; - - if (!parents) - return; - for (i = 0; i < parents->num_pds; i++) - pm_runtime_put(parents->pd_devs[i]); - dev_pm_domain_detach_list(parents); -} - static int apple_pmgr_ps_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; struct apple_pmgr_ps *ps; struct regmap *regmap; - struct dev_pm_domain_list *parents = NULL; struct of_phandle_iterator it; int ret; const char *name; @@ -580,10 +528,6 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) return ret; } - ret = apple_pmgr_ps_hold_parents(ps, active, &parents); - if (ret < 0) - goto err_remove; - of_for_each_phandle(&it, ret, node, "power-domains", "#power-domain-cells", -1) { struct of_phandle_args parent, child; @@ -604,9 +548,6 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) } } - /* The subdomain links hold the parents now. */ - apple_pmgr_ps_release_parents(parents); - /* * Do not participate in regular PM; parent power domains are handled via the * genpd hierarchy. @@ -626,7 +567,6 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) return 0; err_remove: - apple_pmgr_ps_release_parents(parents); of_genpd_del_provider(node); pm_genpd_remove(&ps->genpd); return ret;