From: Roland McGrath <roland@redhat.com> Date: Tue, 18 Dec 2007 15:51:01 -0800 Subject: [ia64] ptrace: access to user register backing Message-id: 20071218235101.5A6AB26F8C2@magilla.localdomain O-Subject: [RHEL 5.2 PATCH] BZ#237749 ia64-restore-rse Bugzilla: 237749 linux-2.6-ia64-restore-rse.patch This fixes the problems with ptrace (or /proc) access to user register backing store memory corresponding to registers used by the kernel (syscall args, etc). diff --git a/arch/ia64/kernel/process.c b/arch/ia64/kernel/process.c index ea914cc..634b385 100644 --- a/arch/ia64/kernel/process.c +++ b/arch/ia64/kernel/process.c @@ -166,6 +166,7 @@ do_notify_resume_user (sigset_t *oldset, struct sigscratch *scr, long in_syscall return; } + clear_thread_flag(TIF_NOTIFY_RESUME); #ifdef CONFIG_PERFMON if (current->thread.pfm_needs_checking) pfm_handle_work(); @@ -174,6 +175,10 @@ do_notify_resume_user (sigset_t *oldset, struct sigscratch *scr, long in_syscall /* deal with pending signal delivery */ if (test_thread_flag(TIF_SIGPENDING)) ia64_do_signal(oldset, scr, in_syscall); + + /* copy user rbs to kernel rbs */ + if (unlikely(test_thread_flag(TIF_RESTORE_RSE))) + ia64_sync_krbs(); } static int pal_halt = 1; diff --git a/arch/ia64/kernel/ptrace.c b/arch/ia64/kernel/ptrace.c index bb2bcef..7f8334f 100644 --- a/arch/ia64/kernel/ptrace.c +++ b/arch/ia64/kernel/ptrace.c @@ -554,6 +554,78 @@ ia64_sync_user_rbs (struct task_struct *child, struct switch_stack *sw, return 0; } +static long +ia64_sync_kernel_rbs (struct task_struct *child, struct switch_stack *sw, + unsigned long user_rbs_start, unsigned long user_rbs_end) +{ + unsigned long addr, val; + long ret; + + /* now copy word for word from user rbs to kernel rbs: */ + for (addr = user_rbs_start; addr < user_rbs_end; addr += 8) { + if (access_process_vm(child, addr, &val, sizeof(val), 0) + != sizeof(val)) + return -EIO; + + ret = ia64_poke(child, sw, user_rbs_end, addr, val); + if (ret < 0) + return ret; + } + return 0; +} + +typedef long (*syncfunc_t)(struct task_struct *, struct switch_stack *, + unsigned long, unsigned long); + +static void do_sync_rbs(struct unw_frame_info *info, void *arg) +{ + struct pt_regs *pt; + unsigned long urbs_end; + syncfunc_t fn = arg; + + if (unw_unwind_to_user(info) < 0) + return; + pt = task_pt_regs(info->task); + urbs_end = ia64_get_user_rbs_end(info->task, pt, NULL); + + fn(info->task, info->sw, pt->ar_bspstore, urbs_end); +} + +/* + * when a thread is stopped (ptraced), debugger might change thread's user + * stack (change memory directly), and we must avoid the RSE stored in kernel + * to override user stack (user space's RSE is newer than kernel's in the + * case). To workaround the issue, we copy kernel RSE to user RSE before the + * task is stopped, so user RSE has updated data. we then copy user RSE to + * kernel after the task is resummed from traced stop and kernel will use the + * newer RSE to return to user. TIF_RESTORE_RSE is the flag to indicate we need + * synchronize user RSE to kernel. + */ +static int gpregs_writeback(struct task_struct *tsk, + const struct utrace_regset *regset, + int now) +{ + if (test_and_set_tsk_thread_flag(tsk, TIF_RESTORE_RSE)) + return 0; + set_tsk_thread_flag(tsk, TIF_NOTIFY_RESUME); + unw_init_running(do_sync_rbs, ia64_sync_user_rbs); + return 0; /* Ignore EFAULT. */ +} + +/* + * This is called to read back the register backing store. + */ +void ia64_sync_krbs(void) +{ + clear_tsk_thread_flag(current, TIF_RESTORE_RSE); +#ifdef CONFIG_PERFMON + if (!current->thread.pfm_needs_checking) + clear_thread_flag(TIF_NOTIFY_RESUME); +#endif + + unw_init_running(do_sync_rbs, ia64_sync_kernel_rbs); +} + /* * Write f32-f127 back to task->thread.fph if it has been modified. */ @@ -728,6 +800,10 @@ syscall_trace_enter (long arg0, long arg1, long arg2, long arg3, if (test_thread_flag(TIF_SYSCALL_TRACE)) tracehook_report_syscall(®s, 0); + /* copy user rbs to kernel rbs */ + if (test_thread_flag(TIF_RESTORE_RSE)) + ia64_sync_krbs(); + if (unlikely(current->audit_context)) { long syscall; int arch; @@ -768,6 +844,10 @@ syscall_trace_leave (long arg0, long arg1, long arg2, long arg3, force_sig(SIGTRAP, current); /* XXX */ tracehook_report_syscall_step(®s); } + + /* copy user rbs to kernel rbs */ + if (test_thread_flag(TIF_RESTORE_RSE)) + ia64_sync_krbs(); } @@ -1398,31 +1478,6 @@ static int gpregs_set(struct task_struct *target, return do_regset_call(do_gpregs_set, target, regset, pos, count, kbuf, ubuf); } -static void do_gpregs_writeback(struct unw_frame_info *info, void *arg) -{ - struct pt_regs *pt; - utrace_getset_t *dst = arg; - unsigned long urbs_end; - - if (unw_unwind_to_user(info) < 0) - return; - pt = task_pt_regs(dst->target); - urbs_end = ia64_get_user_rbs_end(dst->target, pt, NULL); - dst->ret = ia64_sync_user_rbs(dst->target, info->sw, pt->ar_bspstore, urbs_end); -} -/* - * This is called to write back the register backing store. - * ptrace does this before it stops, so that a tracer reading the user - * memory after the thread stops will get the current register data. - */ -static int -gpregs_writeback(struct task_struct *target, - const struct utrace_regset *regset, - int now) -{ - return do_regset_call(do_gpregs_writeback, target, regset, 0, 0, NULL, NULL); -} - static int fpregs_active(struct task_struct *target, const struct utrace_regset *regset) { diff --git a/include/asm-ia64/ptrace.h b/include/asm-ia64/ptrace.h index f4ef87a..13435f7 100644 --- a/include/asm-ia64/ptrace.h +++ b/include/asm-ia64/ptrace.h @@ -292,6 +292,7 @@ struct switch_stack { unsigned long, long); extern void ia64_flush_fph (struct task_struct *); extern void ia64_sync_fph (struct task_struct *); + extern void ia64_sync_krbs(void); extern long ia64_sync_user_rbs (struct task_struct *, struct switch_stack *, unsigned long, unsigned long); @@ -303,6 +304,12 @@ struct switch_stack { extern void ia64_increment_ip (struct pt_regs *pt); extern void ia64_decrement_ip (struct pt_regs *pt); + extern void ia64_ptrace_stop(void); + #define arch_ptrace_stop(code, info) \ + ia64_ptrace_stop() + #define arch_ptrace_stop_needed(code, info) \ + (!test_thread_flag(TIF_RESTORE_RSE)) + #endif /* !__KERNEL__ */ /* pt_all_user_regs is used for PTRACE_GETREGS PTRACE_SETREGS */ diff --git a/include/asm-ia64/thread_info.h b/include/asm-ia64/thread_info.h index d324d4c..ebca7a1 100644 --- a/include/asm-ia64/thread_info.h +++ b/include/asm-ia64/thread_info.h @@ -89,6 +89,7 @@ struct thread_info { #define TIF_MEMDIE 17 #define TIF_MCA_INIT 18 /* this task is processing MCA or INIT */ #define TIF_DB_DISABLED 19 /* debug trap disabled for fsyscall */ +#define TIF_RESTORE_RSE 21 /* user RBS is newer than kernel RBS */ #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) #define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT) @@ -100,6 +101,7 @@ struct thread_info { #define _TIF_POLLING_NRFLAG (1 << TIF_POLLING_NRFLAG) #define _TIF_MCA_INIT (1 << TIF_MCA_INIT) #define _TIF_DB_DISABLED (1 << TIF_DB_DISABLED) +#define _TIF_RESTORE_RSE (1 << TIF_RESTORE_RSE) /* "work to do on user-return" bits */ #define TIF_ALLWORK_MASK (_TIF_NOTIFY_RESUME|_TIF_SIGPENDING|_TIF_NEED_RESCHED|_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT)