Unverified Commit 5c875272 authored by NeilBrown's avatar NeilBrown Committed by Christian Brauner
Browse files

VFS/nfsd/ovl: introduce start_renaming() and end_renaming()



start_renaming() combines name lookup and locking to prepare for rename.
It is used when two names need to be looked up as in nfsd and overlayfs -
cases where one or both dentries are already available will be handled
separately.

__start_renaming() avoids the inode_permission check and hash
calculation and is suitable after filename_parentat() in do_renameat2().
It subsumes quite a bit of code from that function.

start_renaming() does calculate the hash and check X permission and is
suitable elsewhere:
- nfsd_rename()
- ovl_rename()

In ovl, ovl_do_rename_rd() is factored out of ovl_do_rename(), which
itself will be gone by the end of the series.

Acked-by: Chuck Lever <chuck.lever@oracle.com> (for nfsd parts)
Reviewed-by: default avatarJeff Layton <jlayton@kernel.org>
Reviewed-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarNeilBrown <neil@brown.name>

--
Changes since v3:
 - added missig dput() in ovl_rename when "whiteout" is not-NULL.

Changes since v2:
 - in __start_renaming() some label have been renamed, and err
   is always set before a "goto out_foo" rather than passing the
   error in a dentry*.
 - ovl_do_rename() changed to call the new ovl_do_rename_rd() rather
   than keeping duplicate code
 - code around ovl_cleanup() call in ovl_rename() restructured.

Link: https://patch.msgid.link/20251113002050.676694-11-neilb@ownmail.net


Tested-by: default avatar <syzbot@syzkaller.appspotmail.com>
Acked-by: default avatarChuck Lever <chuck.lever@oracle.com>
Reviewed-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent ff7c4ea1
Loading
Loading
Loading
Loading
+143 −54
Original line number Diff line number Diff line
@@ -3667,6 +3667,129 @@ void unlock_rename(struct dentry *p1, struct dentry *p2)
}
EXPORT_SYMBOL(unlock_rename);

/**
 * __start_renaming - lookup and lock names for rename
 * @rd:           rename data containing parent and flags, and
 *                for receiving found dentries
 * @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
 *                LOOKUP_NO_SYMLINKS etc).
 * @old_last:     name of object in @rd.old_parent
 * @new_last:     name of object in @rd.new_parent
 *
 * Look up two names and ensure locks are in place for
 * rename.
 *
 * On success the found dentries are stored in @rd.old_dentry,
 * @rd.new_dentry.  These references and the lock are dropped by
 * end_renaming().
 *
 * The passed in qstrs must have the hash calculated, and no permission
 * checking is performed.
 *
 * Returns: zero or an error.
 */
static int
__start_renaming(struct renamedata *rd, int lookup_flags,
		 struct qstr *old_last, struct qstr *new_last)
{
	struct dentry *trap;
	struct dentry *d1, *d2;
	int target_flags = LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
	int err;

	if (rd->flags & RENAME_EXCHANGE)
		target_flags = 0;
	if (rd->flags & RENAME_NOREPLACE)
		target_flags |= LOOKUP_EXCL;

	trap = lock_rename(rd->old_parent, rd->new_parent);
	if (IS_ERR(trap))
		return PTR_ERR(trap);

	d1 = lookup_one_qstr_excl(old_last, rd->old_parent,
				  lookup_flags);
	err = PTR_ERR(d1);
	if (IS_ERR(d1))
		goto out_unlock;

	d2 = lookup_one_qstr_excl(new_last, rd->new_parent,
				  lookup_flags | target_flags);
	err = PTR_ERR(d2);
	if (IS_ERR(d2))
		goto out_dput_d1;

	if (d1 == trap) {
		/* source is an ancestor of target */
		err = -EINVAL;
		goto out_dput_d2;
	}

	if (d2 == trap) {
		/* target is an ancestor of source */
		if (rd->flags & RENAME_EXCHANGE)
			err = -EINVAL;
		else
			err = -ENOTEMPTY;
		goto out_dput_d2;
	}

	rd->old_dentry = d1;
	rd->new_dentry = d2;
	return 0;

out_dput_d2:
	dput(d2);
out_dput_d1:
	dput(d1);
out_unlock:
	unlock_rename(rd->old_parent, rd->new_parent);
	return err;
}

/**
 * start_renaming - lookup and lock names for rename with permission checking
 * @rd:           rename data containing parent and flags, and
 *                for receiving found dentries
 * @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
 *                LOOKUP_NO_SYMLINKS etc).
 * @old_last:     name of object in @rd.old_parent
 * @new_last:     name of object in @rd.new_parent
 *
 * Look up two names and ensure locks are in place for
 * rename.
 *
 * On success the found dentries are stored in @rd.old_dentry,
 * @rd.new_dentry.  These references and the lock are dropped by
 * end_renaming().
 *
 * The passed in qstrs need not have the hash calculated, and basic
 * eXecute permission checking is performed against @rd.mnt_idmap.
 *
 * Returns: zero or an error.
 */
int start_renaming(struct renamedata *rd, int lookup_flags,
		   struct qstr *old_last, struct qstr *new_last)
{
	int err;

	err = lookup_one_common(rd->mnt_idmap, old_last, rd->old_parent);
	if (err)
		return err;
	err = lookup_one_common(rd->mnt_idmap, new_last, rd->new_parent);
	if (err)
		return err;
	return __start_renaming(rd, lookup_flags, old_last, new_last);
}
EXPORT_SYMBOL(start_renaming);

void end_renaming(struct renamedata *rd)
{
	unlock_rename(rd->old_parent, rd->new_parent);
	dput(rd->old_dentry);
	dput(rd->new_dentry);
}
EXPORT_SYMBOL(end_renaming);

/**
 * vfs_prepare_mode - prepare the mode to be used for a new inode
 * @idmap:	idmap of the mount the inode was found from
@@ -5504,14 +5627,11 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
		 struct filename *to, unsigned int flags)
{
	struct renamedata rd;
	struct dentry *old_dentry, *new_dentry;
	struct dentry *trap;
	struct path old_path, new_path;
	struct qstr old_last, new_last;
	int old_type, new_type;
	struct inode *delegated_inode = NULL;
	unsigned int lookup_flags = 0, target_flags =
		LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
	unsigned int lookup_flags = 0;
	bool should_retry = false;
	int error = -EINVAL;

@@ -5522,11 +5642,6 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
	    (flags & RENAME_EXCHANGE))
		goto put_names;

	if (flags & RENAME_EXCHANGE)
		target_flags = 0;
	if (flags & RENAME_NOREPLACE)
		target_flags |= LOOKUP_EXCL;

retry:
	error = filename_parentat(olddfd, from, lookup_flags, &old_path,
				  &old_last, &old_type);
@@ -5556,66 +5671,40 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
		goto exit2;

retry_deleg:
	trap = lock_rename(new_path.dentry, old_path.dentry);
	if (IS_ERR(trap)) {
		error = PTR_ERR(trap);
	rd.old_parent	   = old_path.dentry;
	rd.mnt_idmap	   = mnt_idmap(old_path.mnt);
	rd.new_parent	   = new_path.dentry;
	rd.delegated_inode = &delegated_inode;
	rd.flags	   = flags;

	error = __start_renaming(&rd, lookup_flags, &old_last, &new_last);
	if (error)
		goto exit_lock_rename;
	}

	old_dentry = lookup_one_qstr_excl(&old_last, old_path.dentry,
					  lookup_flags);
	error = PTR_ERR(old_dentry);
	if (IS_ERR(old_dentry))
		goto exit3;
	new_dentry = lookup_one_qstr_excl(&new_last, new_path.dentry,
					  lookup_flags | target_flags);
	error = PTR_ERR(new_dentry);
	if (IS_ERR(new_dentry))
		goto exit4;
	if (flags & RENAME_EXCHANGE) {
		if (!d_is_dir(new_dentry)) {
		if (!d_is_dir(rd.new_dentry)) {
			error = -ENOTDIR;
			if (new_last.name[new_last.len])
				goto exit5;
				goto exit_unlock;
		}
	}
	/* unless the source is a directory trailing slashes give -ENOTDIR */
	if (!d_is_dir(old_dentry)) {
	if (!d_is_dir(rd.old_dentry)) {
		error = -ENOTDIR;
		if (old_last.name[old_last.len])
			goto exit5;
			goto exit_unlock;
		if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len])
			goto exit5;
			goto exit_unlock;
	}
	/* source should not be ancestor of target */
	error = -EINVAL;
	if (old_dentry == trap)
		goto exit5;
	/* target should not be an ancestor of source */
	if (!(flags & RENAME_EXCHANGE))
		error = -ENOTEMPTY;
	if (new_dentry == trap)
		goto exit5;

	error = security_path_rename(&old_path, old_dentry,
				     &new_path, new_dentry, flags);
	error = security_path_rename(&old_path, rd.old_dentry,
				     &new_path, rd.new_dentry, flags);
	if (error)
		goto exit5;
		goto exit_unlock;

	rd.old_parent	   = old_path.dentry;
	rd.old_dentry	   = old_dentry;
	rd.mnt_idmap	   = mnt_idmap(old_path.mnt);
	rd.new_parent	   = new_path.dentry;
	rd.new_dentry	   = new_dentry;
	rd.delegated_inode = &delegated_inode;
	rd.flags	   = flags;
	error = vfs_rename(&rd);
