Support legacy filesystems by creating a set of legacy sb_config operations that builds up a list of mount options and then invokes fs_type->mount() within the sb_config mount operation. All filesystems can then be accessed using sb_config and the fs_type->mount() call is _only_ used from within legacy_mount(). This allows some simplification to take place in the core mount code. --- fs/namespace.c | 37 ++----------- fs/sb_config.c | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 161 insertions(+), 36 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 49d630a4fbd4..6d809f5705cd 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2583,7 +2583,6 @@ static int do_new_mount(struct path *mountpoint, const char *fstype, int flags, int mnt_flags, const char *name, void *data) { struct sb_config *sc; - struct vfsmount *mnt; int err; if (!fstype) @@ -2599,41 +2598,17 @@ static int do_new_mount(struct path *mountpoint, const char *fstype, int flags, if (!sc->device) goto err_sc; - if (sc->ops) { - err = parse_monolithic_mount_data(sc, data); - if (err < 0) - goto err_sc; - - err = do_new_mount_sc(sc, mountpoint, mnt_flags); - if (err) - goto err_sc; - - } else { - mnt = vfs_kern_mount(sc->fs_type, flags, name, data); - if (!IS_ERR(mnt) && (sc->fs_type->fs_flags & FS_HAS_SUBTYPE) && - !mnt->mnt_sb->s_subtype) - mnt = fs_set_subtype(mnt, fstype); - - if (IS_ERR(mnt)) { - err = PTR_ERR(mnt); - goto err_sc; - } - - err = -EPERM; - if (mount_too_revealing(mnt, &mnt_flags)) - goto err_mnt; + err = parse_monolithic_mount_data(sc, data); + if (err < 0) + goto err_sc; - err = do_add_mount(real_mount(mnt), mountpoint, mnt_flags, - sc->mnt_ns); - if (err) - goto err_mnt; - } + err = do_new_mount_sc(sc, mountpoint, mnt_flags); + if (err) + goto err_sc; put_sb_config(sc); return 0; -err_mnt: - mntput(mnt); err_sc: if (sc->error_msg) pr_info("Mount failed: %s\n", sc->error_msg); diff --git a/fs/sb_config.c b/fs/sb_config.c index 2c6789d1d71b..4429ac35161c 100644 --- a/fs/sb_config.c +++ b/fs/sb_config.c @@ -25,6 +25,15 @@ #include <net/net_namespace.h> #include "mount.h" +struct legacy_sb_config { + struct sb_config sc; + char *legacy_data; /* Data page for legacy filesystems */ + char *secdata; + unsigned int data_usage; +}; + +static const struct sb_config_operations legacy_sb_config_ops; + static const match_table_t common_set_mount_options = { { MS_DIRSYNC, "dirsync" }, { MS_I_VERSION, "iversion" }, @@ -186,13 +195,15 @@ struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type, enum sb_config_purpose purpose) { struct sb_config *sc; + size_t sc_size = fs_type->sb_config_size; int ret; - BUG_ON(fs_type->init_sb_config && - fs_type->sb_config_size < sizeof(*sc)); + BUG_ON(fs_type->init_sb_config && sc_size < sizeof(*sc)); + + if (!fs_type->init_sb_config) + sc_size = sizeof(struct legacy_sb_config); - sc = kzalloc(max_t(size_t, fs_type->sb_config_size, sizeof(*sc)), - GFP_KERNEL); + sc = kzalloc(sc_size, GFP_KERNEL); if (!sc) return ERR_PTR(-ENOMEM); @@ -211,9 +222,11 @@ struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type, ret = sc->fs_type->init_sb_config(sc, src_sb); if (ret < 0) goto err_sc; + } else { + sc->ops = &legacy_sb_config_ops; } - /* Do the security check last because ->fsopen may change the + /* Do the security check last because ->init_sb_config may change the * namespace subscriptions. */ ret = security_sb_config_alloc(sc, src_sb); @@ -275,11 +288,16 @@ struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt, struct sb_config *vfs_dup_sb_config(struct sb_config *src_sc) { struct sb_config *sc; + size_t sc_size; int ret; if (!src_sc->ops->dup) return ERR_PTR(-ENOTSUPP); + sc_size = src_sc->fs_type->sb_config_size; + if (!src_sc->fs_type->init_sb_config) + sc_size = sizeof(struct legacy_sb_config); + sc = kmemdup(src_sc, src_sc->fs_type->sb_config_size, GFP_KERNEL); if (!sc) return ERR_PTR(-ENOMEM); @@ -333,3 +351,135 @@ void put_sb_config(struct sb_config *sc) kfree(sc); } EXPORT_SYMBOL(put_sb_config); + +/* + * Free the config for a filesystem that doesn't support sb_config. + */ +static void legacy_sb_config_free(struct sb_config *sc) +{ + struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc); + + free_secdata(cfg->secdata); + kfree(cfg->legacy_data); +} + +/* + * Duplicate a legacy config. + */ +static int legacy_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc) +{ + struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc); + struct legacy_sb_config *src_cfg = container_of(src_sc, struct legacy_sb_config, sc); + + cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!cfg->legacy_data) + return -ENOMEM; + memcpy(cfg->legacy_data, src_cfg->legacy_data, sizeof(PAGE_SIZE)); + return 0; +} + +/* + * Add an option to a legacy config. We build up a comma-separated list of + * options. + */ +static int legacy_parse_option(struct sb_config *sc, char *p) +{ + struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc); + unsigned int usage = cfg->data_usage; + size_t len = strlen(p); + + if (len > PAGE_SIZE - 2 - usage) + return sb_cfg_inval(sc, "VFS: Insufficient data buffer space"); + if (memchr(p, ',', len) != NULL) + return sb_cfg_inval(sc, "VFS: Options cannot contain commas"); + if (!cfg->legacy_data) { + cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!cfg->legacy_data) + return -ENOMEM; + } + + cfg->legacy_data[usage++] = ','; + memcpy(cfg->legacy_data + usage, p, len); + usage += len; + cfg->legacy_data[usage] = '\0'; + cfg->data_usage = usage; + return 0; +} + +/* + * Add monolithic mount data. + */ +static int legacy_monolithic_mount_data(struct sb_config *sc, void *data) +{ + struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc); + + if (cfg->data_usage != 0) + return sb_cfg_inval(sc, "VFS: Can't mix monolithic and individual options"); + if (!data) + return 0; + if (!cfg->legacy_data) { + cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!cfg->legacy_data) + return -ENOMEM; + } + + memcpy(cfg->legacy_data, data, PAGE_SIZE); + cfg->data_usage = PAGE_SIZE; + return 0; +} + +/* + * Use the legacy mount validation step to strip out and process security + * config options. + */ +static int legacy_validate(struct sb_config *sc) +{ + struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc); + + if (!cfg->legacy_data || cfg->sc.fs_type->fs_flags & FS_BINARY_MOUNTDATA) + return 0; + + cfg->secdata = alloc_secdata(); + if (!cfg->secdata) + return -ENOMEM; + + return security_sb_copy_data(cfg->legacy_data, cfg->secdata); +} + +/* + * Perform a legacy mount. + */ +static struct dentry *legacy_mount(struct sb_config *sc) +{ + struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc); + struct super_block *sb; + struct dentry *root; + int ret; + + root = cfg->sc.fs_type->mount(cfg->sc.fs_type, cfg->sc.ms_flags, + cfg->sc.device, cfg->legacy_data); + if (IS_ERR(root)) + return ERR_CAST(root); + + sb = root->d_sb; + BUG_ON(!sb); + ret = security_sb_kern_mount(sb, cfg->sc.ms_flags, cfg->secdata); + if (ret < 0) + goto err_sb; + + return root; + +err_sb: + dput(root); + deactivate_locked_super(sb); + return ERR_PTR(ret); +} + +static const struct sb_config_operations legacy_sb_config_ops = { + .free = legacy_sb_config_free, + .dup = legacy_sb_config_dup, + .parse_option = legacy_parse_option, + .monolithic_mount_data = legacy_monolithic_mount_data, + .validate = legacy_validate, + .mount = legacy_mount, +};