Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Documentation/ABI/testing/sysfs-driver-apple-sep
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,36 @@ Description:
tools/aurora-sep/XART-SAFETY.md. During a boot the value
can only move from "unverified" to "blocked", once the guard
refuses a write.

What: /sys/bus/platform/devices/.../complete_attach
Date: November 2026
Contact: Aurora Silicon maintainers
Description:
Write-only, root-only (0200). Writing 1 completes an attach
started with apple_sep.keystore_only=1: opens host state and
the identity index on the real root, reopens xART with the
configured write and guard policy, and publishes the biometric
devices. Sensor activation still occurs on first device open,
just as with a normal attach. No SEP registration is repeated.
Successful completion is idempotent. Failed store opens may
be retried. Returns EINVAL for other values, or if the driver
was loaded without keystore_only. The read-only module
parameter retains its load-time value after completion.

In keystore-only mode, xART is read-only regardless of
xart_writes, provisioning is disabled, and no host-state or
guard files are opened for writing. The initrd must carry the
existing identity keybag and machine ref-key at their usual
/var/lib paths; both are cached on first trusted-key use.
An unseal needing an xART write fails with EBADMSG so userspace
can use its passphrase fallback.

With apple_sep.keystore_only=2 the initrd phase also serves a
password unlock of a per-user key bag on /dev/sep-bio-rearm:
only the state, import, unlock, lock and FDE unseal requests;
every other request returns EAGAIN until complete_attach.
Loading and unlocking a bag needs enclave writes to xART, so
in this mode xART follows xart_writes, but only when the xART
guard is off (its record list lives in host files on the real
root). With a guard, xART stays read-only, the bag requests
fail with EROFS and the volume falls back to its passphrase.
2 changes: 1 addition & 1 deletion drivers/soc/apple/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ obj-$(CONFIG_APPLE_SEP_FV_INTERFACE) += fv_shim.o
obj-$(CONFIG_APPLE_SEP) += apple-sep.o
apple-sep-y := sep.o hwrng_shim.o store_shim.o bio_shim.o sha_shim.o \
crypto_shim.o sensor_shim.o p256_shim.o trusted_shim.o work_shim.o \
pm_shim.o diag_shim.o
pm_shim.o diag_shim.o attach_shim.o

obj-$(CONFIG_APPLE_PMP) += apple-pmp.o
apple-pmp-y := pmp.o pmp-export.o pmp-display.o pmp-telemetry.o
Expand Down
183 changes: 183 additions & 0 deletions drivers/soc/apple/attach.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
//! The initrd attach phase and its real-root completion.

use kernel::{prelude::*, sync::Arc};

use crate::SepData;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Phase {
Normal,
KeyStoreOnly,
Complete,
}

impl Phase {
pub(crate) fn new(keystore_only: bool) -> Self {
if keystore_only {
Self::KeyStoreOnly
} else {
Self::Normal
}
}

pub(crate) fn needs_completion(self) -> Result<bool> {
match self {
Self::Normal => Err(EINVAL),
Self::KeyStoreOnly => Ok(true),
Self::Complete => Ok(false),
}
}

/// Serialize this under attach_phase. A failed operation stays retryable;
/// success never repeats the operation, even with concurrent sysfs writers.
pub(crate) fn complete(&mut self, operation: impl FnOnce() -> Result<()>) -> Result<()> {
if self.needs_completion()? {
operation()?;
*self = Self::Complete;
}
Ok(())
}
}

/// An enclave success cannot override a refused persistent-state write.
pub(crate) fn unseal_allowed(before: u32, after: u32) -> bool {
before == after
}

extern "C" {
fn sep_keystore_only() -> bool;
fn sep_keystore_user_bags() -> bool;
fn sep_complete_attach_register(
dev: *mut c_void,
ctx: *mut c_void,
complete: unsafe extern "C" fn(*mut c_void) -> c_int,
) -> c_int;
fn sep_complete_attach_unregister(dev: *mut c_void);
}

pub(crate) fn requested() -> bool {
// SAFETY: the parameter is immutable after module loading.
unsafe { sep_keystore_only() }
}

/// `keystore_only=2`: the initrd also unlocks per-user key bags, so that a
/// volume sealed to a user's password can open before the real root.
pub(crate) fn user_bags_requested() -> bool {
// SAFETY: the parameter is immutable after module loading.
unsafe { sep_keystore_user_bags() }
}

