]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
mm,vmscan: Allow preallocating memory for register_shrinker().
authorTetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Wed, 4 Apr 2018 10:53:07 +0000 (19:53 +0900)
committerKhalid Elmously <khalid.elmously@canonical.com>
Wed, 6 Jun 2018 17:44:25 +0000 (13:44 -0400)
BugLink: http://bugs.launchpad.net/bugs/1774063
commit 8e04944f0ea8b838399049bdcda920ab36ae3b04 upstream.

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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Signed-off-by: Khalid Elmously <khalid.elmously@canonical.com>
fs/super.c
include/linux/shrinker.h
mm/vmscan.c

index 8857be9d604eeed74b03f37df8c825f9e4a41d30..7fa0bc5065b95f62a49794407b2acabc5802ee6e 100644 (file)
@@ -166,6 +166,7 @@ static void destroy_unused_super(struct super_block *s)
        security_sb_free(s);
        put_user_ns(s->s_user_ns);
        kfree(s->s_subtype);
+       free_prealloced_shrinker(&s->s_shrink);
        /* no delays needed */
        destroy_super_work(&s->destroy_work);
 }
@@ -251,6 +252,8 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
        s->s_shrink.count_objects = super_cache_count;
        s->s_shrink.batch = 1024;
        s->s_shrink.flags = SHRINKER_NUMA_AWARE | SHRINKER_MEMCG_AWARE;
+       if (prealloc_shrinker(&s->s_shrink))
+               goto fail;
        return s;
 
 fail:
@@ -517,11 +520,7 @@ retry:
        hlist_add_head(&s->s_instances, &type->fs_supers);
        spin_unlock(&sb_lock);
        get_filesystem(type);
-       err = register_shrinker(&s->s_shrink);
-       if (err) {
-               deactivate_locked_super(s);
-               s = ERR_PTR(err);
-       }
+       register_shrinker_prepared(&s->s_shrink);
        return s;
 }
 
index 388ff2936a8716d6c90f6ced8a7a8193a6957e06..6794490f25b2bd80cfb6fc4c8dec7067e824e100 100644 (file)
@@ -75,6 +75,9 @@ struct shrinker {
 #define SHRINKER_NUMA_AWARE    (1 << 0)
 #define SHRINKER_MEMCG_AWARE   (1 << 1)
 
-extern int register_shrinker(struct shrinker *);
-extern void unregister_shrinker(struct shrinker *);
+extern int prealloc_shrinker(struct shrinker *shrinker);
+extern void register_shrinker_prepared(struct shrinker *shrinker);
+extern int register_shrinker(struct shrinker *shrinker);
+extern void unregister_shrinker(struct shrinker *shrinker);
+extern void free_prealloced_shrinker(struct shrinker *shrinker);
 #endif
index ff9066d096c1768c8f8e03a0c702d2b901c6a61d..a7770b1ed527577034e071e9def6924737b7367c 100644 (file)
@@ -274,7 +274,7 @@ unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone
 /*
  * Add a shrinker callback to be called from the vm.
  */
-int register_shrinker(struct shrinker *shrinker)
+int prealloc_shrinker(struct shrinker *shrinker)
 {
        size_t size = sizeof(*shrinker->nr_deferred);
 
@@ -284,10 +284,29 @@ int register_shrinker(struct shrinker *shrinker)
        shrinker->nr_deferred = kzalloc(size, GFP_KERNEL);
        if (!shrinker->nr_deferred)
                return -ENOMEM;
+       return 0;
+}
+
+void free_prealloced_shrinker(struct shrinker *shrinker)
+{
+       kfree(shrinker->nr_deferred);
+       shrinker->nr_deferred = NULL;
+}
 
+void register_shrinker_prepared(struct shrinker *shrinker)
+{
        down_write(&shrinker_rwsem);
        list_add_tail(&shrinker->list, &shrinker_list);
        up_write(&shrinker_rwsem);
+}
+
+int register_shrinker(struct shrinker *shrinker)
+{
+       int err = prealloc_shrinker(shrinker);
+
+       if (err)
+               return err;
+       register_shrinker_prepared(shrinker);
        return 0;
 }
 EXPORT_SYMBOL(register_shrinker);