]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ovl: make use of ->layers safe in rcu pathwalk
authorAmir Goldstein <amir73il@gmail.com>
Mon, 2 Oct 2023 11:21:49 +0000 (14:21 +0300)
committerAmir Goldstein <amir73il@gmail.com>
Mon, 2 Oct 2023 14:45:02 +0000 (17:45 +0300)
ovl_permission() accesses ->layers[...].mnt; we can't have ->layers
freed without an RCU delay on fs shutdown.

Fortunately, kern_unmount_array() that is used to drop those mounts
does include an RCU delay, so freeing is delayed; unfortunately, the
array passed to kern_unmount_array() is formed by mangling ->layers
contents and that happens without any delays.

The ->layers[...].name string entries are used to store the strings to
display in "lowerdir=..." by ovl_show_options().  Those entries are not
accessed in RCU walk.

Move the name strings into a separate array ofs->config.lowerdirs and
reuse the ofs->config.lowerdirs array as the temporary mount array to
pass to kern_unmount_array().

Reported-by: Al Viro <viro@zeniv.linux.org.uk>
Link: https://lore.kernel.org/r/20231002023711.GP3389589@ZenIV/
Acked-by: Miklos Szeredi <mszeredi@redhat.com>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
fs/overlayfs/ovl_entry.h
fs/overlayfs/params.c
fs/overlayfs/super.c

index e9539f98e86a4b29d0c54dc1859ea13d37cd46f2..d82d2a043da2c7b7ea055a5c7dbbe0b3c2544dbc 100644 (file)
@@ -8,6 +8,7 @@
 struct ovl_config {
        char *upperdir;
        char *workdir;
+       char **lowerdirs;
        bool default_permissions;
        int redirect_mode;
        int verity_mode;
@@ -39,17 +40,8 @@ struct ovl_layer {
        int idx;
        /* One fsid per unique underlying sb (upper fsid == 0) */
        int fsid;
-       char *name;
 };
 
-/*
- * ovl_free_fs() relies on @mnt being the first member when unmounting
- * the private mounts created for each layer. Let's check both the
- * offset and type.
- */
-static_assert(offsetof(struct ovl_layer, mnt) == 0);
-static_assert(__same_type(typeof_member(struct ovl_layer, mnt), struct vfsmount *));
-
 struct ovl_path {
        const struct ovl_layer *layer;
        struct dentry *dentry;
index b9355bb6d75a33128c8ad84903dd0358b4edcd96..95b751507ac87f15733f2f38a05bf418e7b62093 100644 (file)
@@ -752,12 +752,12 @@ void ovl_free_fs(struct ovl_fs *ofs)
        if (ofs->upperdir_locked)
                ovl_inuse_unlock(ovl_upper_mnt(ofs)->mnt_root);
 
-       /* Hack!  Reuse ofs->layers as a vfsmount array before freeing it */
-       mounts = (struct vfsmount **) ofs->layers;
+       /* Reuse ofs->config.lowerdirs as a vfsmount array before freeing it */
+       mounts = (struct vfsmount **) ofs->config.lowerdirs;
        for (i = 0; i < ofs->numlayer; i++) {
                iput(ofs->layers[i].trap);
+               kfree(ofs->config.lowerdirs[i]);
                mounts[i] = ofs->layers[i].mnt;
-               kfree(ofs->layers[i].name);
        }
        kern_unmount_array(mounts, ofs->numlayer);
        kfree(ofs->layers);
@@ -765,6 +765,7 @@ void ovl_free_fs(struct ovl_fs *ofs)
                free_anon_bdev(ofs->fs[i].pseudo_dev);
        kfree(ofs->fs);
 
+       kfree(ofs->config.lowerdirs);
        kfree(ofs->config.upperdir);
        kfree(ofs->config.workdir);
        if (ofs->creator_cred)
@@ -949,16 +950,16 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
        struct super_block *sb = dentry->d_sb;
        struct ovl_fs *ofs = OVL_FS(sb);
        size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer;
-       const struct ovl_layer *data_layers = &ofs->layers[nr_merged_lower];
+       char **lowerdatadirs = &ofs->config.lowerdirs[nr_merged_lower];
 
-       /* ofs->layers[0] is the upper layer */
-       seq_printf(m, ",lowerdir=%s", ofs->layers[1].name);
+       /* lowerdirs[] starts from offset 1 */
+       seq_printf(m, ",lowerdir=%s", ofs->config.lowerdirs[1]);
        /* dump regular lower layers */
        for (nr = 2; nr < nr_merged_lower; nr++)
-               seq_printf(m, ":%s", ofs->layers[nr].name);
+               seq_printf(m, ":%s", ofs->config.lowerdirs[nr]);
        /* dump data lower layers */
        for (nr = 0; nr < ofs->numdatalayer; nr++)
-               seq_printf(m, "::%s", data_layers[nr].name);
+               seq_printf(m, "::%s", lowerdatadirs[nr]);
        if (ofs->config.upperdir) {
                seq_show_option(m, "upperdir", ofs->config.upperdir);
                seq_show_option(m, "workdir", ofs->config.workdir);
index 905d3aaf4e5563eb9bc378ca9be7cc2e62042a4b..3fa2416264a4e67b9bccd84700224ca6f484102f 100644 (file)
@@ -572,11 +572,6 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
        upper_layer->idx = 0;
        upper_layer->fsid = 0;
 
-       err = -ENOMEM;
-       upper_layer->name = kstrdup(ofs->config.upperdir, GFP_KERNEL);
-       if (!upper_layer->name)
-               goto out;
-
        /*
         * Inherit SB_NOSEC flag from upperdir.
         *
@@ -1125,7 +1120,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
                layers[ofs->numlayer].idx = ofs->numlayer;
                layers[ofs->numlayer].fsid = fsid;
                layers[ofs->numlayer].fs = &ofs->fs[fsid];
-               layers[ofs->numlayer].name = l->name;
+               /* Store for printing lowerdir=... in ovl_show_options() */
+               ofs->config.lowerdirs[ofs->numlayer] = l->name;
                l->name = NULL;
                ofs->numlayer++;
                ofs->fs[fsid].is_lower = true;
@@ -1370,8 +1366,16 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
        if (!layers)
                goto out_err;
 
+       ofs->config.lowerdirs = kcalloc(ctx->nr + 1, sizeof(char *), GFP_KERNEL);
+       if (!ofs->config.lowerdirs) {
+               kfree(layers);
+               goto out_err;
+       }
        ofs->layers = layers;
-       /* Layer 0 is reserved for upper even if there's no upper */
+       /*
+        * Layer 0 is reserved for upper even if there's no upper.
+        * For consistency, config.lowerdirs[0] is NULL.
+        */
        ofs->numlayer = 1;
 
        sb->s_stack_depth = 0;