diff options
Diffstat (limited to 'fs/overlayfs')
| -rw-r--r-- | fs/overlayfs/Kconfig | 1 | ||||
| -rw-r--r-- | fs/overlayfs/copy_up.c | 57 | ||||
| -rw-r--r-- | fs/overlayfs/dir.c | 61 | ||||
| -rw-r--r-- | fs/overlayfs/inode.c | 12 | ||||
| -rw-r--r-- | fs/overlayfs/namei.c | 16 | ||||
| -rw-r--r-- | fs/overlayfs/overlayfs.h | 16 | ||||
| -rw-r--r-- | fs/overlayfs/ovl_entry.h | 2 | ||||
| -rw-r--r-- | fs/overlayfs/super.c | 18 | ||||
| -rw-r--r-- | fs/overlayfs/util.c | 72 | 
9 files changed, 197 insertions, 58 deletions
| diff --git a/fs/overlayfs/Kconfig b/fs/overlayfs/Kconfig index 0daac5112f7a..c0c9683934b7 100644 --- a/fs/overlayfs/Kconfig +++ b/fs/overlayfs/Kconfig @@ -1,5 +1,6 @@  config OVERLAY_FS  	tristate "Overlay filesystem support" +	select EXPORTFS  	help  	  An overlay filesystem combines two filesystems - an 'upper' filesystem  	  and a 'lower' filesystem.  When a name exists in both filesystems, the diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 9008ab9fbd2e..a2a65120c9d0 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -300,7 +300,11 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower,  			return PTR_ERR(fh);  	} -	err = ovl_do_setxattr(upper, OVL_XATTR_ORIGIN, fh, fh ? fh->len : 0, 0); +	/* +	 * Do not fail when upper doesn't support xattrs. +	 */ +	err = ovl_check_setxattr(dentry, upper, OVL_XATTR_ORIGIN, fh, +				 fh ? fh->len : 0, 0);  	kfree(fh);  	return err; @@ -326,15 +330,9 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,  		.link = link  	}; -	upper = lookup_one_len(dentry->d_name.name, upperdir, -			       dentry->d_name.len); -	err = PTR_ERR(upper); -	if (IS_ERR(upper)) -		goto out; -  	err = security_inode_copy_up(dentry, &new_creds);  	if (err < 0) -		goto out1; +		goto out;  	if (new_creds)  		old_creds = override_creds(new_creds); @@ -342,13 +340,14 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,  	if (tmpfile)  		temp = ovl_do_tmpfile(upperdir, stat->mode);  	else -		temp = ovl_lookup_temp(workdir, dentry); -	err = PTR_ERR(temp); -	if (IS_ERR(temp)) -		goto out1; - +		temp = ovl_lookup_temp(workdir);  	err = 0; -	if (!tmpfile) +	if (IS_ERR(temp)) { +		err = PTR_ERR(temp); +		temp = NULL; +	} + +	if (!err && !tmpfile)  		err = ovl_create_real(wdir, temp, &cattr, NULL, true);  	if (new_creds) { @@ -357,7 +356,7 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,  	}  	if (err) -		goto out2; +		goto out;  	if (S_ISREG(stat->mode)) {  		struct path upperpath; @@ -393,10 +392,23 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,  	/*  	 * Store identifier of lower inode in upper inode xattr to  	 * allow lookup of the copy up origin inode. +	 * +	 * Don't set origin when we are breaking the association with a lower +	 * hard link.  	 */ -	err = ovl_set_origin(dentry, lowerpath->dentry, temp); -	if (err) +	if (S_ISDIR(stat->mode) || stat->nlink == 1) { +		err = ovl_set_origin(dentry, lowerpath->dentry, temp); +		if (err) +			goto out_cleanup; +	} + +	upper = lookup_one_len(dentry->d_name.name, upperdir, +			       dentry->d_name.len); +	if (IS_ERR(upper)) { +		err = PTR_ERR(upper); +		upper = NULL;  		goto out_cleanup; +	}  	if (tmpfile)  		err = ovl_do_link(temp, udir, upper, true); @@ -411,17 +423,15 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,  	/* Restore timestamps on parent (best effort) */  	ovl_set_timestamps(upperdir, pstat); -out2: +out:  	dput(temp); -out1:  	dput(upper); -out:  	return err;  out_cleanup:  	if (!tmpfile)  		ovl_cleanup(wdir, temp); -	goto out2; +	goto out;  }  /* @@ -454,6 +464,11 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,  	ovl_path_upper(parent, &parentpath);  	upperdir = parentpath.dentry; +	/* Mark parent "impure" because it may now contain non-pure upper */ +	err = ovl_set_impure(parent, upperdir); +	if (err) +		return err; +  	err = vfs_getattr(&parentpath, &pstat,  			  STATX_ATIME | STATX_MTIME, AT_STATX_SYNC_AS_STAT);  	if (err) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 723b98b90698..a63a71656e9b 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -41,7 +41,7 @@ void ovl_cleanup(struct inode *wdir, struct dentry *wdentry)  	}  } -struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry) +struct dentry *ovl_lookup_temp(struct dentry *workdir)  {  	struct dentry *temp;  	char name[20]; @@ -68,7 +68,7 @@ static struct dentry *ovl_whiteout(struct dentry *workdir,  	struct dentry *whiteout;  	struct inode *wdir = workdir->d_inode; -	whiteout = ovl_lookup_temp(workdir, dentry); +	whiteout = ovl_lookup_temp(workdir);  	if (IS_ERR(whiteout))  		return whiteout; @@ -127,17 +127,28 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry,  	return err;  } -static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry) +static int ovl_set_opaque_xerr(struct dentry *dentry, struct dentry *upper, +			       int xerr)  {  	int err; -	err = ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0); +	err = ovl_check_setxattr(dentry, upper, OVL_XATTR_OPAQUE, "y", 1, xerr);  	if (!err)  		ovl_dentry_set_opaque(dentry);  	return err;  } +static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry) +{ +	/* +	 * Fail with -EIO when trying to create opaque dir and upper doesn't +	 * support xattrs. ovl_rename() calls ovl_set_opaque_xerr(-EXDEV) to +	 * return a specific error for noxattr case. +	 */ +	return ovl_set_opaque_xerr(dentry, upperdentry, -EIO); +} +  /* Common operations required to be done after creation of file on upper */  static void ovl_instantiate(struct dentry *dentry, struct inode *inode,  			    struct dentry *newdentry, bool hardlink) @@ -162,6 +173,11 @@ static bool ovl_type_merge(struct dentry *dentry)  	return OVL_TYPE_MERGE(ovl_path_type(dentry));  } +static bool ovl_type_origin(struct dentry *dentry) +{ +	return OVL_TYPE_ORIGIN(ovl_path_type(dentry)); +} +  static int ovl_create_upper(struct dentry *dentry, struct inode *inode,  			    struct cattr *attr, struct dentry *hardlink)  { @@ -250,7 +266,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,  	if (upper->d_parent->d_inode != udir)  		goto out_unlock; -	opaquedir = ovl_lookup_temp(workdir, dentry); +	opaquedir = ovl_lookup_temp(workdir);  	err = PTR_ERR(opaquedir);  	if (IS_ERR(opaquedir))  		goto out_unlock; @@ -382,7 +398,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,  	if (err)  		goto out; -	newdentry = ovl_lookup_temp(workdir, dentry); +	newdentry = ovl_lookup_temp(workdir);  	err = PTR_ERR(newdentry);  	if (IS_ERR(newdentry))  		goto out_unlock; @@ -846,18 +862,16 @@ static int ovl_set_redirect(struct dentry *dentry, bool samedir)  	if (IS_ERR(redirect))  		return PTR_ERR(redirect); -	err = ovl_do_setxattr(ovl_dentry_upper(dentry), OVL_XATTR_REDIRECT, -			      redirect, strlen(redirect), 0); +	err = ovl_check_setxattr(dentry, ovl_dentry_upper(dentry), +				 OVL_XATTR_REDIRECT, +				 redirect, strlen(redirect), -EXDEV);  	if (!err) {  		spin_lock(&dentry->d_lock);  		ovl_dentry_set_redirect(dentry, redirect);  		spin_unlock(&dentry->d_lock);  	} else {  		kfree(redirect); -		if (err == -EOPNOTSUPP) -			ovl_clear_redirect_dir(dentry->d_sb); -		else -			pr_warn_ratelimited("overlay: failed to set redirect (%i)\n", err); +		pr_warn_ratelimited("overlay: failed to set redirect (%i)\n", err);  		/* Fall back to userspace copy-up */  		err = -EXDEV;  	} @@ -943,6 +957,25 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,  	old_upperdir = ovl_dentry_upper(old->d_parent);  	new_upperdir = ovl_dentry_upper(new->d_parent); +	if (!samedir) { +		/* +		 * When moving a merge dir or non-dir with copy up origin into +		 * a new parent, we are marking the new parent dir "impure". +		 * When ovl_iterate() iterates an "impure" upper dir, it will +		 * lookup the origin inodes of the entries to fill d_ino. +		 */ +		if (ovl_type_origin(old)) { +			err = ovl_set_impure(new->d_parent, new_upperdir); +			if (err) +				goto out_revert_creds; +		} +		if (!overwrite && ovl_type_origin(new)) { +			err = ovl_set_impure(old->d_parent, old_upperdir); +			if (err) +				goto out_revert_creds; +		} +	} +  	trap = lock_rename(new_upperdir, old_upperdir);  	olddentry = lookup_one_len(old->d_name.name, old_upperdir, @@ -992,7 +1025,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,  		if (ovl_type_merge_or_lower(old))  			err = ovl_set_redirect(old, samedir);  		else if (!old_opaque && ovl_type_merge(new->d_parent)) -			err = ovl_set_opaque(old, olddentry); +			err = ovl_set_opaque_xerr(old, olddentry, -EXDEV);  		if (err)  			goto out_dput;  	} @@ -1000,7 +1033,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,  		if (ovl_type_merge_or_lower(new))  			err = ovl_set_redirect(new, samedir);  		else if (!new_opaque && ovl_type_merge(old->d_parent)) -			err = ovl_set_opaque(new, newdentry); +			err = ovl_set_opaque_xerr(new, newdentry, -EXDEV);  		if (err)  			goto out_dput;  	} diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index ad9547f82da5..d613e2c41242 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -240,6 +240,16 @@ int ovl_xattr_get(struct dentry *dentry, const char *name,  	return res;  } +static bool ovl_can_list(const char *s) +{ +	/* List all non-trusted xatts */ +	if (strncmp(s, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) != 0) +		return true; + +	/* Never list trusted.overlay, list other trusted for superuser only */ +	return !ovl_is_private_xattr(s) && capable(CAP_SYS_ADMIN); +} +  ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)  {  	struct dentry *realdentry = ovl_dentry_real(dentry); @@ -263,7 +273,7 @@ ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)  			return -EIO;  		len -= slen; -		if (ovl_is_private_xattr(s)) { +		if (!ovl_can_list(s)) {  			res -= slen;  			memmove(s, s + slen, len);  		} else { diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index bad0f665a635..f3136c31e72a 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -169,17 +169,7 @@ invalid:  static bool ovl_is_opaquedir(struct dentry *dentry)  { -	int res; -	char val; - -	if (!d_is_dir(dentry)) -		return false; - -	res = vfs_getxattr(dentry, OVL_XATTR_OPAQUE, &val, 1); -	if (res == 1 && val == 'y') -		return true; - -	return false; +	return ovl_check_dir_xattr(dentry, OVL_XATTR_OPAQUE);  }  static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d, @@ -351,6 +341,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,  	unsigned int ctr = 0;  	struct inode *inode = NULL;  	bool upperopaque = false; +	bool upperimpure = false;  	char *upperredirect = NULL;  	struct dentry *this;  	unsigned int i; @@ -395,6 +386,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,  				poe = roe;  		}  		upperopaque = d.opaque; +		if (upperdentry && d.is_dir) +			upperimpure = ovl_is_impuredir(upperdentry);  	}  	if (!d.stop && poe->numlower) { @@ -463,6 +456,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,  	revert_creds(old_cred);  	oe->opaque = upperopaque; +	oe->impure = upperimpure;  	oe->redirect = upperredirect;  	oe->__upperdentry = upperdentry;  	memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index caa36cb9c46d..0623cebeefff 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -24,6 +24,7 @@ enum ovl_path_type {  #define OVL_XATTR_OPAQUE OVL_XATTR_PREFIX "opaque"  #define OVL_XATTR_REDIRECT OVL_XATTR_PREFIX "redirect"  #define OVL_XATTR_ORIGIN OVL_XATTR_PREFIX "origin" +#define OVL_XATTR_IMPURE OVL_XATTR_PREFIX "impure"  /*   * The tuple (fh,uuid) is a universal unique identifier for a copy up origin, @@ -203,10 +204,10 @@ struct dentry *ovl_dentry_real(struct dentry *dentry);  struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry);  void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache);  bool ovl_dentry_is_opaque(struct dentry *dentry); +bool ovl_dentry_is_impure(struct dentry *dentry);  bool ovl_dentry_is_whiteout(struct dentry *dentry);  void ovl_dentry_set_opaque(struct dentry *dentry);  bool ovl_redirect_dir(struct super_block *sb); -void ovl_clear_redirect_dir(struct super_block *sb);  const char *ovl_dentry_get_redirect(struct dentry *dentry);  void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect);  void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); @@ -219,6 +220,17 @@ bool ovl_is_whiteout(struct dentry *dentry);  struct file *ovl_path_open(struct path *path, int flags);  int ovl_copy_up_start(struct dentry *dentry);  void ovl_copy_up_end(struct dentry *dentry); +bool ovl_check_dir_xattr(struct dentry *dentry, const char *name); +int ovl_check_setxattr(struct dentry *dentry, struct dentry *upperdentry, +		       const char *name, const void *value, size_t size, +		       int xerr); +int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry); + +static inline bool ovl_is_impuredir(struct dentry *dentry) +{ +	return ovl_check_dir_xattr(dentry, OVL_XATTR_IMPURE); +} +  /* namei.c */  int ovl_path_next(int idx, struct dentry *dentry, struct path *path); @@ -263,7 +275,7 @@ static inline void ovl_copyattr(struct inode *from, struct inode *to)  /* dir.c */  extern const struct inode_operations ovl_dir_inode_operations; -struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry); +struct dentry *ovl_lookup_temp(struct dentry *workdir);  struct cattr {  	dev_t rdev;  	umode_t mode; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index b2023ddb8532..34bc4a9f5c61 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -28,6 +28,7 @@ struct ovl_fs {  	/* creds of process who forced instantiation of super block */  	const struct cred *creator_cred;  	bool tmpfile; +	bool noxattr;  	wait_queue_head_t copyup_wq;  	/* sb common to all layers */  	struct super_block *same_sb; @@ -42,6 +43,7 @@ struct ovl_entry {  			u64 version;  			const char *redirect;  			bool opaque; +			bool impure;  			bool copying;  		};  		struct rcu_head rcu; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 9828b7de8999..4882ffb37bae 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -891,6 +891,19 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)  				dput(temp);  			else  				pr_warn("overlayfs: upper fs does not support tmpfile.\n"); + +			/* +			 * Check if upper/work fs supports trusted.overlay.* +			 * xattr +			 */ +			err = ovl_do_setxattr(ufs->workdir, OVL_XATTR_OPAQUE, +					      "0", 1, 0); +			if (err) { +				ufs->noxattr = true; +				pr_warn("overlayfs: upper fs does not support xattr.\n"); +			} else { +				vfs_removexattr(ufs->workdir, OVL_XATTR_OPAQUE); +			}  		}  	} @@ -961,7 +974,10 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)  	path_put(&workpath);  	kfree(lowertmp); -	oe->__upperdentry = upperpath.dentry; +	if (upperpath.dentry) { +		oe->__upperdentry = upperpath.dentry; +		oe->impure = ovl_is_impuredir(upperpath.dentry); +	}  	for (i = 0; i < numlower; i++) {  		oe->lowerstack[i].dentry = stack[i].dentry;  		oe->lowerstack[i].mnt = ufs->lower_mnt[i]; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index cfdea47313a1..809048913889 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -175,6 +175,13 @@ bool ovl_dentry_is_opaque(struct dentry *dentry)  	return oe->opaque;  } +bool ovl_dentry_is_impure(struct dentry *dentry) +{ +	struct ovl_entry *oe = dentry->d_fsdata; + +	return oe->impure; +} +  bool ovl_dentry_is_whiteout(struct dentry *dentry)  {  	return !dentry->d_inode && ovl_dentry_is_opaque(dentry); @@ -191,14 +198,7 @@ bool ovl_redirect_dir(struct super_block *sb)  {  	struct ovl_fs *ofs = sb->s_fs_info; -	return ofs->config.redirect_dir; -} - -void ovl_clear_redirect_dir(struct super_block *sb) -{ -	struct ovl_fs *ofs = sb->s_fs_info; - -	ofs->config.redirect_dir = false; +	return ofs->config.redirect_dir && !ofs->noxattr;  }  const char *ovl_dentry_get_redirect(struct dentry *dentry) @@ -303,3 +303,59 @@ void ovl_copy_up_end(struct dentry *dentry)  	wake_up_locked(&ofs->copyup_wq);  	spin_unlock(&ofs->copyup_wq.lock);  } + +bool ovl_check_dir_xattr(struct dentry *dentry, const char *name) +{ +	int res; +	char val; + +	if (!d_is_dir(dentry)) +		return false; + +	res = vfs_getxattr(dentry, name, &val, 1); +	if (res == 1 && val == 'y') +		return true; + +	return false; +} + +int ovl_check_setxattr(struct dentry *dentry, struct dentry *upperdentry, +		       const char *name, const void *value, size_t size, +		       int xerr) +{ +	int err; +	struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + +	if (ofs->noxattr) +		return xerr; + +	err = ovl_do_setxattr(upperdentry, name, value, size, 0); + +	if (err == -EOPNOTSUPP) { +		pr_warn("overlayfs: cannot set %s xattr on upper\n", name); +		ofs->noxattr = true; +		return xerr; +	} + +	return err; +} + +int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry) +{ +	int err; +	struct ovl_entry *oe = dentry->d_fsdata; + +	if (oe->impure) +		return 0; + +	/* +	 * Do not fail when upper doesn't support xattrs. +	 * Upper inodes won't have origin nor redirect xattr anyway. +	 */ +	err = ovl_check_setxattr(dentry, upperdentry, OVL_XATTR_IMPURE, +				 "y", 1, 0); +	if (!err) +		oe->impure = true; + +	return err; +} |