|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  |  | 
|  | #include <linux/fs.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/namei.h> | 
|  | #include <linux/fs_context.h> | 
|  | #include <linux/fs_parser.h> | 
|  | #include <linux/posix_acl_xattr.h> | 
|  | #include <linux/seq_file.h> | 
|  | #include <linux/xattr.h> | 
|  | #include "overlayfs.h" | 
|  | #include "params.h" | 
|  |  | 
|  | static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR); | 
|  | module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644); | 
|  | MODULE_PARM_DESC(redirect_dir, | 
|  | "Default to on or off for the redirect_dir feature"); | 
|  |  | 
|  | static bool ovl_redirect_always_follow = | 
|  | IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW); | 
|  | module_param_named(redirect_always_follow, ovl_redirect_always_follow, | 
|  | bool, 0644); | 
|  | MODULE_PARM_DESC(redirect_always_follow, | 
|  | "Follow redirects even if redirect_dir feature is turned off"); | 
|  |  | 
|  | static bool ovl_xino_auto_def = IS_ENABLED(CONFIG_OVERLAY_FS_XINO_AUTO); | 
|  | module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644); | 
|  | MODULE_PARM_DESC(xino_auto, | 
|  | "Auto enable xino feature"); | 
|  |  | 
|  | static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX); | 
|  | module_param_named(index, ovl_index_def, bool, 0644); | 
|  | MODULE_PARM_DESC(index, | 
|  | "Default to on or off for the inodes index feature"); | 
|  |  | 
|  | static bool ovl_nfs_export_def = IS_ENABLED(CONFIG_OVERLAY_FS_NFS_EXPORT); | 
|  | module_param_named(nfs_export, ovl_nfs_export_def, bool, 0644); | 
|  | MODULE_PARM_DESC(nfs_export, | 
|  | "Default to on or off for the NFS export feature"); | 
|  |  | 
|  | static bool ovl_metacopy_def = IS_ENABLED(CONFIG_OVERLAY_FS_METACOPY); | 
|  | module_param_named(metacopy, ovl_metacopy_def, bool, 0644); | 
|  | MODULE_PARM_DESC(metacopy, | 
|  | "Default to on or off for the metadata only copy up feature"); | 
|  |  | 
|  | enum ovl_opt { | 
|  | Opt_lowerdir, | 
|  | Opt_lowerdir_add, | 
|  | Opt_datadir_add, | 
|  | Opt_upperdir, | 
|  | Opt_workdir, | 
|  | Opt_default_permissions, | 
|  | Opt_redirect_dir, | 
|  | Opt_index, | 
|  | Opt_uuid, | 
|  | Opt_nfs_export, | 
|  | Opt_userxattr, | 
|  | Opt_xino, | 
|  | Opt_metacopy, | 
|  | Opt_verity, | 
|  | Opt_volatile, | 
|  | Opt_override_creds, | 
|  | }; | 
|  |  | 
|  | static const struct constant_table ovl_parameter_bool[] = { | 
|  | { "on",		true  }, | 
|  | { "off",	false }, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | static const struct constant_table ovl_parameter_uuid[] = { | 
|  | { "off",	OVL_UUID_OFF  }, | 
|  | { "null",	OVL_UUID_NULL }, | 
|  | { "auto",	OVL_UUID_AUTO }, | 
|  | { "on",		OVL_UUID_ON   }, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | static const char *ovl_uuid_mode(struct ovl_config *config) | 
|  | { | 
|  | return ovl_parameter_uuid[config->uuid].name; | 
|  | } | 
|  |  | 
|  | static int ovl_uuid_def(void) | 
|  | { | 
|  | return OVL_UUID_AUTO; | 
|  | } | 
|  |  | 
|  | static const struct constant_table ovl_parameter_xino[] = { | 
|  | { "off",	OVL_XINO_OFF  }, | 
|  | { "auto",	OVL_XINO_AUTO }, | 
|  | { "on",		OVL_XINO_ON   }, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | const char *ovl_xino_mode(struct ovl_config *config) | 
|  | { | 
|  | return ovl_parameter_xino[config->xino].name; | 
|  | } | 
|  |  | 
|  | static int ovl_xino_def(void) | 
|  | { | 
|  | return ovl_xino_auto_def ? OVL_XINO_AUTO : OVL_XINO_OFF; | 
|  | } | 
|  |  | 
|  | const struct constant_table ovl_parameter_redirect_dir[] = { | 
|  | { "off",	OVL_REDIRECT_OFF      }, | 
|  | { "follow",	OVL_REDIRECT_FOLLOW   }, | 
|  | { "nofollow",	OVL_REDIRECT_NOFOLLOW }, | 
|  | { "on",		OVL_REDIRECT_ON       }, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | static const char *ovl_redirect_mode(struct ovl_config *config) | 
|  | { | 
|  | return ovl_parameter_redirect_dir[config->redirect_mode].name; | 
|  | } | 
|  |  | 
|  | static int ovl_redirect_mode_def(void) | 
|  | { | 
|  | return ovl_redirect_dir_def	  ? OVL_REDIRECT_ON : | 
|  | ovl_redirect_always_follow ? OVL_REDIRECT_FOLLOW : | 
|  | OVL_REDIRECT_NOFOLLOW; | 
|  | } | 
|  |  | 
|  | static const struct constant_table ovl_parameter_verity[] = { | 
|  | { "off",	OVL_VERITY_OFF     }, | 
|  | { "on",		OVL_VERITY_ON      }, | 
|  | { "require",	OVL_VERITY_REQUIRE }, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | static const char *ovl_verity_mode(struct ovl_config *config) | 
|  | { | 
|  | return ovl_parameter_verity[config->verity_mode].name; | 
|  | } | 
|  |  | 
|  | static int ovl_verity_mode_def(void) | 
|  | { | 
|  | return OVL_VERITY_OFF; | 
|  | } | 
|  |  | 
|  | const struct fs_parameter_spec ovl_parameter_spec[] = { | 
|  | fsparam_string_empty("lowerdir",    Opt_lowerdir), | 
|  | fsparam_file_or_string("lowerdir+", Opt_lowerdir_add), | 
|  | fsparam_file_or_string("datadir+",  Opt_datadir_add), | 
|  | fsparam_file_or_string("upperdir",  Opt_upperdir), | 
|  | fsparam_file_or_string("workdir",   Opt_workdir), | 
|  | fsparam_flag("default_permissions", Opt_default_permissions), | 
|  | fsparam_enum("redirect_dir",        Opt_redirect_dir, ovl_parameter_redirect_dir), | 
|  | fsparam_enum("index",               Opt_index, ovl_parameter_bool), | 
|  | fsparam_enum("uuid",                Opt_uuid, ovl_parameter_uuid), | 
|  | fsparam_enum("nfs_export",          Opt_nfs_export, ovl_parameter_bool), | 
|  | fsparam_flag("userxattr",           Opt_userxattr), | 
|  | fsparam_enum("xino",                Opt_xino, ovl_parameter_xino), | 
|  | fsparam_enum("metacopy",            Opt_metacopy, ovl_parameter_bool), | 
|  | fsparam_enum("verity",              Opt_verity, ovl_parameter_verity), | 
|  | fsparam_flag("volatile",            Opt_volatile), | 
|  | fsparam_flag_no("override_creds",   Opt_override_creds), | 
|  | {} | 
|  | }; | 
|  |  | 
|  | static char *ovl_next_opt(char **s) | 
|  | { | 
|  | char *sbegin = *s; | 
|  | char *p; | 
|  |  | 
|  | if (sbegin == NULL) | 
|  | return NULL; | 
|  |  | 
|  | for (p = sbegin; *p; p++) { | 
|  | if (*p == '\\') { | 
|  | p++; | 
|  | if (!*p) | 
|  | break; | 
|  | } else if (*p == ',') { | 
|  | *p = '\0'; | 
|  | *s = p + 1; | 
|  | return sbegin; | 
|  | } | 
|  | } | 
|  | *s = NULL; | 
|  | return sbegin; | 
|  | } | 
|  |  | 
|  | static int ovl_parse_monolithic(struct fs_context *fc, void *data) | 
|  | { | 
|  | return vfs_parse_monolithic_sep(fc, data, ovl_next_opt); | 
|  | } | 
|  |  | 
|  | static ssize_t ovl_parse_param_split_lowerdirs(char *str) | 
|  | { | 
|  | ssize_t nr_layers = 1, nr_colons = 0; | 
|  | char *s, *d; | 
|  |  | 
|  | for (s = d = str;; s++, d++) { | 
|  | if (*s == '\\') { | 
|  | /* keep esc chars in split lowerdir */ | 
|  | *d++ = *s++; | 
|  | } else if (*s == ':') { | 
|  | bool next_colon = (*(s + 1) == ':'); | 
|  |  | 
|  | nr_colons++; | 
|  | if (nr_colons == 2 && next_colon) { | 
|  | pr_err("only single ':' or double '::' sequences of unescaped colons in lowerdir mount option allowed.\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | /* count layers, not colons */ | 
|  | if (!next_colon) | 
|  | nr_layers++; | 
|  |  | 
|  | *d = '\0'; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | *d = *s; | 
|  | if (!*s) { | 
|  | /* trailing colons */ | 
|  | if (nr_colons) { | 
|  | pr_err("unescaped trailing colons in lowerdir mount option.\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | break; | 
|  | } | 
|  | nr_colons = 0; | 
|  | } | 
|  |  | 
|  | return nr_layers; | 
|  | } | 
|  |  | 
|  | static int ovl_mount_dir_noesc(const char *name, struct path *path) | 
|  | { | 
|  | int err = -EINVAL; | 
|  |  | 
|  | if (!*name) { | 
|  | pr_err("empty lowerdir\n"); | 
|  | goto out; | 
|  | } | 
|  | err = kern_path(name, LOOKUP_FOLLOW, path); | 
|  | if (err) { | 
|  | pr_err("failed to resolve '%s': %i\n", name, err); | 
|  | goto out; | 
|  | } | 
|  | return 0; | 
|  |  | 
|  | out: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static void ovl_unescape(char *s) | 
|  | { | 
|  | char *d = s; | 
|  |  | 
|  | for (;; s++, d++) { | 
|  | if (*s == '\\') | 
|  | s++; | 
|  | *d = *s; | 
|  | if (!*s) | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int ovl_mount_dir(const char *name, struct path *path) | 
|  | { | 
|  | int err = -ENOMEM; | 
|  | char *tmp = kstrdup(name, GFP_KERNEL); | 
|  |  | 
|  | if (tmp) { | 
|  | ovl_unescape(tmp); | 
|  | err = ovl_mount_dir_noesc(tmp, path); | 
|  | kfree(tmp); | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path, | 
|  | enum ovl_opt layer, const char *name, bool upper) | 
|  | { | 
|  | bool is_casefolded = ovl_dentry_casefolded(path->dentry); | 
|  | struct ovl_fs_context *ctx = fc->fs_private; | 
|  | struct ovl_fs *ofs = fc->s_fs_info; | 
|  |  | 
|  | if (!d_is_dir(path->dentry)) | 
|  | return invalfc(fc, "%s is not a directory", name); | 
|  |  | 
|  | /* | 
|  | * Allow filesystems that are case-folding capable but deny composing | 
|  | * ovl stack from inconsistent case-folded directories. | 
|  | */ | 
|  | if (!ctx->casefold_set) { | 
|  | ofs->casefold = is_casefolded; | 
|  | ctx->casefold_set = true; | 
|  | } | 
|  |  | 
|  | if (ofs->casefold != is_casefolded) { | 
|  | return invalfc(fc, "case-%ssensitive directory on %s is inconsistent", | 
|  | is_casefolded ? "in" : "", name); | 
|  | } | 
|  |  | 
|  | if (ovl_dentry_weird(path->dentry)) | 
|  | return invalfc(fc, "filesystem on %s not supported", name); | 
|  |  | 
|  | /* | 
|  | * Check whether upper path is read-only here to report failures | 
|  | * early. Don't forget to recheck when the superblock is created | 
|  | * as the mount attributes could change. | 
|  | */ | 
|  | if (upper) { | 
|  | if (path->dentry->d_flags & DCACHE_OP_REAL) | 
|  | return invalfc(fc, "filesystem on %s not supported as upperdir", name); | 
|  | if (__mnt_is_readonly(path->mnt)) | 
|  | return invalfc(fc, "filesystem on %s is read-only", name); | 
|  | } else { | 
|  | if (ctx->lowerdir_all && layer != Opt_lowerdir) | 
|  | return invalfc(fc, "lowerdir+ and datadir+ cannot follow lowerdir"); | 
|  | if (ctx->nr_data && layer == Opt_lowerdir_add) | 
|  | return invalfc(fc, "regular lower layers cannot follow data layers"); | 
|  | if (ctx->nr == OVL_MAX_STACK) | 
|  | return invalfc(fc, "too many lower directories, limit is %d", | 
|  | OVL_MAX_STACK); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ovl_ctx_realloc_lower(struct fs_context *fc) | 
|  | { | 
|  | struct ovl_fs_context *ctx = fc->fs_private; | 
|  | struct ovl_fs_context_layer *l; | 
|  | size_t nr; | 
|  |  | 
|  | if (ctx->nr < ctx->capacity) | 
|  | return 0; | 
|  |  | 
|  | nr = min_t(size_t, max(4096 / sizeof(*l), ctx->capacity * 2), | 
|  | OVL_MAX_STACK); | 
|  | l = krealloc_array(ctx->lower, nr, sizeof(*l), GFP_KERNEL_ACCOUNT); | 
|  | if (!l) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ctx->lower = l; | 
|  | ctx->capacity = nr; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer, | 
|  | struct path *path, char **pname) | 
|  | { | 
|  | struct ovl_fs *ofs = fc->s_fs_info; | 
|  | struct ovl_config *config = &ofs->config; | 
|  | struct ovl_fs_context *ctx = fc->fs_private; | 
|  | struct ovl_fs_context_layer *l; | 
|  |  | 
|  | switch (layer) { | 
|  | case Opt_workdir: | 
|  | swap(config->workdir, *pname); | 
|  | swap(ctx->work, *path); | 
|  | break; | 
|  | case Opt_upperdir: | 
|  | swap(config->upperdir, *pname); | 
|  | swap(ctx->upper, *path); | 
|  | break; | 
|  | case Opt_datadir_add: | 
|  | ctx->nr_data++; | 
|  | fallthrough; | 
|  | case Opt_lowerdir: | 
|  | fallthrough; | 
|  | case Opt_lowerdir_add: | 
|  | WARN_ON(ctx->nr >= ctx->capacity); | 
|  | l = &ctx->lower[ctx->nr++]; | 
|  | memset(l, 0, sizeof(*l)); | 
|  | swap(l->name, *pname); | 
|  | swap(l->path, *path); | 
|  | break; | 
|  | default: | 
|  | WARN_ON(1); | 
|  | } | 
|  | } | 
|  |  | 
|  | static inline bool is_upper_layer(enum ovl_opt layer) | 
|  | { | 
|  | return layer == Opt_upperdir || layer == Opt_workdir; | 
|  | } | 
|  |  | 
|  | /* Handle non-file descriptor-based layer options that require path lookup. */ | 
|  | static inline int ovl_kern_path(const char *layer_name, struct path *layer_path, | 
|  | enum ovl_opt layer) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | switch (layer) { | 
|  | case Opt_upperdir: | 
|  | fallthrough; | 
|  | case Opt_workdir: | 
|  | fallthrough; | 
|  | case Opt_lowerdir: | 
|  | err = ovl_mount_dir(layer_name, layer_path); | 
|  | break; | 
|  | case Opt_lowerdir_add: | 
|  | fallthrough; | 
|  | case Opt_datadir_add: | 
|  | err = ovl_mount_dir_noesc(layer_name, layer_path); | 
|  | break; | 
|  | default: | 
|  | WARN_ON_ONCE(true); | 
|  | err = -EINVAL; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int ovl_do_parse_layer(struct fs_context *fc, const char *layer_name, | 
|  | struct path *layer_path, enum ovl_opt layer) | 
|  | { | 
|  | char *name __free(kfree) = kstrdup(layer_name, GFP_KERNEL); | 
|  | bool upper; | 
|  | int err = 0; | 
|  |  | 
|  | if (!name) | 
|  | return -ENOMEM; | 
|  |  | 
|  | upper = is_upper_layer(layer); | 
|  | err = ovl_mount_dir_check(fc, layer_path, layer, name, upper); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | if (!upper) { | 
|  | err = ovl_ctx_realloc_lower(fc); | 
|  | if (err) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Store the user provided path string in ctx to show in mountinfo */ | 
|  | ovl_add_layer(fc, layer, layer_path, &name); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param, | 
|  | enum ovl_opt layer) | 
|  | { | 
|  | struct path layer_path __free(path_put) = {}; | 
|  | int err = 0; | 
|  |  | 
|  | switch (param->type) { | 
|  | case fs_value_is_string: | 
|  | err = ovl_kern_path(param->string, &layer_path, layer); | 
|  | if (err) | 
|  | return err; | 
|  | err = ovl_do_parse_layer(fc, param->string, &layer_path, layer); | 
|  | break; | 
|  | case fs_value_is_file: { | 
|  | char *buf __free(kfree); | 
|  | char *layer_name; | 
|  |  | 
|  | buf = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT); | 
|  | if (!buf) | 
|  | return -ENOMEM; | 
|  |  | 
|  | layer_path = param->file->f_path; | 
|  | path_get(&layer_path); | 
|  |  | 
|  | layer_name = d_path(&layer_path, buf, PATH_MAX); | 
|  | if (IS_ERR(layer_name)) | 
|  | return PTR_ERR(layer_name); | 
|  |  | 
|  | err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | WARN_ON_ONCE(true); | 
|  | err = -EINVAL; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx) | 
|  | { | 
|  | struct ovl_fs_context_layer *l = ctx->lower; | 
|  |  | 
|  | // Reset old user provided lowerdir string | 
|  | kfree(ctx->lowerdir_all); | 
|  | ctx->lowerdir_all = NULL; | 
|  |  | 
|  | for (size_t nr = 0; nr < ctx->nr; nr++, l++) { | 
|  | path_put(&l->path); | 
|  | kfree(l->name); | 
|  | l->name = NULL; | 
|  | } | 
|  | ctx->nr = 0; | 
|  | ctx->nr_data = 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Parse lowerdir= mount option: | 
|  | * | 
|  | * e.g.: lowerdir=/lower1:/lower2:/lower3::/data1::/data2 | 
|  | *     Set "/lower1", "/lower2", and "/lower3" as lower layers and | 
|  | *     "/data1" and "/data2" as data lower layers. Any existing lower | 
|  | *     layers are replaced. | 
|  | */ | 
|  | static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc) | 
|  | { | 
|  | int err; | 
|  | struct ovl_fs_context *ctx = fc->fs_private; | 
|  | char *dup = NULL, *iter; | 
|  | ssize_t nr_lower, nr; | 
|  | bool data_layer = false; | 
|  |  | 
|  | /* | 
|  | * Ensure we're backwards compatible with mount(2) | 
|  | * by allowing relative paths. | 
|  | */ | 
|  |  | 
|  | /* drop all existing lower layers */ | 
|  | ovl_reset_lowerdirs(ctx); | 
|  |  | 
|  | if (!*name) | 
|  | return 0; | 
|  |  | 
|  | if (*name == ':') { | 
|  | pr_err("cannot append lower layer\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | // Store user provided lowerdir string to show in mount options | 
|  | ctx->lowerdir_all = kstrdup(name, GFP_KERNEL); | 
|  | if (!ctx->lowerdir_all) | 
|  | return -ENOMEM; | 
|  |  | 
|  | dup = kstrdup(name, GFP_KERNEL); | 
|  | if (!dup) | 
|  | return -ENOMEM; | 
|  |  | 
|  | err = -EINVAL; | 
|  | nr_lower = ovl_parse_param_split_lowerdirs(dup); | 
|  | if (nr_lower < 0) | 
|  | goto out_err; | 
|  |  | 
|  | if (nr_lower > OVL_MAX_STACK) { | 
|  | pr_err("too many lower directories, limit is %d\n", OVL_MAX_STACK); | 
|  | goto out_err; | 
|  | } | 
|  |  | 
|  | iter = dup; | 
|  | for (nr = 0; nr < nr_lower; nr++) { | 
|  | struct path path __free(path_put) = {}; | 
|  |  | 
|  | err = ovl_kern_path(iter, &path, Opt_lowerdir); | 
|  | if (err) | 
|  | goto out_err; | 
|  |  | 
|  | err = ovl_do_parse_layer(fc, iter, &path, Opt_lowerdir); | 
|  | if (err) | 
|  | goto out_err; | 
|  |  | 
|  | if (data_layer) | 
|  | ctx->nr_data++; | 
|  |  | 
|  | /* Calling strchr() again would overrun. */ | 
|  | if (ctx->nr == nr_lower) | 
|  | break; | 
|  |  | 
|  | err = -EINVAL; | 
|  | iter = strchr(iter, '\0') + 1; | 
|  | if (*iter) { | 
|  | /* | 
|  | * This is a regular layer so we require that | 
|  | * there are no data layers. | 
|  | */ | 
|  | if (ctx->nr_data > 0) { | 
|  | pr_err("regular lower layers cannot follow data lower layers\n"); | 
|  | goto out_err; | 
|  | } | 
|  |  | 
|  | data_layer = false; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* This is a data lower layer. */ | 
|  | data_layer = true; | 
|  | iter++; | 
|  | } | 
|  | kfree(dup); | 
|  | return 0; | 
|  |  | 
|  | out_err: | 
|  | kfree(dup); | 
|  |  | 
|  | /* Intentionally don't realloc to a smaller size. */ | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param) | 
|  | { | 
|  | int err = 0; | 
|  | struct fs_parse_result result; | 
|  | struct ovl_fs *ofs = fc->s_fs_info; | 
|  | struct ovl_config *config = &ofs->config; | 
|  | struct ovl_fs_context *ctx = fc->fs_private; | 
|  | int opt; | 
|  |  | 
|  | if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) { | 
|  | /* | 
|  | * On remount overlayfs has always ignored all mount | 
|  | * options no matter if malformed or not so for | 
|  | * backwards compatibility we do the same here. | 
|  | */ | 
|  | if (fc->oldapi) | 
|  | return 0; | 
|  |  | 
|  | /* | 
|  | * Give us the freedom to allow changing mount options | 
|  | * with the new mount api in the future. So instead of | 
|  | * silently ignoring everything we report a proper | 
|  | * error. This is only visible for users of the new | 
|  | * mount api. | 
|  | */ | 
|  | return invalfc(fc, "No changes allowed in reconfigure"); | 
|  | } | 
|  |  | 
|  | opt = fs_parse(fc, ovl_parameter_spec, param, &result); | 
|  | if (opt < 0) | 
|  | return opt; | 
|  |  | 
|  | switch (opt) { | 
|  | case Opt_lowerdir: | 
|  | err = ovl_parse_param_lowerdir(param->string, fc); | 
|  | break; | 
|  | case Opt_lowerdir_add: | 
|  | case Opt_datadir_add: | 
|  | case Opt_upperdir: | 
|  | case Opt_workdir: | 
|  | err = ovl_parse_layer(fc, param, opt); | 
|  | break; | 
|  | case Opt_default_permissions: | 
|  | config->default_permissions = true; | 
|  | break; | 
|  | case Opt_redirect_dir: | 
|  | config->redirect_mode = result.uint_32; | 
|  | if (config->redirect_mode == OVL_REDIRECT_OFF) { | 
|  | config->redirect_mode = ovl_redirect_always_follow ? | 
|  | OVL_REDIRECT_FOLLOW : | 
|  | OVL_REDIRECT_NOFOLLOW; | 
|  | } | 
|  | ctx->set.redirect = true; | 
|  | break; | 
|  | case Opt_index: | 
|  | config->index = result.uint_32; | 
|  | ctx->set.index = true; | 
|  | break; | 
|  | case Opt_uuid: | 
|  | config->uuid = result.uint_32; | 
|  | break; | 
|  | case Opt_nfs_export: | 
|  | config->nfs_export = result.uint_32; | 
|  | ctx->set.nfs_export = true; | 
|  | break; | 
|  | case Opt_xino: | 
|  | config->xino = result.uint_32; | 
|  | break; | 
|  | case Opt_metacopy: | 
|  | config->metacopy = result.uint_32; | 
|  | ctx->set.metacopy = true; | 
|  | break; | 
|  | case Opt_verity: | 
|  | config->verity_mode = result.uint_32; | 
|  | break; | 
|  | case Opt_volatile: | 
|  | config->ovl_volatile = true; | 
|  | break; | 
|  | case Opt_userxattr: | 
|  | config->userxattr = true; | 
|  | break; | 
|  | case Opt_override_creds: { | 
|  | const struct cred *cred = NULL; | 
|  |  | 
|  | if (result.negated) { | 
|  | swap(cred, ofs->creator_cred); | 
|  | put_cred(cred); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!current_in_userns(fc->user_ns)) { | 
|  | err = -EINVAL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | cred = prepare_creds(); | 
|  | if (cred) | 
|  | swap(cred, ofs->creator_cred); | 
|  | else | 
|  | err = -ENOMEM; | 
|  |  | 
|  | put_cred(cred); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | pr_err("unrecognized mount option \"%s\" or missing value\n", | 
|  | param->key); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int ovl_get_tree(struct fs_context *fc) | 
|  | { | 
|  | return get_tree_nodev(fc, ovl_fill_super); | 
|  | } | 
|  |  | 
|  | static inline void ovl_fs_context_free(struct ovl_fs_context *ctx) | 
|  | { | 
|  | ovl_reset_lowerdirs(ctx); | 
|  | path_put(&ctx->upper); | 
|  | path_put(&ctx->work); | 
|  | kfree(ctx->lower); | 
|  | kfree(ctx); | 
|  | } | 
|  |  | 
|  | static void ovl_free(struct fs_context *fc) | 
|  | { | 
|  | struct ovl_fs *ofs = fc->s_fs_info; | 
|  | struct ovl_fs_context *ctx = fc->fs_private; | 
|  |  | 
|  | /* | 
|  | * ofs is stored in the fs_context when it is initialized. | 
|  | * ofs is transferred to the superblock on a successful mount, | 
|  | * but if an error occurs before the transfer we have to free | 
|  | * it here. | 
|  | */ | 
|  | if (ofs) | 
|  | ovl_free_fs(ofs); | 
|  |  | 
|  | if (ctx) | 
|  | ovl_fs_context_free(ctx); | 
|  | } | 
|  |  | 
|  | static int ovl_reconfigure(struct fs_context *fc) | 
|  | { | 
|  | struct super_block *sb = fc->root->d_sb; | 
|  | struct ovl_fs *ofs = OVL_FS(sb); | 
|  | struct super_block *upper_sb; | 
|  | int ret = 0; | 
|  |  | 
|  | if (!(fc->sb_flags & SB_RDONLY) && ovl_force_readonly(ofs)) | 
|  | return -EROFS; | 
|  |  | 
|  | if (fc->sb_flags & SB_RDONLY && !sb_rdonly(sb)) { | 
|  | upper_sb = ovl_upper_mnt(ofs)->mnt_sb; | 
|  | if (ovl_should_sync(ofs)) { | 
|  | down_read(&upper_sb->s_umount); | 
|  | ret = sync_filesystem(upper_sb); | 
|  | up_read(&upper_sb->s_umount); | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct fs_context_operations ovl_context_ops = { | 
|  | .parse_monolithic = ovl_parse_monolithic, | 
|  | .parse_param = ovl_parse_param, | 
|  | .get_tree    = ovl_get_tree, | 
|  | .reconfigure = ovl_reconfigure, | 
|  | .free        = ovl_free, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * This is called during fsopen() and will record the user namespace of | 
|  | * the caller in fc->user_ns since we've raised FS_USERNS_MOUNT. We'll | 
|  | * need it when we actually create the superblock to verify that the | 
|  | * process creating the superblock is in the same user namespace as | 
|  | * process that called fsopen(). | 
|  | */ | 
|  | int ovl_init_fs_context(struct fs_context *fc) | 
|  | { | 
|  | struct ovl_fs_context *ctx; | 
|  | struct ovl_fs *ofs; | 
|  |  | 
|  | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL_ACCOUNT); | 
|  | if (!ctx) | 
|  | return -ENOMEM; | 
|  |  | 
|  | /* | 
|  | * By default we allocate for three lower layers. It's likely | 
|  | * that it'll cover most users. | 
|  | */ | 
|  | ctx->lower = kmalloc_array(3, sizeof(*ctx->lower), GFP_KERNEL_ACCOUNT); | 
|  | if (!ctx->lower) | 
|  | goto out_err; | 
|  | ctx->capacity = 3; | 
|  |  | 
|  | ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL); | 
|  | if (!ofs) | 
|  | goto out_err; | 
|  |  | 
|  | ofs->config.redirect_mode	= ovl_redirect_mode_def(); | 
|  | ofs->config.index		= ovl_index_def; | 
|  | ofs->config.uuid		= ovl_uuid_def(); | 
|  | ofs->config.nfs_export		= ovl_nfs_export_def; | 
|  | ofs->config.xino		= ovl_xino_def(); | 
|  | ofs->config.metacopy		= ovl_metacopy_def; | 
|  |  | 
|  | fc->s_fs_info		= ofs; | 
|  | fc->fs_private		= ctx; | 
|  | fc->ops			= &ovl_context_ops; | 
|  |  | 
|  | mutex_init(&ofs->whiteout_lock); | 
|  | return 0; | 
|  |  | 
|  | out_err: | 
|  | ovl_fs_context_free(ctx); | 
|  | return -ENOMEM; | 
|  |  | 
|  | } | 
|  |  | 
|  | void ovl_free_fs(struct ovl_fs *ofs) | 
|  | { | 
|  | struct vfsmount **mounts; | 
|  | unsigned i; | 
|  |  | 
|  | iput(ofs->workbasedir_trap); | 
|  | iput(ofs->workdir_trap); | 
|  | dput(ofs->whiteout); | 
|  | dput(ofs->workdir); | 
|  | if (ofs->workdir_locked) | 
|  | ovl_inuse_unlock(ofs->workbasedir); | 
|  | dput(ofs->workbasedir); | 
|  | if (ofs->upperdir_locked) | 
|  | ovl_inuse_unlock(ovl_upper_mnt(ofs)->mnt_root); | 
|  |  | 
|  | /* 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; | 
|  | } | 
|  | kern_unmount_array(mounts, ofs->numlayer); | 
|  | kfree(ofs->layers); | 
|  | for (i = 0; i < ofs->numfs; i++) | 
|  | 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) | 
|  | put_cred(ofs->creator_cred); | 
|  | kfree(ofs); | 
|  | } | 
|  |  | 
|  | int ovl_fs_params_verify(const struct ovl_fs_context *ctx, | 
|  | struct ovl_config *config) | 
|  | { | 
|  | struct ovl_opt_set set = ctx->set; | 
|  |  | 
|  | /* Workdir/index are useless in non-upper mount */ | 
|  | if (!config->upperdir) { | 
|  | if (config->workdir) { | 
|  | pr_info("option \"workdir=%s\" is useless in a non-upper mount, ignore\n", | 
|  | config->workdir); | 
|  | kfree(config->workdir); | 
|  | config->workdir = NULL; | 
|  | } | 
|  | if (config->index && set.index) { | 
|  | pr_info("option \"index=on\" is useless in a non-upper mount, ignore\n"); | 
|  | set.index = false; | 
|  | } | 
|  | config->index = false; | 
|  | } | 
|  |  | 
|  | if (!config->upperdir && config->ovl_volatile) { | 
|  | pr_info("option \"volatile\" is meaningless in a non-upper mount, ignoring it.\n"); | 
|  | config->ovl_volatile = false; | 
|  | } | 
|  |  | 
|  | if (!config->upperdir && config->uuid == OVL_UUID_ON) { | 
|  | pr_info("option \"uuid=on\" requires an upper fs, falling back to uuid=null.\n"); | 
|  | config->uuid = OVL_UUID_NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * This is to make the logic below simpler.  It doesn't make any other | 
|  | * difference, since redirect_dir=on is only used for upper. | 
|  | */ | 
|  | if (!config->upperdir && config->redirect_mode == OVL_REDIRECT_FOLLOW) | 
|  | config->redirect_mode = OVL_REDIRECT_ON; | 
|  |  | 
|  | /* metacopy -> redirect_dir dependency */ | 
|  | if (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) { | 
|  | if (set.metacopy && set.redirect) { | 
|  | pr_err("conflicting options: metacopy=on,redirect_dir=%s\n", | 
|  | ovl_redirect_mode(config)); | 
|  | return -EINVAL; | 
|  | } | 
|  | if (set.redirect) { | 
|  | /* | 
|  | * There was an explicit redirect_dir=... that resulted | 
|  | * in this conflict. | 
|  | */ | 
|  | pr_info("disabling metacopy due to redirect_dir=%s\n", | 
|  | ovl_redirect_mode(config)); | 
|  | config->metacopy = false; | 
|  | } else { | 
|  | /* Automatically enable redirect otherwise. */ | 
|  | config->redirect_mode = OVL_REDIRECT_ON; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Resolve nfs_export -> index dependency */ | 
|  | if (config->nfs_export && !config->index) { | 
|  | if (!config->upperdir && | 
|  | config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { | 
|  | pr_info("NFS export requires \"redirect_dir=nofollow\" on non-upper mount, falling back to nfs_export=off.\n"); | 
|  | config->nfs_export = false; | 
|  | } else if (set.nfs_export && set.index) { | 
|  | pr_err("conflicting options: nfs_export=on,index=off\n"); | 
|  | return -EINVAL; | 
|  | } else if (set.index) { | 
|  | /* | 
|  | * There was an explicit index=off that resulted | 
|  | * in this conflict. | 
|  | */ | 
|  | pr_info("disabling nfs_export due to index=off\n"); | 
|  | config->nfs_export = false; | 
|  | } else { | 
|  | /* Automatically enable index otherwise. */ | 
|  | config->index = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Resolve nfs_export -> !metacopy && !verity dependency */ | 
|  | if (config->nfs_export && config->metacopy) { | 
|  | if (set.nfs_export && set.metacopy) { | 
|  | pr_err("conflicting options: nfs_export=on,metacopy=on\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | if (set.metacopy) { | 
|  | /* | 
|  | * There was an explicit metacopy=on that resulted | 
|  | * in this conflict. | 
|  | */ | 
|  | pr_info("disabling nfs_export due to metacopy=on\n"); | 
|  | config->nfs_export = false; | 
|  | } else if (config->verity_mode) { | 
|  | /* | 
|  | * There was an explicit verity=.. that resulted | 
|  | * in this conflict. | 
|  | */ | 
|  | pr_info("disabling nfs_export due to verity=%s\n", | 
|  | ovl_verity_mode(config)); | 
|  | config->nfs_export = false; | 
|  | } else { | 
|  | /* | 
|  | * There was an explicit nfs_export=on that resulted | 
|  | * in this conflict. | 
|  | */ | 
|  | pr_info("disabling metacopy due to nfs_export=on\n"); | 
|  | config->metacopy = false; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Resolve userxattr -> !redirect && !metacopy dependency */ | 
|  | if (config->userxattr) { | 
|  | if (set.redirect && | 
|  | config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { | 
|  | pr_err("conflicting options: userxattr,redirect_dir=%s\n", | 
|  | ovl_redirect_mode(config)); | 
|  | return -EINVAL; | 
|  | } | 
|  | if (config->metacopy && set.metacopy) { | 
|  | pr_err("conflicting options: userxattr,metacopy=on\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | /* | 
|  | * Silently disable default setting of redirect and metacopy. | 
|  | * This shall be the default in the future as well: these | 
|  | * options must be explicitly enabled if used together with | 
|  | * userxattr. | 
|  | */ | 
|  | config->redirect_mode = OVL_REDIRECT_NOFOLLOW; | 
|  | config->metacopy = false; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Fail if we don't have trusted xattr capability and a feature was | 
|  | * explicitly requested that requires them. | 
|  | */ | 
|  | if (!config->userxattr && !capable(CAP_SYS_ADMIN)) { | 
|  | if (set.redirect && | 
|  | config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { | 
|  | pr_err("redirect_dir requires permission to access trusted xattrs\n"); | 
|  | return -EPERM; | 
|  | } | 
|  | if (config->metacopy && set.metacopy) { | 
|  | pr_err("metacopy requires permission to access trusted xattrs\n"); | 
|  | return -EPERM; | 
|  | } | 
|  | if (config->verity_mode) { | 
|  | pr_err("verity requires permission to access trusted xattrs\n"); | 
|  | return -EPERM; | 
|  | } | 
|  | if (ctx->nr_data > 0) { | 
|  | pr_err("lower data-only dirs require permission to access trusted xattrs\n"); | 
|  | return -EPERM; | 
|  | } | 
|  | /* | 
|  | * Other xattr-dependent features should be disabled without | 
|  | * great disturbance to the user in ovl_make_workdir(). | 
|  | */ | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ovl_show_options | 
|  | * @m: the seq_file handle | 
|  | * @dentry: The dentry to query | 
|  | * | 
|  | * Prints the mount options for a given superblock. | 
|  | * Returns zero; does not fail. | 
|  | */ | 
|  | 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, nr_lower = 0; | 
|  | char **lowerdirs = ofs->config.lowerdirs; | 
|  |  | 
|  | /* | 
|  | * lowerdirs[0] holds the colon separated list that user provided | 
|  | * with lowerdir mount option. | 
|  | * lowerdirs[1..numlayer] hold the lowerdir paths that were added | 
|  | * using the lowerdir+ and datadir+ mount options. | 
|  | * For now, we do not allow mixing the legacy lowerdir mount option | 
|  | * with the new lowerdir+ and datadir+ mount options. | 
|  | */ | 
|  | if (lowerdirs[0]) { | 
|  | seq_show_option(m, "lowerdir", lowerdirs[0]); | 
|  | } else { | 
|  | nr_lower = ofs->numlayer; | 
|  | nr_merged_lower = nr_lower - ofs->numdatalayer; | 
|  | } | 
|  | for (nr = 1; nr < nr_lower; nr++) { | 
|  | if (nr < nr_merged_lower) | 
|  | seq_show_option(m, "lowerdir+", lowerdirs[nr]); | 
|  | else | 
|  | seq_show_option(m, "datadir+", lowerdirs[nr]); | 
|  | } | 
|  | if (ofs->config.upperdir) { | 
|  | seq_show_option(m, "upperdir", ofs->config.upperdir); | 
|  | seq_show_option(m, "workdir", ofs->config.workdir); | 
|  | } | 
|  | if (ofs->config.default_permissions) | 
|  | seq_puts(m, ",default_permissions"); | 
|  | if (ofs->config.redirect_mode != ovl_redirect_mode_def()) | 
|  | seq_printf(m, ",redirect_dir=%s", | 
|  | ovl_redirect_mode(&ofs->config)); | 
|  | if (ofs->config.index != ovl_index_def) | 
|  | seq_printf(m, ",index=%s", str_on_off(ofs->config.index)); | 
|  | if (ofs->config.uuid != ovl_uuid_def()) | 
|  | seq_printf(m, ",uuid=%s", ovl_uuid_mode(&ofs->config)); | 
|  | if (ofs->config.nfs_export != ovl_nfs_export_def) | 
|  | seq_printf(m, ",nfs_export=%s", | 
|  | str_on_off(ofs->config.nfs_export)); | 
|  | if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(ofs)) | 
|  | seq_printf(m, ",xino=%s", ovl_xino_mode(&ofs->config)); | 
|  | if (ofs->config.metacopy != ovl_metacopy_def) | 
|  | seq_printf(m, ",metacopy=%s", str_on_off(ofs->config.metacopy)); | 
|  | if (ofs->config.ovl_volatile) | 
|  | seq_puts(m, ",volatile"); | 
|  | if (ofs->config.userxattr) | 
|  | seq_puts(m, ",userxattr"); | 
|  | if (ofs->config.verity_mode != ovl_verity_mode_def()) | 
|  | seq_printf(m, ",verity=%s", | 
|  | ovl_verity_mode(&ofs->config)); | 
|  | return 0; | 
|  | } |