Skip to content

Commit ac2081c

Browse files
committed
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: <[email protected]> Cc: Mark Rutland <[email protected]> Tested-by: Luis Machado <[email protected]> Reported-by: Keno Fischer <[email protected]> Signed-off-by: Will Deacon <[email protected]>
1 parent bdc5c74 commit ac2081c

File tree

4 files changed

+23
-16
lines changed

4 files changed

+23
-16
lines changed

arch/arm64/include/asm/thread_info.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ void arch_release_task_struct(struct task_struct *tsk);
9393
#define _TIF_SYSCALL_EMU (1 << TIF_SYSCALL_EMU)
9494
#define _TIF_UPROBE (1 << TIF_UPROBE)
9595
#define _TIF_FSCHECK (1 << TIF_FSCHECK)
96+
#define _TIF_SINGLESTEP (1 << TIF_SINGLESTEP)
9697
#define _TIF_32BIT (1 << TIF_32BIT)
9798
#define _TIF_SVE (1 << TIF_SVE)
9899

arch/arm64/kernel/ptrace.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,12 +1818,23 @@ static void tracehook_report_syscall(struct pt_regs *regs,
18181818
saved_reg = regs->regs[regno];
18191819
regs->regs[regno] = dir;
18201820

1821-
if (dir == PTRACE_SYSCALL_EXIT)
1821+
if (dir == PTRACE_SYSCALL_ENTER) {
1822+
if (tracehook_report_syscall_entry(regs))
1823+
forget_syscall(regs);
1824+
regs->regs[regno] = saved_reg;
1825+
} else if (!test_thread_flag(TIF_SINGLESTEP)) {
18221826
tracehook_report_syscall_exit(regs, 0);
1823-
else if (tracehook_report_syscall_entry(regs))
1824-
forget_syscall(regs);
1827+
regs->regs[regno] = saved_reg;
1828+
} else {
1829+
regs->regs[regno] = saved_reg;
18251830

1826-
regs->regs[regno] = saved_reg;
1831+
/*
1832+
* Signal a pseudo-step exception since we are stepping but
1833+
* tracer modifications to the registers may have rewound the
1834+
* state machine.
1835+
*/
1836+
tracehook_report_syscall_exit(regs, 1);
1837+
}
18271838
}
18281839

18291840
int syscall_trace_enter(struct pt_regs *regs)
@@ -1851,12 +1862,14 @@ int syscall_trace_enter(struct pt_regs *regs)
18511862

18521863
void syscall_trace_exit(struct pt_regs *regs)
18531864
{
1865+
unsigned long flags = READ_ONCE(current_thread_info()->flags);
1866+
18541867
audit_syscall_exit(regs);
18551868

1856-
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
1869+
if (flags & _TIF_SYSCALL_TRACEPOINT)
18571870
trace_sys_exit(regs, regs_return_value(regs));
18581871

1859-
if (test_thread_flag(TIF_SYSCALL_TRACE))
1872+
if (flags & (_TIF_SYSCALL_TRACE | _TIF_SINGLESTEP))
18601873
tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT);
18611874

18621875
rseq_syscall(regs);

arch/arm64/kernel/signal.c

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,6 @@ static void setup_restart_syscall(struct pt_regs *regs)
800800
*/
801801
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
802802
{
803-
struct task_struct *tsk = current;
804803
sigset_t *oldset = sigmask_to_save();
805804
int usig = ksig->sig;
806805
int ret;
@@ -824,14 +823,8 @@ static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
824823
*/
825824
ret |= !valid_user_regs(&regs->user_regs, current);
826825

827-
/*
828-
* Fast forward the stepping logic so we step into the signal
829-
* handler.
830-
*/
831-
if (!ret)
832-
user_fastforward_single_step(tsk);
833-
834-
signal_setup_done(ret, ksig, 0);
826+
/* Step into the signal handler if we are stepping */
827+
signal_setup_done(ret, ksig, test_thread_flag(TIF_SINGLESTEP));
835828
}
836829

837830
/*

arch/arm64/kernel/syscall.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
139139
if (!has_syscall_work(flags) && !IS_ENABLED(CONFIG_DEBUG_RSEQ)) {
140140
local_daif_mask();
141141
flags = current_thread_info()->flags;
142-
if (!has_syscall_work(flags)) {
142+
if (!has_syscall_work(flags) && !(flags & _TIF_SINGLESTEP)) {
143143
/*
144144
* We're off to userspace, where interrupts are
145145
* always enabled after we restore the flags from

0 commit comments

Comments
 (0)