Fix a race where trap instruction is executed and active_ppt is not yet
set and the probepoint in question is removed.  

A thread could execute a trap instruction resulting in SIGTRAP to be
sent to the process. However before this signal is handled, the
breakpoint could be removed. The core of report_signal(i.e after it
write locks uproc), checks to find that there is no corresponding
breakpoint structure and assumes SIGTRAP to be because of other reasons. 

This can cause the process to die.

For more context please read the thread at 
https://www.redhat.com/archives/utrace-devel/2009-September/msg00028.html

This patch fixes the problem at breakpoint removal time. 
Patch adjusts instruction pointer for those threads that have executed
the trap instruction but have not yet singlestepped 

Signed-off-by: Srikar Dronamraju <sri...@linux.vnet.ibm.com>
---
 kernel/uprobes_core.c |   41 ++++++++++++++++++++++++++++++++---------
 1 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/kernel/uprobes_core.c b/kernel/uprobes_core.c
index 27c2977..372325b 100644
--- a/kernel/uprobes_core.c
+++ b/kernel/uprobes_core.c
@@ -214,21 +214,42 @@ static void uprobe_insert_bkpt(struct uprobe_probept *ppt,
 }
 
 /*
+ * Check if task has just stepped on a trap instruction at the
+ * indicated address. If it has indeed stepped on that address,
+ * then reset Instruction Pointer for the task.
+ *
+ * tsk should either be current thread or already quiesced thread.
+ */
+static inline void reset_thread_ip(struct task_struct *tsk,
+                               struct pt_regs *regs, unsigned long addr)
+{
+       if ((ubp_get_bkpt_addr(regs) == addr) &&
+                               !test_tsk_thread_flag(tsk, TIF_SINGLESTEP))
+               ubp_set_ip(regs, addr);
+}
+
+/*
  * ppt's breakpoint has been removed.  If any threads are in the middle of
  * single-stepping at this probepoint, fix things up so they can proceed.
+ * If any threads have just hit breakpoint but are yet to start
+ * pre-processing, reset their instruction pointers.
+ *
  * Runs with all of ppt->uproc's threads quiesced and ppt->uproc->rwsem
  * write-locked
  */
-static inline void adjust_ip_active_ppt(struct uprobe_probept *ppt)
+static inline void adjust_trapped_thread_ip(struct uprobe_probept *ppt)
 {
-#ifdef CONFIG_UBP_XOL
        struct uprobe_process *uproc = ppt->uproc;
        struct uprobe_task *utask;
        struct pt_regs *regs;
 
        list_for_each_entry(utask, &uproc->thread_list, list) {
-               if (utask->active_probe != ppt)
+               regs = task_pt_regs(utask->tsk);
+               if (utask->active_probe != ppt) {
+                       reset_thread_ip(utask->tsk, regs, ppt->ubp.vaddr);
                        continue;
+               }
+
                /*
                 * Current thread cannot have an active breakpoint
                 * and still request for a breakpoint removal. The
@@ -236,15 +257,15 @@ static inline void adjust_ip_active_ppt(struct 
uprobe_probept *ppt)
                 */
                BUG_ON(utask->tsk == current);
 
-               regs = task_pt_regs(utask->tsk);
+#ifdef CONFIG_UBP_XOL
                if (instruction_pointer(regs) == ppt->ubp.xol_vaddr)
                        /* adjust the ip to breakpoint addr.  */
                        ubp_set_ip(regs, ppt->ubp.vaddr);
                else
                        /* adjust the ip to next instruction.  */
                        uprobe_post_ssout(utask, ppt, regs);
-       }
 #endif
+       }
 }
 
 static void uprobe_remove_bkpt(struct uprobe_probept *ppt,
@@ -264,9 +285,8 @@ static void uprobe_remove_bkpt(struct uprobe_probept *ppt,
                         * breakpoint is hit.
                         */
                }
-               if (!(ppt->ubp.strategy & UBP_HNT_INLINE))
-                       adjust_ip_active_ppt(ppt);
-               else {
+               adjust_trapped_thread_ip(ppt);
+               if (ppt->ubp.strategy & UBP_HNT_INLINE) {
                        unsigned long flags;
                        spin_lock_irqsave(&ppt->ssil_lock, flags);
                        ppt->ssil_state = SSIL_DISABLE;
@@ -1300,8 +1320,10 @@ static void uprobe_pre_ssin(struct uprobe_task *utask,
 {
        unsigned long flags;
 
-       if (unlikely(ppt->ssil_state == SSIL_DISABLE))
+       if (unlikely(ppt->ssil_state == SSIL_DISABLE)) {
+               reset_thread_ip(utask->tsk, regs, ppt->ubp.vaddr);
                return;
+       }
        spin_lock_irqsave(&ppt->ssil_lock, flags);
        while (ppt->ssil_state == SSIL_SET) {
                spin_unlock_irqrestore(&ppt->ssil_lock, flags);
@@ -1316,6 +1338,7 @@ static void uprobe_pre_ssin(struct uprobe_task *utask,
                 * been removed. Thread continues as if nothing happened.
                 */
                spin_unlock_irqrestore(&ppt->ssil_lock, flags);
+               reset_thread_ip(utask->tsk, regs, ppt->ubp.vaddr);
                return;
        }
        ppt->ssil_state = SSIL_SET;

Reply via email to