exit5:
	dput(new_dentry);
exit4:
	dput(old_dentry);
exit3:
	unlock_rename(new_path.dentry, old_path.dentry);
exit_unlock:
	end_renaming(&rd);
exit_lock_rename:
	if (delegated_inode) {
		error = break_deleg_wait(&delegated_inode);
+25 −48
Original line number Diff line number Diff line
@@ -1885,11 +1885,12 @@ __be32
nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
			    struct svc_fh *tfhp, char *tname, int tlen)
{
	struct dentry	*fdentry, *tdentry, *odentry, *ndentry, *trap;
	struct dentry	*fdentry, *tdentry;
	int		type = S_IFDIR;
	struct renamedata rd = {};
	__be32		err;
	int		host_err;
	bool		close_cached = false;
	struct dentry	*close_cached;

	trace_nfsd_vfs_rename(rqstp, ffhp, tfhp, fname, flen, tname, tlen);

@@ -1915,15 +1916,22 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
		goto out;

retry:
	close_cached = NULL;
	host_err = fh_want_write(ffhp);
	if (host_err) {
		err = nfserrno(host_err);
		goto out;
	}

	trap = lock_rename(tdentry, fdentry);
	if (IS_ERR(trap)) {
		err = nfserr_xdev;
	rd.mnt_idmap	= &nop_mnt_idmap;
	rd.old_parent	= fdentry;
	rd.new_parent	= tdentry;

	host_err = start_renaming(&rd, 0, &QSTR_LEN(fname, flen),
				  &QSTR_LEN(tname, tlen));

	if (host_err) {
		err = nfserrno(host_err);
		goto out_want_write;
	}
	err = fh_fill_pre_attrs(ffhp);
@@ -1933,48 +1941,23 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
	if (err != nfs_ok)
		goto out_unlock;

	odentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), fdentry);
	host_err = PTR_ERR(odentry);
	if (IS_ERR(odentry))
		goto out_nfserr;
	type = d_inode(rd.old_dentry)->i_mode & S_IFMT;

	host_err = -ENOENT;
	if (d_really_is_negative(odentry))
		goto out_dput_old;
	host_err = -EINVAL;
	if (odentry == trap)
		goto out_dput_old;
	type = d_inode(odentry)->i_mode & S_IFMT;

	ndentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(tname, tlen), tdentry);
	host_err = PTR_ERR(ndentry);
	if (IS_ERR(ndentry))
		goto out_dput_old;
	if (d_inode(ndentry))
		type = d_inode(ndentry)->i_mode & S_IFMT;
	host_err = -ENOTEMPTY;
	if (ndentry == trap)
		goto out_dput_new;

	if ((ndentry->d_sb->s_export_op->flags & EXPORT_OP_CLOSE_BEFORE_UNLINK) &&
	    nfsd_has_cached_files(ndentry)) {
		close_cached = true;
		goto out_dput_old;
	if (d_inode(rd.new_dentry))
		type = d_inode(rd.new_dentry)->i_mode & S_IFMT;

	if ((rd.new_dentry->d_sb->s_export_op->flags & EXPORT_OP_CLOSE_BEFORE_UNLINK) &&
	    nfsd_has_cached_files(rd.new_dentry)) {
		close_cached = dget(rd.new_dentry);
		goto out_unlock;
	} else {
		struct renamedata rd = {
			.mnt_idmap	= &nop_mnt_idmap,
			.old_parent	= fdentry,
			.old_dentry	= odentry,
			.new_parent	= tdentry,
			.new_dentry	= ndentry,
		};
		int retries;

		for (retries = 1;;) {
			host_err = vfs_rename(&rd);
			if (host_err != -EAGAIN || !retries--)
				break;
			if (!nfsd_wait_for_delegreturn(rqstp, d_inode(odentry)))
			if (!nfsd_wait_for_delegreturn(rqstp, d_inode(rd.old_dentry)))
				break;
		}
		if (!host_err) {
@@ -1983,11 +1966,6 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
				host_err = commit_metadata(ffhp);
		}
	}
 out_dput_new:
	dput(ndentry);
 out_dput_old:
	dput(odentry);
 out_nfserr:
	if (host_err == -EBUSY) {
		/*
		 * See RFC 8881 Section 18.26.4 para 1-3: NFSv4 RENAME
@@ -2006,7 +1984,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
		fh_fill_post_attrs(tfhp);
	}
out_unlock:
	unlock_rename(tdentry, fdentry);
	end_renaming(&rd);
out_want_write:
	fh_drop_write(ffhp);

@@ -2017,9 +1995,8 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
	 * until this point and then reattempt the whole shebang.
	 */
	if (close_cached) {
		close_cached = false;
		nfsd_close_cached_files(ndentry);
		dput(ndentry);
		nfsd_close_cached_files(close_cached);
		dput(close_cached);
		goto retry;
	}
out:
+32 −42
Original line number Diff line number Diff line
@@ -1124,9 +1124,7 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
	int err;
	struct dentry *old_upperdir;
	struct dentry *new_upperdir;
	struct dentry *olddentry = NULL;
	struct dentry *newdentry = NULL;
	struct dentry *trap, *de;
	struct renamedata rd = {};
	bool old_opaque;
	bool new_opaque;
	bool cleanup_whiteout = false;
@@ -1136,6 +1134,7 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
	bool new_is_dir = d_is_dir(new);
	bool samedir = olddir == newdir;
	struct dentry *opaquedir = NULL;
	struct dentry *whiteout = NULL;
	const struct cred *old_cred = NULL;
	struct ovl_fs *ofs = OVL_FS(old->d_sb);
	LIST_HEAD(list);
@@ -1233,29 +1232,21 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
		}
	}

	trap = lock_rename(new_upperdir, old_upperdir);
	if (IS_ERR(trap)) {
		err = PTR_ERR(trap);
		goto out_revert_creds;
	}
	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
	rd.old_parent = old_upperdir;
	rd.new_parent = new_upperdir;
	rd.flags = flags;

	de = ovl_lookup_upper(ofs, old->d_name.name, old_upperdir,
			      old->d_name.len);
	err = PTR_ERR(de);
	if (IS_ERR(de))
		goto out_unlock;
	olddentry = de;
	err = start_renaming(&rd, 0,
			     &QSTR_LEN(old->d_name.name, old->d_name.len),
			     &QSTR_LEN(new->d_name.name, new->d_name.len));

	err = -ESTALE;
	if (!ovl_matches_upper(old, olddentry))
		goto out_unlock;
	if (err)
		goto out_revert_creds;

	de = ovl_lookup_upper(ofs, new->d_name.name, new_upperdir,
			      new->d_name.len);
	err = PTR_ERR(de);
	if (IS_ERR(de))
	err = -ESTALE;
	if (!ovl_matches_upper(old, rd.old_dentry))
		goto out_unlock;
	newdentry = de;

	old_opaque = ovl_dentry_is_opaque(old);
	new_opaque = ovl_dentry_is_opaque(new);
