]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
arm64: ptrace: Override SPSR.SS when single-stepping is enabled
authorWill Deacon <will@kernel.org>
Thu, 13 Feb 2020 12:06:26 +0000 (12:06 +0000)
committerWill Deacon <will@kernel.org>
Thu, 16 Jul 2020 10:41:21 +0000 (11:41 +0100)
Luis reports that, when reverse debugging with GDB, single-step does not
function as expected on arm64:

  | I've noticed, under very specific conditions, that a PTRACE_SINGLESTEP
  | request by GDB won't execute the underlying instruction. As a consequence,
  | the PC doesn't move, but we return a SIGTRAP just like we would for a
  | regular successful PTRACE_SINGLESTEP request.

The underlying problem is that when the CPU register state is restored
as part of a reverse step, the SPSR.SS bit is cleared and so the hardware
single-step state can transition to the "active-pending" state, causing
an unexpected step exception to be taken immediately if a step operation
is attempted.

In hindsight, we probably shouldn't have exposed SPSR.SS in the pstate
accessible by the GPR regset, but it's a bit late for that now. Instead,
simply prevent userspace from configuring the bit to a value which is
inconsistent with the TIF_SINGLESTEP state for the task being traced.

Cc: <stable@vger.kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Keno Fischer <keno@juliacomputing.com>
Link: https://lore.kernel.org/r/1eed6d69-d53d-9657-1fc9-c089be07f98c@linaro.org
Reported-by: Luis Machado <luis.machado@linaro.org>
Tested-by: Luis Machado <luis.machado@linaro.org>
Signed-off-by: Will Deacon <will@kernel.org>
arch/arm64/include/asm/debug-monitors.h
arch/arm64/kernel/debug-monitors.c
arch/arm64/kernel/ptrace.c

index e5ceea213e39a580c932e16b994922de3cfd7915..0b298f48f5bfa231aa2b398d48acc0dab0af221e 100644 (file)
@@ -109,6 +109,8 @@ void disable_debug_monitors(enum dbg_active_el el);
 
 void user_rewind_single_step(struct task_struct *task);
 void user_fastforward_single_step(struct task_struct *task);
+void user_regs_reset_single_step(struct user_pt_regs *regs,
+                                struct task_struct *task);
 
 void kernel_enable_single_step(struct pt_regs *regs);
 void kernel_disable_single_step(void);
index 5df49366e9abe0cde9fa26fc632c988d062b2fca..91146c0a36911455e7d6ebce6cbfae7606c4fc28 100644 (file)
@@ -141,17 +141,20 @@ postcore_initcall(debug_monitors_init);
 /*
  * Single step API and exception handling.
  */
-static void set_regs_spsr_ss(struct pt_regs *regs)
+static void set_user_regs_spsr_ss(struct user_pt_regs *regs)
 {
        regs->pstate |= DBG_SPSR_SS;
 }
-NOKPROBE_SYMBOL(set_regs_spsr_ss);
+NOKPROBE_SYMBOL(set_user_regs_spsr_ss);
 
-static void clear_regs_spsr_ss(struct pt_regs *regs)
+static void clear_user_regs_spsr_ss(struct user_pt_regs *regs)
 {
        regs->pstate &= ~DBG_SPSR_SS;
 }
-NOKPROBE_SYMBOL(clear_regs_spsr_ss);
+NOKPROBE_SYMBOL(clear_user_regs_spsr_ss);
+
+#define set_regs_spsr_ss(r)    set_user_regs_spsr_ss(&(r)->user_regs)
+#define clear_regs_spsr_ss(r)  clear_user_regs_spsr_ss(&(r)->user_regs)
 
 static DEFINE_SPINLOCK(debug_hook_lock);
 static LIST_HEAD(user_step_hook);
@@ -402,6 +405,15 @@ void user_fastforward_single_step(struct task_struct *task)
                clear_regs_spsr_ss(task_pt_regs(task));
 }
 
+void user_regs_reset_single_step(struct user_pt_regs *regs,
+                                struct task_struct *task)
+{
+       if (test_tsk_thread_flag(task, TIF_SINGLESTEP))
+               set_user_regs_spsr_ss(regs);
+       else
+               clear_user_regs_spsr_ss(regs);
+}
+
 /* Kernel API */
 void kernel_enable_single_step(struct pt_regs *regs)
 {
index 057d4aa1af4d364e175e9553f16cf2c3ffd06aff..22f9053b55b6e4b4eec7b85bdaa59b9f7e685f08 100644 (file)
@@ -1947,8 +1947,8 @@ static int valid_native_regs(struct user_pt_regs *regs)
  */
 int valid_user_regs(struct user_pt_regs *regs, struct task_struct *task)
 {
-       if (!test_tsk_thread_flag(task, TIF_SINGLESTEP))
-               regs->pstate &= ~DBG_SPSR_SS;
+       /* https://lore.kernel.org/lkml/20191118131525.GA4180@willie-the-truck */
+       user_regs_reset_single_step(regs, task);
 
        if (is_compat_thread(task_thread_info(task)))
                return valid_compat_regs(regs);