Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: split EL0/EL1 UNDEF handlers

In general, exceptions taken from EL1 need to be handled separately from
exceptions taken from EL0, as the logic to handle the two cases can be
significantly divergent, and exceptions taken from EL1 typically have
more stringent requirements on locking and instrumentation.

Subsequent patches will rework the way EL1 UNDEFs are handled in order
to address longstanding soundness issues with instrumentation and RCU.
In preparation for that rework, this patch splits the existing
do_undefinstr() handler into separate do_el0_undef() and do_el1_undef()
handlers.

Prior to this patch, do_undefinstr() was marked with NOKPROBE_SYMBOL(),
preventing instrumentation via kprobes. However, do_undefinstr() invokes
other code which can be instrumented, and:

* For UNDEFINED exceptions taken from EL0, there is no risk of recursion
within kprobes. Therefore it is safe for do_el0_undef to be
instrumented with kprobes, and it does not need to be marked with
NOKPROBE_SYMBOL().

* For UNDEFINED exceptions taken from EL1, either:

(a) The exception is has been taken when manipulating SSBS; these cases
are limited and do not occur within code that can be invoked
recursively via kprobes. Hence, in these cases instrumentation
with kprobes is benign.

(b) The exception has been taken for an unknown reason, as other than
manipulating SSBS we do not expect to take UNDEFINED exceptions
from EL1. Any handling of these exception is best-effort.

... and in either case, marking do_el1_undef() with NOKPROBE_SYMBOL()
isn't sufficient to prevent recursion via kprobes as functions it
calls (including die()) are instrumentable via kprobes.

Hence, it's not worthwhile to mark do_el1_undef() with
NOKPROBE_SYMBOL(). The same applies to do_el1_bti() and do_el1_fpac(),
so their NOKPROBE_SYMBOL() annotations are also removed.

Aside from the new instrumentability, there should be no functional
change as a result of this patch.

Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Joey Gouly <joey.gouly@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20221019144123.612388-3-mark.rutland@arm.com
Signed-off-by: Will Deacon <will@kernel.org>

authored by

Mark Rutland and committed by
Will Deacon
61d64a37 b3a0c010

+16 -13
+2 -1
arch/arm64/include/asm/exception.h
··· 58 58 asmlinkage void asm_exit_to_user_mode(struct pt_regs *regs); 59 59 60 60 void do_mem_abort(unsigned long far, unsigned long esr, struct pt_regs *regs); 61 - void do_undefinstr(struct pt_regs *regs, unsigned long esr); 61 + void do_el0_undef(struct pt_regs *regs, unsigned long esr); 62 + void do_el1_undef(struct pt_regs *regs, unsigned long esr); 62 63 void do_el0_bti(struct pt_regs *regs); 63 64 void do_el1_bti(struct pt_regs *regs, unsigned long esr); 64 65 void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
+2 -2
arch/arm64/kernel/entry-common.c
··· 384 384 { 385 385 enter_from_kernel_mode(regs); 386 386 local_daif_inherit(regs); 387 - do_undefinstr(regs, esr); 387 + do_el1_undef(regs, esr); 388 388 local_daif_mask(); 389 389 exit_to_kernel_mode(regs); 390 390 } ··· 599 599 { 600 600 enter_from_user_mode(regs); 601 601 local_daif_restore(DAIF_PROCCTX); 602 - do_undefinstr(regs, esr); 602 + do_el0_undef(regs, esr); 603 603 exit_to_user_mode(regs); 604 604 } 605 605
+12 -10
arch/arm64/kernel/traps.c
··· 486 486 force_signal_inject(SIGSEGV, code, addr, 0); 487 487 } 488 488 489 - void do_undefinstr(struct pt_regs *regs, unsigned long esr) 489 + void do_el0_undef(struct pt_regs *regs, unsigned long esr) 490 490 { 491 491 /* check for AArch32 breakpoint instructions */ 492 492 if (!aarch32_break_handler(regs)) ··· 495 495 if (call_undef_hook(regs) == 0) 496 496 return; 497 497 498 - if (!user_mode(regs)) 499 - die("Oops - Undefined instruction", regs, esr); 500 - 501 498 force_signal_inject(SIGILL, ILL_ILLOPC, regs->pc, 0); 502 499 } 503 - NOKPROBE_SYMBOL(do_undefinstr); 500 + 501 + void do_el1_undef(struct pt_regs *regs, unsigned long esr) 502 + { 503 + if (call_undef_hook(regs) == 0) 504 + return; 505 + 506 + die("Oops - Undefined instruction", regs, esr); 507 + } 504 508 505 509 void do_el0_bti(struct pt_regs *regs) 506 510 { ··· 515 511 { 516 512 die("Oops - BTI", regs, esr); 517 513 } 518 - NOKPROBE_SYMBOL(do_el1_bti); 519 514 520 515 void do_el0_fpac(struct pt_regs *regs, unsigned long esr) 521 516 { ··· 529 526 */ 530 527 die("Oops - FPAC", regs, esr); 531 528 } 532 - NOKPROBE_SYMBOL(do_el1_fpac) 533 529 534 530 #define __user_cache_maint(insn, address, res) \ 535 531 if (address >= TASK_SIZE_MAX) { \ ··· 771 769 hook_base = cp15_64_hooks; 772 770 break; 773 771 default: 774 - do_undefinstr(regs, esr); 772 + do_el0_undef(regs, esr); 775 773 return; 776 774 } 777 775 ··· 786 784 * EL0. Fall back to our usual undefined instruction handler 787 785 * so that we handle these consistently. 788 786 */ 789 - do_undefinstr(regs, esr); 787 + do_el0_undef(regs, esr); 790 788 } 791 789 #endif 792 790 ··· 805 803 * back to our usual undefined instruction handler so that we handle 806 804 * these consistently. 807 805 */ 808 - do_undefinstr(regs, esr); 806 + do_el0_undef(regs, esr); 809 807 } 810 808 811 809 static const char *esr_class_str[] = {