Commit ac2081cd authored by Will Deacon's avatar Will Deacon

arm64: ptrace: Consistently use pseudo-singlestep exceptions

Although the arm64 single-step state machine can be fast-forwarded in
cases where we wish to generate a SIGTRAP without actually executing an
instruction, this has two major limitations outside of simply skipping
an instruction due to emulation.

1. Stepping out of a ptrace signal stop into a signal handler where
   SIGTRAP is blocked. Fast-forwarding the stepping state machine in
   this case will result in a forced SIGTRAP, with the handler reset to
   SIG_DFL.

2. The hardware implicitly fast-forwards the state machine when executing
   an SVC instruction for issuing a system call. This can interact badly
   with subsequent ptrace stops signalled during the execution of the
   system call (e.g. SYSCALL_EXIT or seccomp traps), as they may corrupt
   the stepping state by updating the PSTATE for the tracee.

Resolve both of these issues by injecting a pseudo-singlestep exception
on entry to a signal handler and also on return to userspace following a
system call.

Cc: <stable@vger.kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Tested-by: default avatarLuis Machado <luis.machado@linaro.org>
Reported-by: default avatarKeno Fischer <keno@juliacomputing.com>
Signed-off-by: default avatarWill Deacon <will@kernel.org>
parent bdc5c744
...@@ -93,6 +93,7 @@ void arch_release_task_struct(struct task_struct *tsk); ...@@ -93,6 +93,7 @@ void arch_release_task_struct(struct task_struct *tsk);
#define _TIF_SYSCALL_EMU (1 << TIF_SYSCALL_EMU) #define _TIF_SYSCALL_EMU (1 << TIF_SYSCALL_EMU)
#define _TIF_UPROBE (1 << TIF_UPROBE) #define _TIF_UPROBE (1 << TIF_UPROBE)
#define _TIF_FSCHECK (1 << TIF_FSCHECK) #define _TIF_FSCHECK (1 << TIF_FSCHECK)
#define _TIF_SINGLESTEP (1 << TIF_SINGLESTEP)
#define _TIF_32BIT (1 << TIF_32BIT) #define _TIF_32BIT (1 << TIF_32BIT)
#define _TIF_SVE (1 << TIF_SVE) #define _TIF_SVE (1 << TIF_SVE)
......
...@@ -1818,12 +1818,23 @@ static void tracehook_report_syscall(struct pt_regs *regs, ...@@ -1818,12 +1818,23 @@ static void tracehook_report_syscall(struct pt_regs *regs,
saved_reg = regs->regs[regno]; saved_reg = regs->regs[regno];
regs->regs[regno] = dir; regs->regs[regno] = dir;
if (dir == PTRACE_SYSCALL_EXIT) if (dir == PTRACE_SYSCALL_ENTER) {
if (tracehook_report_syscall_entry(regs))
forget_syscall(regs);
regs->regs[regno] = saved_reg;
} else if (!test_thread_flag(TIF_SINGLESTEP)) {
tracehook_report_syscall_exit(regs, 0); tracehook_report_syscall_exit(regs, 0);
else if (tracehook_report_syscall_entry(regs)) regs->regs[regno] = saved_reg;
forget_syscall(regs); } else {
regs->regs[regno] = saved_reg;
regs->regs[regno] = saved_reg; /*
* Signal a pseudo-step exception since we are stepping but
* tracer modifications to the registers may have rewound the
* state machine.
*/
tracehook_report_syscall_exit(regs, 1);
}
} }
int syscall_trace_enter(struct pt_regs *regs) int syscall_trace_enter(struct pt_regs *regs)
...@@ -1851,12 +1862,14 @@ int syscall_trace_enter(struct pt_regs *regs) ...@@ -1851,12 +1862,14 @@ int syscall_trace_enter(struct pt_regs *regs)
void syscall_trace_exit(struct pt_regs *regs) void syscall_trace_exit(struct pt_regs *regs)
{ {
unsigned long flags = READ_ONCE(current_thread_info()->flags);
audit_syscall_exit(regs); audit_syscall_exit(regs);
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) if (flags & _TIF_SYSCALL_TRACEPOINT)
trace_sys_exit(regs, regs_return_value(regs)); trace_sys_exit(regs, regs_return_value(regs));
if (test_thread_flag(TIF_SYSCALL_TRACE)) if (flags & (_TIF_SYSCALL_TRACE | _TIF_SINGLESTEP))
tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT); tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT);
rseq_syscall(regs); rseq_syscall(regs);
......
...@@ -800,7 +800,6 @@ static void setup_restart_syscall(struct pt_regs *regs) ...@@ -800,7 +800,6 @@ static void setup_restart_syscall(struct pt_regs *regs)
*/ */
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs) static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{ {
struct task_struct *tsk = current;
sigset_t *oldset = sigmask_to_save(); sigset_t *oldset = sigmask_to_save();
int usig = ksig->sig; int usig = ksig->sig;
int ret; int ret;
...@@ -824,14 +823,8 @@ static void handle_signal(struct ksignal *ksig, struct pt_regs *regs) ...@@ -824,14 +823,8 @@ static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
*/ */
ret |= !valid_user_regs(&regs->user_regs, current); ret |= !valid_user_regs(&regs->user_regs, current);
/* /* Step into the signal handler if we are stepping */
* Fast forward the stepping logic so we step into the signal signal_setup_done(ret, ksig, test_thread_flag(TIF_SINGLESTEP));
* handler.
*/
if (!ret)
user_fastforward_single_step(tsk);
signal_setup_done(ret, ksig, 0);
} }
/* /*
......
...@@ -139,7 +139,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr, ...@@ -139,7 +139,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) { if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
local_daif_mask(); local_daif_mask();
flags = current_thread_info()->flags; flags = current_thread_info()->flags;
if (!has_syscall_work(flags)) { if (!has_syscall_work(flags) && !(flags & _TIF_SINGLESTEP)) {
/* /*
* We're off to userspace, where interrupts are * We're off to userspace, where interrupts are
* always enabled after we restore the flags from * always enabled after we restore the flags from
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment