struct rw_semaphore     register_rwsem;
        struct rw_semaphore     consumer_rwsem;
        struct list_head        pending_list;
-       struct uprobe_consumer  *consumers;
+       struct list_head        consumers;
        struct inode            *inode;         /* Also hold a ref to inode */
        struct rcu_head         rcu;
        loff_t                  offset;
        uprobe->inode = inode;
        uprobe->offset = offset;
        uprobe->ref_ctr_offset = ref_ctr_offset;
+       INIT_LIST_HEAD(&uprobe->consumers);
        init_rwsem(&uprobe->register_rwsem);
        init_rwsem(&uprobe->consumer_rwsem);
        RB_CLEAR_NODE(&uprobe->rb_node);
 static void consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc)
 {
        down_write(&uprobe->consumer_rwsem);
-       uc->next = uprobe->consumers;
-       uprobe->consumers = uc;
+       list_add_rcu(&uc->cons_node, &uprobe->consumers);
        up_write(&uprobe->consumer_rwsem);
 }
 
 /*
  * For uprobe @uprobe, delete the consumer @uc.
- * Return true if the @uc is deleted successfully
- * or return false.
+ * Should never be called with consumer that's not part of @uprobe->consumers.
  */
-static bool consumer_del(struct uprobe *uprobe, struct uprobe_consumer *uc)
+static void consumer_del(struct uprobe *uprobe, struct uprobe_consumer *uc)
 {
-       struct uprobe_consumer **con;
-       bool ret = false;
-
        down_write(&uprobe->consumer_rwsem);
-       for (con = &uprobe->consumers; *con; con = &(*con)->next) {
-               if (*con == uc) {
-                       *con = uc->next;
-                       ret = true;
-                       break;
-               }
-       }
+       list_del_rcu(&uc->cons_node);
        up_write(&uprobe->consumer_rwsem);
-
-       return ret;
 }
 
 static int __copy_insn(struct address_space *mapping, struct file *filp,
        bool ret = false;
 
        down_read(&uprobe->consumer_rwsem);
-       for (uc = uprobe->consumers; uc; uc = uc->next) {
+       list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node,
+                                srcu_read_lock_held(&uprobes_srcu)) {
                ret = consumer_filter(uc, mm);
                if (ret)
                        break;
        int err;
 
        down_write(&uprobe->register_rwsem);
-       if (WARN_ON(!consumer_del(uprobe, uc))) {
-               err = -ENOENT;
-       } else {
-               err = register_for_each_vma(uprobe, NULL);
-               /* TODO : cant unregister? schedule a worker thread */
-               if (unlikely(err))
-                       uprobe_warn(current, "unregister, leaking uprobe");
-       }
+       consumer_del(uprobe, uc);
+       err = register_for_each_vma(uprobe, NULL);
        up_write(&uprobe->register_rwsem);
 
-       if (!err)
-               put_uprobe(uprobe);
+       /* TODO : cant unregister? schedule a worker thread */
+       if (unlikely(err)) {
+               uprobe_warn(current, "unregister, leaking uprobe");
+               goto out_sync;
+       }
+
+       put_uprobe(uprobe);
+
+out_sync:
+       /*
+        * Now that handler_chain() and handle_uretprobe_chain() iterate over
+        * uprobe->consumers list under RCU protection without holding
+        * uprobe->register_rwsem, we need to wait for RCU grace period to
+        * make sure that we can't call into just unregistered
+        * uprobe_consumer's callbacks anymore. If we don't do that, fast and
+        * unlucky enough caller can free consumer's memory and cause
+        * handler_chain() or handle_uretprobe_chain() to do an use-after-free.
+        */
+       synchronize_srcu(&uprobes_srcu);
 }
 EXPORT_SYMBOL_GPL(uprobe_unregister);
 
 int uprobe_apply(struct uprobe *uprobe, struct uprobe_consumer *uc, bool add)
 {
        struct uprobe_consumer *con;
-       int ret = -ENOENT;
+       int ret = -ENOENT, srcu_idx;
 
        down_write(&uprobe->register_rwsem);
-       for (con = uprobe->consumers; con && con != uc ; con = con->next)
-               ;
-       if (con)
-               ret = register_for_each_vma(uprobe, add ? uc : NULL);
+
+       srcu_idx = srcu_read_lock(&uprobes_srcu);
+       list_for_each_entry_srcu(con, &uprobe->consumers, cons_node,
+                                srcu_read_lock_held(&uprobes_srcu)) {
+               if (con == uc) {
+                       ret = register_for_each_vma(uprobe, add ? uc : NULL);
+                       break;
+               }
+       }
+       srcu_read_unlock(&uprobes_srcu, srcu_idx);
+
        up_write(&uprobe->register_rwsem);
 
        return ret;
        struct uprobe_consumer *uc;
        int remove = UPROBE_HANDLER_REMOVE;
        bool need_prep = false; /* prepare return uprobe, when needed */
+       bool has_consumers = false;
 
-       down_read(&uprobe->register_rwsem);
        current->utask->auprobe = &uprobe->arch;
-       for (uc = uprobe->consumers; uc; uc = uc->next) {
+
+       list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node,
+                                srcu_read_lock_held(&uprobes_srcu)) {
                int rc = 0;
 
                if (uc->handler) {
                        need_prep = true;
 
                remove &= rc;
+               has_consumers = true;
        }
        current->utask->auprobe = NULL;
 
        if (need_prep && !remove)
                prepare_uretprobe(uprobe, regs); /* put bp at return */
 
-       if (remove && uprobe->consumers) {
-               WARN_ON(!uprobe_is_active(uprobe));
-               unapply_uprobe(uprobe, current->mm);
+       if (remove && has_consumers) {
+               down_read(&uprobe->register_rwsem);
+
+               /* re-check that removal is still required, this time under lock */
+               if (!filter_chain(uprobe, current->mm)) {
+                       WARN_ON(!uprobe_is_active(uprobe));
+                       unapply_uprobe(uprobe, current->mm);
+               }
+
+               up_read(&uprobe->register_rwsem);
        }
-       up_read(&uprobe->register_rwsem);
 }
 
 static void
 {
        struct uprobe *uprobe = ri->uprobe;
        struct uprobe_consumer *uc;
+       int srcu_idx;
 
-       down_read(&uprobe->register_rwsem);
-       for (uc = uprobe->consumers; uc; uc = uc->next) {
+       srcu_idx = srcu_read_lock(&uprobes_srcu);
+       list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node,
+                                srcu_read_lock_held(&uprobes_srcu)) {
                if (uc->ret_handler)
                        uc->ret_handler(uc, ri->func, regs);
        }
-       up_read(&uprobe->register_rwsem);
+       srcu_read_unlock(&uprobes_srcu, srcu_idx);
 }
 
 static struct return_instance *find_next_ret_chain(struct return_instance *ri)