diff options
author | Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> | 2018-04-04 06:53:07 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2018-04-16 02:06:47 -0400 |
commit | 8e04944f0ea8b838399049bdcda920ab36ae3b04 (patch) | |
tree | 830ce2c35100627212caaf732396cafd7bfc1dc8 | |
parent | 4a3877c4cedd95543f8726b0a98743ed8db0c0fb (diff) |
mm,vmscan: Allow preallocating memory for register_shrinker().
syzbot is catching so many bugs triggered by commit 9ee332d99e4d5a97
("sget(): handle failures of register_shrinker()"). That commit expected
that calling kill_sb() from deactivate_locked_super() without successful
fill_super() is safe, but the reality was different; some callers assign
attributes which are needed for kill_sb() after sget() succeeds.
For example, [1] is a report where sb->s_mode (which seems to be either
FMODE_READ | FMODE_EXCL | FMODE_WRITE or FMODE_READ | FMODE_EXCL) is not
assigned unless sget() succeeds. But it does not worth complicate sget()
so that register_shrinker() failure path can safely call
kill_block_super() via kill_sb(). Making alloc_super() fail if memory
allocation for register_shrinker() failed is much simpler. Let's avoid
calling deactivate_locked_super() from sget_userns() by preallocating
memory for the shrinker and making register_shrinker() in sget_userns()
never fail.
[1] https://syzkaller.appspot.com/bug?id=588996a25a2587be2e3a54e8646728fb9cae44e7
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Reported-by: syzbot <syzbot+5a170e19c963a2e0df79@syzkaller.appspotmail.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Michal Hocko <mhocko@suse.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r-- | fs/super.c | 9 | ||||
-rw-r--r-- | include/linux/shrinker.h | 7 | ||||
-rw-r--r-- | mm/vmscan.c | 21 |
3 files changed, 29 insertions, 8 deletions
diff --git a/fs/super.c b/fs/super.c index 5fa9a8d8d865..122c402049a2 100644 --- a/fs/super.c +++ b/fs/super.c | |||
@@ -167,6 +167,7 @@ static void destroy_unused_super(struct super_block *s) | |||
167 | security_sb_free(s); | 167 | security_sb_free(s); |
168 | put_user_ns(s->s_user_ns); | 168 | put_user_ns(s->s_user_ns); |
169 | kfree(s->s_subtype); | 169 | kfree(s->s_subtype); |
170 | free_prealloced_shrinker(&s->s_shrink); | ||
170 | /* no delays needed */ | 171 | /* no delays needed */ |
171 | destroy_super_work(&s->destroy_work); | 172 | destroy_super_work(&s->destroy_work); |
172 | } | 173 | } |
@@ -252,6 +253,8 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, | |||
252 | s->s_shrink.count_objects = super_cache_count; | 253 | s->s_shrink.count_objects = super_cache_count; |
253 | s->s_shrink.batch = 1024; | 254 | s->s_shrink.batch = 1024; |
254 | s->s_shrink.flags = SHRINKER_NUMA_AWARE | SHRINKER_MEMCG_AWARE; | 255 | s->s_shrink.flags = SHRINKER_NUMA_AWARE | SHRINKER_MEMCG_AWARE; |
256 | if (prealloc_shrinker(&s->s_shrink)) | ||
257 | goto fail; | ||
255 | return s; | 258 | return s; |
256 | 259 | ||
257 | fail: | 260 | fail: |
@@ -518,11 +521,7 @@ retry: | |||
518 | hlist_add_head(&s->s_instances, &type->fs_supers); | 521 | hlist_add_head(&s->s_instances, &type->fs_supers); |
519 | spin_unlock(&sb_lock); | 522 | spin_unlock(&sb_lock); |
520 | get_filesystem(type); | 523 | get_filesystem(type); |
521 | err = register_shrinker(&s->s_shrink); | 524 | register_shrinker_prepared(&s->s_shrink); |
522 | if (err) { | ||
523 | deactivate_locked_super(s); | ||
524 | s = ERR_PTR(err); | ||
525 | } | ||
526 | return s; | 525 | return s; |
527 | } | 526 | } |
528 | 527 | ||
diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h index 388ff2936a87..6794490f25b2 100644 --- a/include/linux/shrinker.h +++ b/include/linux/shrinker.h | |||
@@ -75,6 +75,9 @@ struct shrinker { | |||
75 | #define SHRINKER_NUMA_AWARE (1 << 0) | 75 | #define SHRINKER_NUMA_AWARE (1 << 0) |
76 | #define SHRINKER_MEMCG_AWARE (1 << 1) | 76 | #define SHRINKER_MEMCG_AWARE (1 << 1) |
77 | 77 | ||
78 | extern int register_shrinker(struct shrinker *); | 78 | extern int prealloc_shrinker(struct shrinker *shrinker); |
79 | extern void unregister_shrinker(struct shrinker *); | 79 | extern void register_shrinker_prepared(struct shrinker *shrinker); |
80 | extern int register_shrinker(struct shrinker *shrinker); | ||
81 | extern void unregister_shrinker(struct shrinker *shrinker); | ||
82 | extern void free_prealloced_shrinker(struct shrinker *shrinker); | ||
80 | #endif | 83 | #endif |
diff --git a/mm/vmscan.c b/mm/vmscan.c index 8b920ce3ae02..9b697323a88c 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c | |||
@@ -303,7 +303,7 @@ unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone | |||
303 | /* | 303 | /* |
304 | * Add a shrinker callback to be called from the vm. | 304 | * Add a shrinker callback to be called from the vm. |
305 | */ | 305 | */ |
306 | int register_shrinker(struct shrinker *shrinker) | 306 | int prealloc_shrinker(struct shrinker *shrinker) |
307 | { | 307 | { |
308 | size_t size = sizeof(*shrinker->nr_deferred); | 308 | size_t size = sizeof(*shrinker->nr_deferred); |
309 | 309 | ||
@@ -313,10 +313,29 @@ int register_shrinker(struct shrinker *shrinker) | |||
313 | shrinker->nr_deferred = kzalloc(size, GFP_KERNEL); | 313 | shrinker->nr_deferred = kzalloc(size, GFP_KERNEL); |
314 | if (!shrinker->nr_deferred) | 314 | if (!shrinker->nr_deferred) |
315 | return -ENOMEM; | 315 | return -ENOMEM; |
316 | return 0; | ||
317 | } | ||
318 | |||
319 | void free_prealloced_shrinker(struct shrinker *shrinker) | ||
320 | { | ||
321 | kfree(shrinker->nr_deferred); | ||
322 | shrinker->nr_deferred = NULL; | ||
323 | } | ||
316 | 324 | ||
325 | void register_shrinker_prepared(struct shrinker *shrinker) | ||
326 | { | ||
317 | down_write(&shrinker_rwsem); | 327 | down_write(&shrinker_rwsem); |
318 | list_add_tail(&shrinker->list, &shrinker_list); | 328 | list_add_tail(&shrinker->list, &shrinker_list); |
319 | up_write(&shrinker_rwsem); | 329 | up_write(&shrinker_rwsem); |
330 | } | ||
331 | |||
332 | int register_shrinker(struct shrinker *shrinker) | ||
333 | { | ||
334 | int err = prealloc_shrinker(shrinker); | ||
335 | |||
336 | if (err) | ||
337 | return err; | ||
338 | register_shrinker_prepared(shrinker); | ||
320 | return 0; | 339 | return 0; |
321 | } | 340 | } |
322 | EXPORT_SYMBOL(register_shrinker); | 341 | EXPORT_SYMBOL(register_shrinker); |