1104 lines · 27.4 KB · C
RawDownload
// 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_fsync,	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;} static const struct constant_table ovl_parameter_fsync[] = {	{ "volatile",	OVL_FSYNC_VOLATILE },	{ "auto",	OVL_FSYNC_AUTO     },	{ "strict",	OVL_FSYNC_STRICT   },	{}}; static const char *ovl_fsync_mode(struct ovl_config *config){	return ovl_parameter_fsync[config->fsync_mode].name;} static int ovl_fsync_mode_def(void){	return OVL_FSYNC_AUTO;} 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_enum("fsync",               Opt_fsync, ovl_parameter_fsync),	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);

Lines 1–500 of 1104.