Skip to content
Open
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
15 changes: 6 additions & 9 deletions drivers/soc/apple/bio.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// Copyright 2026 Dj

Expand Down Expand Up @@ -279,22 +279,19 @@
let Some(raw) = store.read(&identity_key())? else {
return Ok(index);
};
if raw.len() < 4 {
return Ok(index);
if raw.len() < 8 {
return Err(EINVAL);
}
if u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) != INDEX_FORMAT {
return Err(EINVAL);
}
if raw.len() < 8 {
return Ok(index);
}
let count = u32::from_le_bytes([raw[4], raw[5], raw[6], raw[7]]) as usize;
let stride = UUID_LEN + LABEL_LEN;
for i in 0..count.min(MAX_IDENTITIES) {
if count > MAX_IDENTITIES || raw.len() != 8 + count * stride {
return Err(EINVAL);
}
for i in 0..count {
let at = 8 + i * stride;
if at + stride > raw.len() {
break;
}
let mut entry = Identity::ZERO;
entry.uuid.copy_from_slice(&raw[at..at + UUID_LEN]);
entry
Expand Down
43 changes: 30 additions & 13 deletions drivers/soc/apple/catacomb.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// Copyright 2026 Dj

Expand Down Expand Up @@ -46,28 +46,45 @@
file.sync()
}

pub(crate) fn read(kind: u8) -> Option<KVec<u8>> {
let path = path_for(kind)?;
let file = shim::StoreFile::open_readonly(path).ok()?;
let size = file.size().ok()?;
/// `Ok(None)` only when the file is missing. A file that exists but cannot be
/// read or fails validation is an error: "cannot tell" must never read as
/// "nothing saved", or a caller may select a fresh context over a saved one.
pub(crate) fn read(kind: u8) -> Result<Option<KVec<u8>>> {
let path = path_for(kind).ok_or(EINVAL)?;
let file = match shim::StoreFile::open_readonly(path) {
Ok(file) => file,
Err(e) if e == ENOENT => return Ok(None),
Err(e) => return Err(e),
};
let size = file.size()?;
if size < HEADER as u64 {
return None;
return Err(EINVAL);
}
let mut head = [0u8; HEADER];
file.read_exact(0, &mut head).ok()?;
file.read_exact(0, &mut head)?;
if head[..MAGIC.len()] != MAGIC {
return None;
return Err(EINVAL);
}
let len = u32::from_le_bytes([head[8], head[9], head[10], head[11]]) as usize;
let crc = u16::from_le_bytes([head[12], head[13]]);
if len == 0 || len > MAX_CATACOMB || size != (HEADER + len) as u64 {
return None;
return Err(EINVAL);
}
let mut blob: KVec<u8> = KVec::with_capacity(len, GFP_KERNEL).ok()?;
blob.resize(len, 0, GFP_KERNEL).ok()?;
file.read_exact(HEADER as u64, &mut blob).ok()?;
let mut blob: KVec<u8> = KVec::with_capacity(len, GFP_KERNEL)?;
blob.resize(len, 0, GFP_KERNEL)?;
file.read_exact(HEADER as u64, &mut blob)?;
if crc16_ccitt_false(&blob) != crc {
return None;
return Err(EINVAL);
}
Some(blob)
Ok(Some(blob))
}

/// True only for a present, valid file.
pub(crate) fn is_saved(kind: u8) -> bool {
matches!(read(kind), Ok(Some(_)))
}

/// True only when the file is provably missing.
pub(crate) fn is_absent(kind: u8) -> bool {
matches!(read(kind), Ok(None))
}
9 changes: 9 additions & 0 deletions drivers/soc/apple/keybag.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// Copyright 2026 Dj

Expand Down Expand Up @@ -177,6 +177,15 @@
head[OFF_STATE + 3],
]);
if state == STATE_REFUSED {
let expected_crc = crc16_ccitt_false(&checksum_input(state, &[], &[0; UUID_LEN], &[])?);
let crc = u16::from_le_bytes([head[OFF_CRC], head[OFF_CRC + 1]]);
if size != HEADER as u64
|| crc != expected_crc
|| head[OFF_LEN..OFF_CRC].iter().any(|byte| *byte != 0)
|| head[OFF_CRC + 2..].iter().any(|byte| *byte != 0)
{
return Err(EINVAL);
}
return Ok(State::Absent(NoStoredKeyBag { slot }));
}
let Some(provenance) = UuidProvenance::from_state(state) else {
Expand Down
14 changes: 13 additions & 1 deletion drivers/soc/apple/refkey.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// Copyright 2026 Dj
//! Machine ref-key: Secure-Enclave key sealing, signing, and attestation.
Expand Down Expand Up @@ -177,7 +177,19 @@
return;
}

