Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] arm64/efi: Do not call EFI runtime services preemptibly under SW PAN
@ 2026-08-06  0:01 gus bourg
  0 siblings, 0 replies; only message in thread
From: gus bourg @ 2026-08-06  0:01 UTC (permalink / raw)
  To: ardb
  Cc: catalin.marinas, will, ilias.apalodimas, linux-efi,
	linux-arm-kernel, linux-kernel, stable, Gus Bourg

From: Gus Bourg <gus@bourg.net>

When PAN is emulated by switching TTBR0_EL1, arch_efi_call_virt_setup()
installs the EFI mm into TTBR0_EL1 via uaccess_ttbr0_enable(), and only a
return from exception ever puts it back: check_and_switch_context() skips
the register write by design ("Defer TTBR0_EL1 setting for user threads to
uaccess_enable() when emulating PAN"), and __switch_to() does not touch it
either. switch_mm() updates only thread_info->ttbr0.

Since commit a5baf582f4c0 ("arm64/efi: Call EFI runtime services without
disabling preemption") the runtime call is preemptible, so the EFI worker
can now be scheduled out inside that window. An involuntary preemption is
harmless, because __swpan_entry_el1()/__swpan_exit_el1() save and restore
the state around the exception. A voluntary reschedule is not: it performs
no return from exception, so the worker resumes with another task's
TTBR0_EL1 - or reserved_pg_dir - and the next efi_mm access takes a level 0
translation fault.

There is such a preemption point in arch_efi_call_virt_setup() itself,
immediately after the TTBR0 install: __efi_fpsimd_begin() ->
kernel_neon_begin() -> put_cpu_fpsimd_context() -> local_bh_enable() ->
preempt_check_resched().

On a Khadas VIM3 (Cortex-A73/A53, no FEAT_PAN, so CONFIG_ARM64_SW_TTBR0_PAN
is in use) this reproduces as:

  Unable to handle kernel access to user memory outside uaccess routines
    at virtual address 00000000f322ff30
  Mem abort info:
    ESR = 0x0000000096000004
    FSC = 0x04: level 0 translation fault
  Internal error: Oops: 0000000096000004 [#1]  SMP
  Workqueue: efi_rts_wq efi_call_rts
  pstate: 80400005 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
  pc : efi_call_rts+0xd8/0x288
  Call trace:
   efi_call_rts+0xd8/0x288 (P)
   process_one_work+0x178/0x4f8
   worker_thread+0x194/0x328

The faulting instruction is the kernel's own load of efi.runtime->get_time;
firmware is never entered, so efi_runtime_fixup_exception() declines it
(the PC is kernel text) and the result is a full oops rather than
EFI_ABORTED. The EFI runtime path is then wedged for the rest of the boot:
efi_rts_work.efi_rts_comp is never completed and the runtime lock is never
released, so every subsequent caller blocks in uninterruptible sleep, and
poweroff hangs because EFI_RUNTIME_SERVICES is still set.

The failing window was identified with ftrace, tracing efi_call_rts,
arch_efi_call_virt_setup, kthread_use_mm, __efi_fpsimd_begin,
kernel_neon_begin and arch_efi_call_virt_teardown together with
sched_switch. Of four preemptions observed inside the window over 12726
calls, the two that landed before the TTBR0 install (in kthread_use_mm)
were harmless and the two that landed in kernel_neon_begin were not; one
of those faulted.

Keep configurations that emulate PAN on the non-preemptible path, which is
what they used before a5baf582f4c0. Systems with FEAT_PAN are unaffected:
system_uses_ttbr0_pan() is false there, uaccess_ttbr0_enable() is a no-op,
and check_and_switch_context() installs TTBR0_EL1 on every switch.

Fixes: a5baf582f4c0 ("arm64/efi: Call EFI runtime services without disabling preemption")
Cc: <stable@vger.kernel.org> # v6.19+
Assisted-by: Claude:claude-opus-5 ftrace
Signed-off-by: gus bourg <gus@bourg.net>
---
 arch/arm64/kernel/efi.c | 25 +++++++++++++++++++++++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kernel/efi.c b/arch/arm64/kernel/efi.c
index 30cd7f804398..1b666ca79758 100644
--- a/arch/arm64/kernel/efi.c
+++ b/arch/arm64/kernel/efi.c
@@ -166,11 +166,32 @@ asmlinkage efi_status_t efi_handle_corrupted_x18(efi_status_t s, const char *f)
 	return s;
 }
 
+/*
+ * Whether an EFI runtime service call may run preemptibly.
+ *
+ * When PAN is emulated by switching TTBR0_EL1, the EFI mm is installed into
+ * the register by uaccess_ttbr0_enable() below, and only a return from
+ * exception ever puts it back: check_and_switch_context() deliberately skips
+ * the write ("Defer TTBR0_EL1 setting for user threads to uaccess_enable()
+ * when emulating PAN"), and __switch_to() never touches it. A voluntary
+ * reschedule performs no return from exception, so a preemptible runtime call
+ * can be scheduled back in holding another task's TTBR0_EL1 and take a level 0
+ * translation fault on its next efi_mm access. Keep those configurations on
+ * the non-preemptible path, which is what they used before commit
+ * a5baf582f4c0 ("arm64/efi: Call EFI runtime services without disabling
+ * preemption").
+ */
+static bool efi_runtime_preemptible(void)
+{
+	return !system_uses_ttbr0_pan() && preemptible() &&
+	       (current->flags & PF_KTHREAD);
+}
+
 void arch_efi_call_virt_setup(void)
 {
 	efi_runtime_assert_lock_held();
 
-	if (preemptible() && (current->flags & PF_KTHREAD)) {
+	if (efi_runtime_preemptible()) {
 		/*
 		 * Disable migration to ensure that a preempted EFI runtime
 		 * service call will be resumed on the same CPU. This avoids
@@ -207,7 +228,7 @@ void arch_efi_call_virt_teardown(void)
 	 */
 	uaccess_ttbr0_disable();
 
-	if (preemptible() && (current->flags & PF_KTHREAD)) {
+	if (efi_runtime_preemptible()) {
 		kthread_unuse_mm(&efi_mm);
 		migrate_enable();
 	} else {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-08-06  0:02 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06  0:01 [PATCH] arm64/efi: Do not call EFI runtime services preemptibly under SW PAN gus bourg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox