Commit bf26c018490c2fce7fe9b629083b96ce0e6ad019

Authored by Frederic Weisbecker
1 parent f4929bd372

ptrace: Prepare to fix racy accesses on task breakpoints

When a task is traced and is in a stopped state, the tracer
may execute a ptrace request to examine the tracee state and
get its task struct. Right after, the tracee can be killed
and thus its breakpoints released.
This can happen concurrently when the tracer is in the middle
of reading or modifying these breakpoints, leading to dereferencing
a freed pointer.

Hence, to prepare the fix, create a generic breakpoint reference
holding API. When a reference on the breakpoints of a task is
held, the breakpoints won't be released until the last reference
is dropped. After that, no more ptrace request on the task's
breakpoints can be serviced for the tracer.

Reported-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Prasad <prasad@linux.vnet.ibm.com>
Cc: Paul Mundt <lethal@linux-sh.org>
Cc: v2.6.33.. <stable@kernel.org>
Link: http://lkml.kernel.org/r/1302284067-7860-2-git-send-email-fweisbec@gmail.com

Showing 4 changed files with 33 additions and 2 deletions Side-by-side Diff

include/linux/ptrace.h
... ... @@ -189,6 +189,10 @@
189 189 child->ptrace = current->ptrace;
190 190 __ptrace_link(child, current->parent);
191 191 }
  192 +
  193 +#ifdef CONFIG_HAVE_HW_BREAKPOINT
  194 + atomic_set(&child->ptrace_bp_refcnt, 1);
  195 +#endif
192 196 }
193 197  
194 198 /**
... ... @@ -350,7 +354,14 @@
350 354 unsigned long args[6], unsigned int maxargs,
351 355 unsigned long *sp, unsigned long *pc);
352 356  
353   -#endif
  357 +#ifdef CONFIG_HAVE_HW_BREAKPOINT
  358 +extern int ptrace_get_breakpoints(struct task_struct *tsk);
  359 +extern void ptrace_put_breakpoints(struct task_struct *tsk);
  360 +#else
  361 +static inline void ptrace_put_breakpoints(struct task_struct *tsk) { }
  362 +#endif /* CONFIG_HAVE_HW_BREAKPOINT */
  363 +
  364 +#endif /* __KERNEL */
354 365  
355 366 #endif
include/linux/sched.h
... ... @@ -1537,6 +1537,9 @@
1537 1537 unsigned long memsw_nr_pages; /* uncharged mem+swap usage */
1538 1538 } memcg_batch;
1539 1539 #endif
  1540 +#ifdef CONFIG_HAVE_HW_BREAKPOINT
  1541 + atomic_t ptrace_bp_refcnt;
  1542 +#endif
1540 1543 };
1541 1544  
1542 1545 /* Future-safe accessor for struct task_struct's cpus_allowed. */
... ... @@ -1016,7 +1016,7 @@
1016 1016 /*
1017 1017 * FIXME: do that only when needed, using sched_exit tracepoint
1018 1018 */
1019   - flush_ptrace_hw_breakpoint(tsk);
  1019 + ptrace_put_breakpoints(tsk);
1020 1020  
1021 1021 exit_notify(tsk, group_dead);
1022 1022 #ifdef CONFIG_NUMA
... ... @@ -22,6 +22,7 @@
22 22 #include <linux/syscalls.h>
23 23 #include <linux/uaccess.h>
24 24 #include <linux/regset.h>
  25 +#include <linux/hw_breakpoint.h>
25 26  
26 27  
27 28 /*
... ... @@ -879,4 +880,20 @@
879 880 return ret;
880 881 }
881 882 #endif /* CONFIG_COMPAT */
  883 +
  884 +#ifdef CONFIG_HAVE_HW_BREAKPOINT
  885 +int ptrace_get_breakpoints(struct task_struct *tsk)
  886 +{
  887 + if (atomic_inc_not_zero(&tsk->ptrace_bp_refcnt))
  888 + return 0;
  889 +
  890 + return -1;
  891 +}
  892 +
  893 +void ptrace_put_breakpoints(struct task_struct *tsk)
  894 +{
  895 + if (atomic_dec_and_test(&tsk->ptrace_bp_refcnt))
  896 + flush_ptrace_hw_breakpoint(tsk);
  897 +}
  898 +#endif /* CONFIG_HAVE_HW_BREAKPOINT */