@@ -1263,15 +1254,15 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
	err = -ESTALE;
	if (d_inode(new) && ovl_dentry_upper(new)) {
		if (opaquedir) {
			if (newdentry != opaquedir)
			if (rd.new_dentry != opaquedir)
				goto out_unlock;
		} else {
			if (!ovl_matches_upper(new, newdentry))
			if (!ovl_matches_upper(new, rd.new_dentry))
				goto out_unlock;
		}
	} else {
		if (!d_is_negative(newdentry)) {
			if (!new_opaque || !ovl_upper_is_whiteout(ofs, newdentry))
		if (!d_is_negative(rd.new_dentry)) {
			if (!new_opaque || !ovl_upper_is_whiteout(ofs, rd.new_dentry))
				goto out_unlock;
		} else {
			if (flags & RENAME_EXCHANGE)
@@ -1279,19 +1270,14 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
		}
	}

	if (olddentry == trap)
		goto out_unlock;
	if (newdentry == trap)
		goto out_unlock;

	if (olddentry->d_inode == newdentry->d_inode)
	if (rd.old_dentry->d_inode == rd.new_dentry->d_inode)
		goto out_unlock;

	err = 0;
	if (ovl_type_merge_or_lower(old))
		err = ovl_set_redirect(old, samedir);
	else if (is_dir && !old_opaque && ovl_type_merge(new->d_parent))
		err = ovl_set_opaque_xerr(old, olddentry, -EXDEV);
		err = ovl_set_opaque_xerr(old, rd.old_dentry, -EXDEV);
	if (err)
		goto out_unlock;

@@ -1299,18 +1285,24 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
		err = ovl_set_redirect(new, samedir);
	else if (!overwrite && new_is_dir && !new_opaque &&
		 ovl_type_merge(old->d_parent))
		err = ovl_set_opaque_xerr(new, newdentry, -EXDEV);
		err = ovl_set_opaque_xerr(new, rd.new_dentry, -EXDEV);
	if (err)
		goto out_unlock;

	err = ovl_do_rename(ofs, old_upperdir, olddentry,
			    new_upperdir, newdentry, flags);
	unlock_rename(new_upperdir, old_upperdir);
	err = ovl_do_rename_rd(&rd);

	if (!err && cleanup_whiteout)
		whiteout = dget(rd.new_dentry);

	end_renaming(&rd);

	if (err)
		goto out_revert_creds;

	if (cleanup_whiteout)
		ovl_cleanup(ofs, old_upperdir, newdentry);
	if (whiteout) {
		ovl_cleanup(ofs, old_upperdir, whiteout);
		dput(whiteout);
	}

	if (overwrite && d_inode(new)) {
		if (new_is_dir)
@@ -1336,14 +1328,12 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
	else
		ovl_drop_write(old);
out:
	dput(newdentry);
	dput(olddentry);
	dput(opaquedir);
	ovl_cache_free(&list);
	return err;

out_unlock:
	unlock_rename(new_upperdir, old_upperdir);
	end_renaming(&rd);
	goto out_revert_creds;
}

+15 −8
Original line number Diff line number Diff line
@@ -355,11 +355,24 @@ static inline int ovl_do_remove_acl(struct ovl_fs *ofs, struct dentry *dentry,
	return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), dentry, acl_name);
}