if let Ok(f) = shim::StoreFile::open_readonly(path) {
let existing = match shim::StoreFile::open_readonly(path) {
Ok(file) => Some(file),
Err(ENOENT) => None,
Err(error) => {
dev_err!(
self.dev,
"sks: cannot read existing machine ref-key: {:?}; refusing replacement\n",
error
);
return;
}
};
if let Some(f) = existing {
let sz = f.size().unwrap_or(0);
if (1..=8192).contains(&sz) {
let mut blob: KVec<u8> = KVec::new();
Expand Down
55 changes: 34 additions & 21 deletions drivers/soc/apple/sbio.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// Copyright 2026 Dj
// Copyright 2026 Ryan Murray
Expand Down Expand Up @@ -424,7 +424,7 @@
dev_err!(self.dev, "enrol: opt-in fresh-context owner export failed\n");
return false;
}
if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() {
if !crate::catacomb::is_saved(PRIVATE_TYPE_CATACOMB_OWNER) {
dev_err!(self.dev, "enrol: opt-in fresh context has no owner file\n");
return false;
}
Expand Down Expand Up @@ -510,9 +510,9 @@
return;
}
if self.templates_restored.load(Relaxed)
|| crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_some()
|| crate::catacomb::read(PRIVATE_TYPE_CATACOMB_MASTER).is_none()
|| crate::catacomb::read(PRIVATE_TYPE_CATACOMB_USER).is_none()
|| !crate::catacomb::is_absent(PRIVATE_TYPE_CATACOMB_OWNER)
|| !crate::catacomb::is_saved(PRIVATE_TYPE_CATACOMB_MASTER)
|| !crate::catacomb::is_saved(PRIVATE_TYPE_CATACOMB_USER)
{
dev_warn!(self.dev, "owner-probe: preconditions not met; no context selected\n");
return;
Expand Down Expand Up @@ -648,8 +648,10 @@
crate::sbio::ComponentAction::AlreadyActive => RestoreOutcome::AlreadyActive,
crate::sbio::ComponentAction::Unsupported => RestoreOutcome::Failed,
crate::sbio::ComponentAction::Load => {
let Some(blob) = self.read_stored(kind, what) else {
return RestoreOutcome::NoStoredFile;
let blob = match self.read_stored(kind, what) {
Ok(Some(blob)) => blob,
Ok(None) => return RestoreOutcome::NoStoredFile,
Err(_) => return RestoreOutcome::Failed,
};

let at = crate::sbio::SBIO_SAVED_USER_ID_AT;
Expand Down Expand Up @@ -766,8 +768,10 @@
}

fn restore_lockout(&self) -> RestoreOutcome {
let Some(blob) = self.read_stored(PRIVATE_TYPE_LOCKOUT, c"bio lockout record") else {
return RestoreOutcome::NoStoredFile;
let blob = match self.read_stored(PRIVATE_TYPE_LOCKOUT, c"bio lockout record") {
Ok(Some(blob)) => blob,
Ok(None) => return RestoreOutcome::NoStoredFile,
Err(_) => return RestoreOutcome::Failed,
};
let Some(request) = crate::sbio::SbioLoadLockout::new(&blob) else {
return RestoreOutcome::Failed;
Expand All @@ -787,17 +791,26 @@
}
}

fn read_stored(&self, kind: u8, _what: &CStr) -> Option<KVec<u8>> {
/// `Ok(None)` means provably not stored; any read failure is an error so
/// it cannot be mistaken for a missing file.
fn read_stored(&self, kind: u8, what: &CStr) -> Result<Option<KVec<u8>>> {
if crate::catacomb::is_kind(kind) {
if let Some(blob) = crate::catacomb::read(kind) {
return Some(blob);
match crate::catacomb::read(kind) {
Ok(Some(blob)) => return Ok(Some(blob)),
Ok(None) => {}
Err(e) => {
dev_err!(self.dev, "restore: cannot read {}: {:?}\n", what, e);
return Err(e);
}
}
}
match self.with_host_store(|store| store.read(&store::Key::root(kind))) {
Some(Ok(Some(blob))) => Some(blob),
Some(Ok(None)) => None,
Some(Err(_)) => None,
None => None,
Some(Ok(found)) => Ok(found),
Some(Err(e)) => {
dev_err!(self.dev, "restore: cannot read {}: {:?}\n", what, e);
Err(e)
}
None => Ok(None),
}
}

Expand Down Expand Up @@ -851,7 +864,7 @@
}
if (who.value() == user.value()
|| who.value() == crate::sbio::CatacombUser::MASTER.value())
&& crate::catacomb::read(kind).is_none()
&& !crate::catacomb::is_saved(kind)
{
dev_err!(self.dev, "enrol: {} has no durable host file\n", what);
return false;
Expand All @@ -865,7 +878,7 @@
}

fn save_all_components_user_first(&self, user: crate::sbio::UserId) -> bool {
if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() {
if !crate::catacomb::is_saved(PRIVATE_TYPE_CATACOMB_OWNER) {
dev_err!(self.dev, "enrol: opt-in completion has no owner Catacomb file\n");
return false;
}
Expand Down Expand Up @@ -904,7 +917,7 @@
if !self.save_catacomb(who, kind, what) {
return false;
}
if crate::catacomb::read(kind).is_none() {
if !crate::catacomb::is_saved(kind) {
dev_err!(self.dev, "enrol: opt-in {} has no durable host file\n", what);
return false;
}
Expand Down Expand Up @@ -960,7 +973,7 @@
dev_err!(self.dev, "verify: {} persistence failed\n", what);
return false;
}
if crate::catacomb::read(kind).is_none() {
if !crate::catacomb::is_saved(kind) {
dev_err!(self.dev, "verify: {} has no durable host file\n", what);
return false;
}
Expand Down Expand Up @@ -3777,7 +3790,7 @@
}

// 0x57 cannot be undone. Check first that its deletion can be saved.
if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none()
if !crate::catacomb::is_saved(PRIVATE_TYPE_CATACOMB_OWNER)
|| !self
.enrol_material
.lock()
Expand Down Expand Up @@ -3855,7 +3868,7 @@
);
return Err(EIO);
}
if crate::catacomb::read(kind).is_none() {
if !crate::catacomb::is_saved(kind) {
dev_err!(self.dev, "bio: the {} has no durable host file\n", what);
return Err(EIO);
}
Expand Down
25 changes: 10 additions & 15 deletions drivers/soc/apple/shim.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// Copyright 2026 Dj

Expand Down Expand Up @@ -50,11 +50,9 @@

impl StoreFile {
pub(crate) fn open(path: &CStr) -> Result<StoreFile> {
// SAFETY: `path` is NUL-terminated; the shim returns NULL on failure.
let handle = unsafe { sep_store_open(path.as_char_ptr()) };
if handle.is_null() {
return Err(ENOENT);
}
// SAFETY: `path` is NUL-terminated; the shim returns a live file or
// an error pointer, as required by `from_err_ptr`.
let handle = unsafe { kernel::error::from_err_ptr(sep_store_open(path.as_char_ptr()))? };
Ok(StoreFile { handle })
}

Expand All @@ -72,20 +70,17 @@
}

pub(crate) fn open_trunc(path: &CStr) -> Result<StoreFile> {
// SAFETY: `path` is NUL-terminated; the shim returns NULL on failure.
let handle = unsafe { sep_store_open_trunc(path.as_char_ptr()) };
if handle.is_null() {
return Err(ENOENT);
}
// SAFETY: `path` is NUL-terminated; the shim returns a live file or
// an error pointer, as required by `from_err_ptr`.
let handle =
unsafe { kernel::error::from_err_ptr(sep_store_open_trunc(path.as_char_ptr()))? };
Ok(StoreFile { handle })
}

pub(crate) fn open_readonly(path: &CStr) -> Result<StoreFile> {
// SAFETY: `path` is NUL-terminated; the shim returns NULL on failure.
let handle = unsafe { sep_store_open_ro(path.as_char_ptr()) };
if handle.is_null() {
return Err(ENOENT);
}
// SAFETY: `path` is NUL-terminated; the shim returns a live file or
// an error pointer, as required by `from_err_ptr`.
let handle = unsafe { kernel::error::from_err_ptr(sep_store_open_ro(path.as_char_ptr()))? };
Ok(StoreFile { handle })
}

Expand Down
16 changes: 5 additions & 11 deletions drivers/soc/apple/store_shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ static struct file *open_as_kernel(const char *path, int flags, umode_t mode)
/* Opens the store, creating if absent. Mode 0600: the file is the driver's own. */
void *sep_store_open(const char *path)
{
struct file *f = open_as_kernel(path, O_RDWR | O_CREAT | O_LARGEFILE, 0600);

return IS_ERR(f) ? NULL : f;
return open_as_kernel(path, O_RDWR | O_CREAT | O_LARGEFILE, 0600);
}

/*
Expand All @@ -75,20 +73,16 @@ void *sep_store_open_host(const char *path, bool *created)
*/
void *sep_store_open_trunc(const char *path)
{
struct file *f = open_as_kernel(path, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0600);

return IS_ERR(f) ? NULL : f;
return open_as_kernel(path, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0600);
}

/*
* 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.
* Opens an existing file read-only, returning an error pointer on failure.
* O_RDONLY and no O_CREAT: the seed cannot be created, truncated or written.
*/
void *sep_store_open_ro(const char *path)
{
struct file *f = open_as_kernel(path, O_RDONLY | O_LARGEFILE, 0);

return IS_ERR(f) ? NULL : f;
return open_as_kernel(path, O_RDONLY | O_LARGEFILE, 0);
}

void *sep_store_open_block(const char *path, int writable)
Expand Down
Loading