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)
commit86e3baf6a6a2b8c145f17dad7df7c6af2d6cc293
tree9de4aabe984c92181635cdd7bc6de3c69db2bc62
parentb184caaf62aa4ee6a5932eee555e630f34880616
mm: shrinkers: fix deadlock in shrinker debugfs

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