static inline int ovl_do_rename_rd(struct renamedata *rd)
{
	int err;

	pr_debug("rename(%pd2, %pd2, 0x%x)\n", rd->old_dentry, rd->new_dentry,
		 rd->flags);
	err = vfs_rename(rd);
	if (err) {
		pr_debug("...rename(%pd2, %pd2, ...) = %i\n",
			 rd->old_dentry, rd->new_dentry, err);
	}
	return err;
}

static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir,
				struct dentry *olddentry, struct dentry *newdir,
				struct dentry *newdentry, unsigned int flags)
{
	int err;
	struct renamedata rd = {
		.mnt_idmap	= ovl_upper_mnt_idmap(ofs),
		.old_parent	= olddir,
@@ -369,13 +382,7 @@ static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir,
		.flags		= flags,
	};

	pr_debug("rename(%pd2, %pd2, 0x%x)\n", olddentry, newdentry, flags);
	err = vfs_rename(&rd);
	if (err) {
		pr_debug("...rename(%pd2, %pd2, ...) = %i\n",
			 olddentry, newdentry, err);
	}
	return err;
	return ovl_do_rename_rd(&rd);
}

static inline int ovl_do_whiteout(struct ovl_fs *ofs,
+3 −0
Original line number Diff line number Diff line
@@ -156,6 +156,9 @@ extern int follow_up(struct path *);
extern struct dentry *lock_rename(struct dentry *, struct dentry *);
extern struct dentry *lock_rename_child(struct dentry *, struct dentry *);
extern void unlock_rename(struct dentry *, struct dentry *);
int start_renaming(struct renamedata *rd, int lookup_flags,
		   struct qstr *old_last, struct qstr *new_last);
void end_renaming(struct renamedata *rd);

/**
 * mode_strip_umask - handle vfs umask stripping