Currently put_uprobe() might trigger mutex_lock()/mutex_unlock(), which
makes it unsuitable to be called from more restricted context like softirq.

Let's make put_uprobe() agnostic to the context in which it is called,
and use work queue to defer the mutex-protected clean up steps.

RB tree removal step is also moved into work-deferred callback to avoid
potential deadlock between softirq-based timer callback, added in the
next patch, and the rest of uprobe code.

Signed-off-by: Andrii Nakryiko <[email protected]>
---
 kernel/events/uprobes.c | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index a2e6a57f79f2..9d3ab472200d 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -27,6 +27,7 @@
 #include <linux/shmem_fs.h>
 #include <linux/khugepaged.h>
 #include <linux/rcupdate_trace.h>
+#include <linux/workqueue.h>
 
 #include <linux/uprobes.h>
 
@@ -61,7 +62,10 @@ struct uprobe {
        struct list_head        pending_list;
        struct list_head        consumers;
        struct inode            *inode;         /* Also hold a ref to inode */
-       struct rcu_head         rcu;
+       union {
+               struct rcu_head         rcu;
+               struct work_struct      work;
+       };
        loff_t                  offset;
        loff_t                  ref_ctr_offset;
        unsigned long           flags;
@@ -627,10 +631,9 @@ static void uprobe_free_rcu(struct rcu_head *rcu)
        kfree(uprobe);
 }
 
-static void put_uprobe(struct uprobe *uprobe)
+static void uprobe_free_deferred(struct work_struct *work)
 {
-       if (!refcount_dec_and_test(&uprobe->ref))
-               return;
+       struct uprobe *uprobe = container_of(work, struct uprobe, work);
 
        write_lock(&uprobes_treelock);
 
@@ -654,6 +657,15 @@ static void put_uprobe(struct uprobe *uprobe)
        call_rcu_tasks_trace(&uprobe->rcu, uprobe_free_rcu);
 }
 
+static void put_uprobe(struct uprobe *uprobe)
+{
+       if (!refcount_dec_and_test(&uprobe->ref))
+               return;
+
+       INIT_WORK(&uprobe->work, uprobe_free_deferred);
+       schedule_work(&uprobe->work);
+}
+
 static __always_inline
 int uprobe_cmp(const struct inode *l_inode, const loff_t l_offset,
               const struct uprobe *r)
-- 
2.43.5


Reply via email to