mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: gus bourg <gus@bourg.net>
To: ardb@kernel.org
Cc: catalin.marinas@arm.com, will@kernel.org,
	ilias.apalodimas@linaro.org, linux-efi@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org,
	Gus Bourg <gus@bourg.net>
Subject: [PATCH] arm64/efi: Do not call EFI runtime services preemptibly under SW PAN
Date: Wed,  5 Aug 2026 17:01:44 -0700	[thread overview]
Message-ID: <20260806000144.3388823-1-gus@bourg.net> (raw)

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


             reply	other threads:[~2026-08-06  0:02 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-06  0:01 gus bourg [this message]
2026-08-07 12:16 ` Will Deacon
2026-08-07 13:56   ` Ard Biesheuvel
2026-08-10 11:04     ` Will Deacon
2026-08-10 15:43       ` Ard Biesheuvel
2026-08-11  0:51       ` Gus Bourg
2026-08-11 12:09         ` Will Deacon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260806000144.3388823-1-gus@bourg.net \
    --to=gus@bourg.net \
    --cc=ardb@kernel.org \
    --cc=catalin.marinas@arm.com \
    --cc=ilias.apalodimas@linaro.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-efi@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=stable@vger.kernel.org \
    --cc=will@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®