/// Whether the initrd phase may let the enclave write the gigalocker. A
/// per-user bag cannot be loaded or unlocked without those writes, but the
/// xART guard keeps its record list in host files on the real root, which
/// the initrd must not write. So writes are allowed only for user bags and
/// only when no guard runs; with a guard the bags wait for complete_attach.
pub(crate) fn initrd_xart_writes(xart_writes: bool, user_bags: bool, guard: Option<u8>) -> bool {
xart_writes && user_bags && guard.is_none()
}

/// Holds the callback context alive until sysfs has drained all writers.
pub(crate) struct Attribute {
sep: Arc<SepData>,
registered: bool,
}

impl Attribute {
pub(crate) fn register(sep: Arc<SepData>) -> Result<Self> {
// SAFETY: the owned Arc pins the context until unregister has drained
// sysfs callbacks. The shim keeps no pointer after unregister returns.
kernel::error::to_result(unsafe {
sep_complete_attach_register(
sep.dev.as_raw().cast(),
Arc::as_ptr(&sep).cast_mut().cast(),
complete,
)
})?;
Ok(Self {
sep,
registered: true,
})
}

pub(crate) fn unregister(&mut self) {
if self.registered {
// SAFETY: matched to the successful register and called only once.
unsafe { sep_complete_attach_unregister(self.sep.dev.as_raw().cast()) };
self.registered = false;
}
}
}

impl Drop for Attribute {
fn drop(&mut self) {
self.unregister();
}
}

/// # Safety
/// The shim calls with the context pinned by Attribute, under sysfs active
/// protection; unregister waits for this callback before releasing the Arc.
unsafe extern "C" fn complete(ctx: *mut c_void) -> c_int {
// SAFETY: guaranteed by the registration above.
let sep = unsafe { &*ctx.cast::<SepData>() };
match sep.complete_attach() {
Ok(()) => 0,
Err(e) => e.to_errno(),
}
}

#[kernel::macros::kunit_tests(apple_sep_attach)]
mod tests {
use super::*;

#[test]
fn normal_attach_rejects_completion() {
let mut phase = Phase::new(false);
assert_eq!(
phase.complete(|| panic!("must not touch stores")),
Err(EINVAL)
);
}

#[test]
fn completion_is_idempotent() {
let mut phase = Phase::new(true);
let mut calls = 0;
assert_eq!(
phase.complete(|| {
calls += 1;
Ok(())
}),
Ok(())
);
assert_eq!(phase.complete(|| panic!("must not repeat attach")), Ok(()));
assert_eq!(calls, 1);
}

#[test]
fn failed_completion_can_retry() {
let mut phase = Phase::new(true);
assert_eq!(phase.complete(|| Err(EIO)), Err(EIO));
assert_eq!(phase.needs_completion(), Ok(true));
assert_eq!(phase.complete(|| Ok(())), Ok(()));
assert_eq!(phase.needs_completion(), Ok(false));
}

#[test]
fn initrd_writes_only_for_user_bags_without_a_guard() {
assert!(initrd_xart_writes(true, true, None));
assert!(!initrd_xart_writes(false, true, None));
assert!(!initrd_xart_writes(true, false, None));
assert!(!initrd_xart_writes(true, true, Some(1)));
assert!(!initrd_xart_writes(true, true, Some(2)));
}

#[test]
fn refused_initrd_write_fails_unseal_closed() {
assert!(unseal_allowed(0, 0));
assert!(unseal_allowed(17, 17));
assert!(!unseal_allowed(0, 1));
assert!(!unseal_allowed(u32::MAX, 0));
}
}
55 changes: 55 additions & 0 deletions drivers/soc/apple/attach_shim.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Initrd mode parameter and the root-only completion trigger. */
#include <linux/device.h>
#include <linux/moduleparam.h>
#include <linux/sysfs.h>

#include "shim.h"

/* Rust's module! parameters currently have no sysfs getter. */
static unsigned char keystore_only;
module_param(keystore_only, byte, 0444);
MODULE_PARM_DESC(keystore_only,
"Initrd attach: 1 = trusted keys only, 2 = also per-user key bags for a password unlock; defer persistent host state and Touch ID until complete_attach");

bool sep_keystore_only(void)
{
return keystore_only != 0;
}

bool sep_keystore_user_bags(void)
{
return keystore_only == 2;
}

/* One SEP per machine. device_remove_file drains callbacks before release. */
static void *sep_attach_ctx;
static int (*sep_attach_complete)(void *ctx);

static ssize_t complete_attach_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;

if (!sysfs_streq(buf, "1"))
return -EINVAL;
ret = sep_attach_complete(sep_attach_ctx);
return ret ? ret : count;
}
static DEVICE_ATTR_WO(complete_attach);

