mm: shrinkers: fix deadlock in shrinker debugfs
authorQi Zheng <zhengqi.arch@bytedance.com>
Thu, 2 Feb 2023 10:56:12 +0000 (18:56 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 22 Feb 2023 11:59:46 +0000 (12:59 +0100)
commit badc28d4924bfed73efc93f716a0c3aa3afbdf6f upstream.

The debugfs_remove_recursive() is invoked by unregister_shrinker(), which
is holding the write lock of shrinker_rwsem.  It will waits for the
handler of debugfs file complete.  The handler also needs to hold the read
lock of shrinker_rwsem to do something.  So it may cause the following
deadlock:

  CPU0 CPU1

debugfs_file_get()
shrinker_debugfs_count_show()/shrinker_debugfs_scan_write()

      unregister_shrinker()
--> down_write(&shrinker_rwsem);
    debugfs_remove_recursive()
// wait for (A)
    --> wait_for_completion();

    // wait for (B)
--> down_read_killable(&shrinker_rwsem)
debugfs_file_put() -- (A)

    up_write() -- (B)

The down_read_killable() can be killed, so that the above deadlock can be
recovered.  But it still requires an extra kill action, otherwise it will
block all subsequent shrinker-related operations, so it's better to fix
it.

[akpm@linux-foundation.org: fix CONFIG_SHRINKER_DEBUG=n stub]
Link: https://lkml.kernel.org/r/20230202105612.64641-1-zhengqi.arch@bytedance.com
Fixes: 5035ebc644ae ("mm: shrinkers: introduce debugfs interface for memory shrinkers")
Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
Reviewed-by: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Kent Overstreet <kent.overstreet@gmail.com>
Cc: Muchun Song <songmuchun@bytedance.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
include/linux/shrinker.h
mm/shrinker_debug.c
mm/vmscan.c

index 08e6054..258b615 100644 (file)
@@ -104,7 +104,7 @@ extern void synchronize_shrinkers(void);
 
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
-extern void shrinker_debugfs_remove(struct shrinker *shrinker);
+extern struct dentry *shrinker_debugfs_remove(struct shrinker *shrinker);
 extern int __printf(2, 3) shrinker_debugfs_rename(struct shrinker *shrinker,
                                                  const char *fmt, ...);
 #else /* CONFIG_SHRINKER_DEBUG */
@@ -112,8 +112,9 @@ static inline int shrinker_debugfs_add(struct shrinker *shrinker)
 {
        return 0;
 }
-static inline void shrinker_debugfs_remove(struct shrinker *shrinker)
+static inline struct dentry *shrinker_debugfs_remove(struct shrinker *shrinker)
 {
+       return NULL;
 }
 static inline __printf(2, 3)
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
index b05295b..39c3491 100644 (file)
@@ -246,18 +246,21 @@ int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
 }
 EXPORT_SYMBOL(shrinker_debugfs_rename);
 
-void shrinker_debugfs_remove(struct shrinker *shrinker)
+struct dentry *shrinker_debugfs_remove(struct shrinker *shrinker)
 {
+       struct dentry *entry = shrinker->debugfs_entry;
+
        lockdep_assert_held(&shrinker_rwsem);
 
        kfree_const(shrinker->name);
        shrinker->name = NULL;
 
-       if (!shrinker->debugfs_entry)
-               return;
+       if (entry) {
+               ida_free(&shrinker_debugfs_ida, shrinker->debugfs_id);
+               shrinker->debugfs_entry = NULL;
+       }
 
-       debugfs_remove_recursive(shrinker->debugfs_entry);
-       ida_free(&shrinker_debugfs_ida, shrinker->debugfs_id);
+       return entry;
 }
 
 static int __init shrinker_debugfs_init(void)
index 96eb9da..dc66f67 100644 (file)
@@ -740,6 +740,8 @@ EXPORT_SYMBOL(register_shrinker);
  */
 void unregister_shrinker(struct shrinker *shrinker)
 {
+       struct dentry *debugfs_entry;
+
        if (!(shrinker->flags & SHRINKER_REGISTERED))
                return;
 
@@ -748,9 +750,11 @@ void unregister_shrinker(struct shrinker *shrinker)
        shrinker->flags &= ~SHRINKER_REGISTERED;
        if (shrinker->flags & SHRINKER_MEMCG_AWARE)
                unregister_memcg_shrinker(shrinker);
-       shrinker_debugfs_remove(shrinker);
+       debugfs_entry = shrinker_debugfs_remove(shrinker);
        up_write(&shrinker_rwsem);
 
+       debugfs_remove_recursive(debugfs_entry);
+
        kfree(shrinker->nr_deferred);
        shrinker->nr_deferred = NULL;
 }