Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mm: shrinkers: fix race condition on debugfs cleanup

When something registers and unregisters many shrinkers, such as:
for x in $(seq 10000); do unshare -Ui true; done

Sometimes the following error is printed to the kernel log:
debugfs: Directory '...' with parent 'shrinker' already present!

This occurs since commit badc28d4924b ("mm: shrinkers: fix deadlock in
shrinker debugfs") / v6.2: Since the call to `debugfs_remove_recursive`
was moved outside the `shrinker_rwsem`/`shrinker_mutex` lock, but the call
to `ida_free` stayed inside, a newly registered shrinker can be
re-assigned that ID and attempt to create the debugfs directory before the
directory from the previous shrinker has been removed.

The locking changes in commit f95bdb700bc6 ("mm: vmscan: make global slab
shrink lockless") made the race condition more likely, though it existed
before then.

Commit badc28d4924b ("mm: shrinkers: fix deadlock in shrinker debugfs")
could be reverted since the issue is addressed should no longer occur
since the count and scan operations are lockless since commit 20cd1892fcc3
("mm: shrinkers: make count and scan in shrinker debugfs lockless").
However, since this is a contended lock, prefer instead moving `ida_free`
outside the lock to avoid the race.

Link: https://lkml.kernel.org/r/20230503013232.299211-1-joanbrugueram@gmail.com
Fixes: badc28d4924b ("mm: shrinkers: fix deadlock in shrinker debugfs")
Signed-off-by: Joan Bruguera Micó <joanbrugueram@gmail.com>
Cc: Qi Zheng <zhengqi.arch@bytedance.com>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Joan Bruguera Micó and committed by
Andrew Morton
26e239b3 0257d990

+24 -9
+11 -2
include/linux/shrinker.h
··· 107 107 108 108 #ifdef CONFIG_SHRINKER_DEBUG 109 109 extern int shrinker_debugfs_add(struct shrinker *shrinker); 110 - extern struct dentry *shrinker_debugfs_remove(struct shrinker *shrinker); 110 + extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker, 111 + int *debugfs_id); 112 + extern void shrinker_debugfs_remove(struct dentry *debugfs_entry, 113 + int debugfs_id); 111 114 extern int __printf(2, 3) shrinker_debugfs_rename(struct shrinker *shrinker, 112 115 const char *fmt, ...); 113 116 #else /* CONFIG_SHRINKER_DEBUG */ ··· 118 115 { 119 116 return 0; 120 117 } 121 - static inline struct dentry *shrinker_debugfs_remove(struct shrinker *shrinker) 118 + static inline struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker, 119 + int *debugfs_id) 122 120 { 121 + *debugfs_id = -1; 123 122 return NULL; 123 + } 124 + static inline void shrinker_debugfs_remove(struct dentry *debugfs_entry, 125 + int debugfs_id) 126 + { 124 127 } 125 128 static inline __printf(2, 3) 126 129 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
+10 -5
mm/shrinker_debug.c
··· 237 237 } 238 238 EXPORT_SYMBOL(shrinker_debugfs_rename); 239 239 240 - struct dentry *shrinker_debugfs_remove(struct shrinker *shrinker) 240 + struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker, 241 + int *debugfs_id) 241 242 { 242 243 struct dentry *entry = shrinker->debugfs_entry; 243 244 ··· 247 246 kfree_const(shrinker->name); 248 247 shrinker->name = NULL; 249 248 250 - if (entry) { 251 - ida_free(&shrinker_debugfs_ida, shrinker->debugfs_id); 252 - shrinker->debugfs_entry = NULL; 253 - } 249 + *debugfs_id = entry ? shrinker->debugfs_id : -1; 250 + shrinker->debugfs_entry = NULL; 254 251 255 252 return entry; 253 + } 254 + 255 + void shrinker_debugfs_remove(struct dentry *debugfs_entry, int debugfs_id) 256 + { 257 + debugfs_remove_recursive(debugfs_entry); 258 + ida_free(&shrinker_debugfs_ida, debugfs_id); 256 259 } 257 260 258 261 static int __init shrinker_debugfs_init(void)
+3 -2
mm/vmscan.c
··· 805 805 void unregister_shrinker(struct shrinker *shrinker) 806 806 { 807 807 struct dentry *debugfs_entry; 808 + int debugfs_id; 808 809 809 810 if (!(shrinker->flags & SHRINKER_REGISTERED)) 810 811 return; ··· 815 814 shrinker->flags &= ~SHRINKER_REGISTERED; 816 815 if (shrinker->flags & SHRINKER_MEMCG_AWARE) 817 816 unregister_memcg_shrinker(shrinker); 818 - debugfs_entry = shrinker_debugfs_remove(shrinker); 817 + debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id); 819 818 mutex_unlock(&shrinker_mutex); 820 819 821 820 atomic_inc(&shrinker_srcu_generation); 822 821 synchronize_srcu(&shrinker_srcu); 823 822 824 - debugfs_remove_recursive(debugfs_entry); 823 + shrinker_debugfs_remove(debugfs_entry, debugfs_id); 825 824 826 825 kfree(shrinker->nr_deferred); 827 826 shrinker->nr_deferred = NULL;