int sep_complete_attach_register(struct device *dev, void *ctx,
int (*complete)(void *ctx))
{
sep_attach_ctx = ctx;
sep_attach_complete = complete;
return device_create_file(dev, &dev_attr_complete_attach);
}

void sep_complete_attach_unregister(struct device *dev)
{
device_remove_file(dev, &dev_attr_complete_attach);
sep_attach_ctx = NULL;
sep_attach_complete = NULL;
}
20 changes: 20 additions & 0 deletions drivers/soc/apple/diag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ extern "C" {
fn sep_diag_set_attach(attached: bool);
fn sep_diag_set_endpoints(count: c_uint);
fn sep_diag_set_keystore_open();
fn sep_diag_keystore_ended();
fn sep_diag_set_xart(writable: bool);
fn sep_diag_set_keybag(state: c_int);
fn sep_diag_sensor_bind() -> c_uint;
fn sep_diag_set_sensor_result(bind: c_uint, online: bool);
Expand Down Expand Up @@ -114,6 +116,24 @@ pub(crate) fn register(
kernel::error::to_result(ret)
}

pub(crate) fn xart_state(writable: bool, guard: Option<(u8, bool)>) {
XART_WRITABLE.store(writable, Relaxed);
let (level, loaded) = guard.unwrap_or((0, false));
GUARD_LEVEL.store(u32::from(level), Relaxed);
GUARD_LIST.store(loaded, Relaxed);
REFUSALS.store(0, Relaxed);
// SAFETY: each setter only publishes diagnostic state.
unsafe {
sep_diag_set_xart(writable);
sep_diag_set_xart_guard(c_uint::from(level), loaded, matching() as c_int);
}
}

pub(crate) fn keystore_ended() {
// SAFETY: no preconditions; updates one diagnostic word.
unsafe { sep_diag_keystore_ended() };
}

pub(crate) fn attach(attached: bool) {
// SAFETY: no preconditions; stores one word.
unsafe { sep_diag_set_attach(attached) };
Expand Down
24 changes: 17 additions & 7 deletions drivers/soc/apple/diag_shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ static DEVICE_ATTR_RO(protocol);
static ssize_t xart_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%s\n", sep_diag.xart ? "enabled" : "disabled");
return sysfs_emit(buf, "%s\n", READ_ONCE(sep_diag.xart) ? "enabled" : "disabled");
}
static DEVICE_ATTR_RO(xart);

Expand Down Expand Up @@ -289,7 +289,7 @@ static DEVICE_ATTR_RO(restore);
static ssize_t xart_guard_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%u\n", sep_diag.xart_guard);
return sysfs_emit(buf, "%u\n", READ_ONCE(sep_diag.xart_guard));
}
static DEVICE_ATTR_RO(xart_guard);

Expand All @@ -298,9 +298,9 @@ static ssize_t xart_guard_list_show(struct device *dev,
{
const char *state;

if (!sep_diag.xart_guard)
if (!READ_ONCE(sep_diag.xart_guard))
state = "none";
else if (sep_diag.xart_guard_list)
else if (READ_ONCE(sep_diag.xart_guard_list))
state = "loaded";
else
state = "unavailable";
Expand Down Expand Up @@ -481,8 +481,8 @@ void sep_diag_set_restore(int state)
void sep_diag_set_xart_guard(unsigned int level, bool list_loaded,
int matching)
{
sep_diag.xart_guard = level;
sep_diag.xart_guard_list = list_loaded;
WRITE_ONCE(sep_diag.xart_guard, level);
WRITE_ONCE(sep_diag.xart_guard_list, list_loaded);
atomic_set(&sep_diag.xart_refusals, 0);
atomic_set(&sep_diag.matching, matching);
}
Expand All @@ -499,10 +499,20 @@ void sep_diag_xart_guard_refused(int matching)
* and it is not repeated. Whatever it left undecided is final for this boot:
* the key store stays closed and Touch ID cannot start.
*/
void sep_diag_bringup_ended(void)
void sep_diag_set_xart(bool writable)
{
WRITE_ONCE(sep_diag.xart, writable);
}

void sep_diag_keystore_ended(void)
{
atomic_cmpxchg(&sep_diag.keystore, SEP_DIAG_KEYSTORE_UNKNOWN,
SEP_DIAG_KEYSTORE_CLOSED);
}

void sep_diag_bringup_ended(void)
{
sep_diag_keystore_ended();
atomic_cmpxchg(&sep_diag.touchid, SEP_DIAG_TOUCHID_PENDING,
SEP_DIAG_TOUCHID_UNAVAILABLE);
}
Loading
Loading