mirror of
https://github.com/torvalds/linux.git
synced 2026-05-30 18:13:41 +02:00
Add start_renaming_two_dentries()
A few callers want to lock for a rename and already have both dentries. Also debugfs does want to perform a lookup but doesn't want permission checking, so start_renaming_dentry() cannot be used. This patch introduces start_renaming_two_dentries() which is given both dentries. debugfs performs one lookup itself. As it will only continue with a negative dentry and as those cannot be renamed or unlinked, it is safe to do the lookup before getting the rename locks. overlayfs uses start_renaming_two_dentries() in three places and selinux uses it twice in sel_make_policy_nodes(). In sel_make_policy_nodes() we now lock for rename twice instead of just once so the combined operation is no longer atomic w.r.t the parent directory locks. As selinux_state.policy_mutex is held across the whole operation this does not open up any interesting races. Reviewed-by: Amir Goldstein <amir73il@gmail.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: NeilBrown <neil@brown.name> Link: https://patch.msgid.link/20251113002050.676694-13-neilb@ownmail.net Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
parent
ac50950ca1
commit
833d2b3a07
|
|
@ -842,7 +842,8 @@ int __printf(2, 3) debugfs_change_name(struct dentry *dentry, const char *fmt, .
|
||||||
int error = 0;
|
int error = 0;
|
||||||
const char *new_name;
|
const char *new_name;
|
||||||
struct name_snapshot old_name;
|
struct name_snapshot old_name;
|
||||||
struct dentry *parent, *target;
|
struct dentry *target;
|
||||||
|
struct renamedata rd = {};
|
||||||
struct inode *dir;
|
struct inode *dir;
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
|
|
@ -855,36 +856,31 @@ int __printf(2, 3) debugfs_change_name(struct dentry *dentry, const char *fmt, .
|
||||||
if (!new_name)
|
if (!new_name)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
parent = dget_parent(dentry);
|
rd.old_parent = dget_parent(dentry);
|
||||||
dir = d_inode(parent);
|
rd.new_parent = rd.old_parent;
|
||||||
inode_lock(dir);
|
rd.flags = RENAME_NOREPLACE;
|
||||||
|
target = lookup_noperm_unlocked(&QSTR(new_name), rd.new_parent);
|
||||||
|
if (IS_ERR(target))
|
||||||
|
return PTR_ERR(target);
|
||||||
|
|
||||||
|
error = start_renaming_two_dentries(&rd, dentry, target);
|
||||||
|
if (error) {
|
||||||
|
if (error == -EEXIST && target == dentry)
|
||||||
|
/* it isn't an error to rename a thing to itself */
|
||||||
|
error = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = d_inode(rd.old_parent);
|
||||||
take_dentry_name_snapshot(&old_name, dentry);
|
take_dentry_name_snapshot(&old_name, dentry);
|
||||||
|
simple_rename_timestamp(dir, dentry, dir, rd.new_dentry);
|
||||||
if (WARN_ON_ONCE(dentry->d_parent != parent)) {
|
d_move(dentry, rd.new_dentry);
|
||||||
error = -EINVAL;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (strcmp(old_name.name.name, new_name) == 0)
|
|
||||||
goto out;
|
|
||||||
target = lookup_noperm(&QSTR(new_name), parent);
|
|
||||||
if (IS_ERR(target)) {
|
|
||||||
error = PTR_ERR(target);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (d_really_is_positive(target)) {
|
|
||||||
dput(target);
|
|
||||||
error = -EINVAL;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
simple_rename_timestamp(dir, dentry, dir, target);
|
|
||||||
d_move(dentry, target);
|
|
||||||
dput(target);
|
|
||||||
fsnotify_move(dir, dir, &old_name.name, d_is_dir(dentry), NULL, dentry);
|
fsnotify_move(dir, dir, &old_name.name, d_is_dir(dentry), NULL, dentry);
|
||||||
out:
|
|
||||||
release_dentry_name_snapshot(&old_name);
|
release_dentry_name_snapshot(&old_name);
|
||||||
inode_unlock(dir);
|
end_renaming(&rd);
|
||||||
dput(parent);
|
out:
|
||||||
|
dput(rd.old_parent);
|
||||||
|
dput(target);
|
||||||
kfree_const(new_name);
|
kfree_const(new_name);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
65
fs/namei.c
65
fs/namei.c
|
|
@ -3877,6 +3877,71 @@ int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(start_renaming_dentry);
|
EXPORT_SYMBOL(start_renaming_dentry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start_renaming_two_dentries - Lock to dentries in given parents for rename
|
||||||
|
* @rd: rename data containing parent
|
||||||
|
* @old_dentry: dentry of name to move
|
||||||
|
* @new_dentry: dentry to move to
|
||||||
|
*
|
||||||
|
* Ensure locks are in place for rename and check parentage is still correct.
|
||||||
|
*
|
||||||
|
* On success the two dentries are stored in @rd.old_dentry and
|
||||||
|
* @rd.new_dentry and @rd.old_parent and @rd.new_parent are confirmed to
|
||||||
|
* be the parents of the dentries.
|
||||||
|
*
|
||||||
|
* References and the lock can be dropped with end_renaming()
|
||||||
|
*
|
||||||
|
* Returns: zero or an error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
start_renaming_two_dentries(struct renamedata *rd,
|
||||||
|
struct dentry *old_dentry, struct dentry *new_dentry)
|
||||||
|
{
|
||||||
|
struct dentry *trap;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Already have the dentry - need to be sure to lock the correct parent */
|
||||||
|
trap = lock_rename_child(old_dentry, rd->new_parent);
|
||||||
|
if (IS_ERR(trap))
|
||||||
|
return PTR_ERR(trap);
|
||||||
|
err = -EINVAL;
|
||||||
|
if (d_unhashed(old_dentry) ||
|
||||||
|
(rd->old_parent && rd->old_parent != old_dentry->d_parent))
|
||||||
|
/* old_dentry was removed, or moved and explicit parent requested */
|
||||||
|
goto out_unlock;
|
||||||
|
if (d_unhashed(new_dentry) ||
|
||||||
|
rd->new_parent != new_dentry->d_parent)
|
||||||
|
/* new_dentry was removed or moved */
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
|
if (old_dentry == trap)
|
||||||
|
/* source is an ancestor of target */
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
|
if (new_dentry == trap) {
|
||||||
|
/* target is an ancestor of source */
|
||||||
|
if (rd->flags & RENAME_EXCHANGE)
|
||||||
|
err = -EINVAL;
|
||||||
|
else
|
||||||
|
err = -ENOTEMPTY;
|
||||||
|
goto out_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = -EEXIST;
|
||||||
|
if (d_is_positive(new_dentry) && (rd->flags & RENAME_NOREPLACE))
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
|
rd->old_dentry = dget(old_dentry);
|
||||||
|
rd->new_dentry = dget(new_dentry);
|
||||||
|
rd->old_parent = dget(old_dentry->d_parent);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_unlock:
|
||||||
|
unlock_rename(old_dentry->d_parent, rd->new_parent);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(start_renaming_two_dentries);
|
||||||
|
|
||||||
void end_renaming(struct renamedata *rd)
|
void end_renaming(struct renamedata *rd)
|
||||||
{
|
{
|
||||||
unlock_rename(rd->old_parent, rd->new_parent);
|
unlock_rename(rd->old_parent, rd->new_parent);
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
|
||||||
struct dentry *dentry)
|
struct dentry *dentry)
|
||||||
{
|
{
|
||||||
struct dentry *whiteout;
|
struct dentry *whiteout;
|
||||||
|
struct renamedata rd = {};
|
||||||
int err;
|
int err;
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
|
|
@ -134,10 +135,14 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
|
||||||
if (d_is_dir(dentry))
|
if (d_is_dir(dentry))
|
||||||
flags = RENAME_EXCHANGE;
|
flags = RENAME_EXCHANGE;
|
||||||
|
|
||||||
err = ovl_lock_rename_workdir(ofs->workdir, whiteout, dir, dentry);
|
rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
|
||||||
|
rd.old_parent = ofs->workdir;
|
||||||
|
rd.new_parent = dir;
|
||||||
|
rd.flags = flags;
|
||||||
|
err = start_renaming_two_dentries(&rd, whiteout, dentry);
|
||||||
if (!err) {
|
if (!err) {
|
||||||
err = ovl_do_rename(ofs, ofs->workdir, whiteout, dir, dentry, flags);
|
err = ovl_do_rename_rd(&rd);
|
||||||
unlock_rename(ofs->workdir, dir);
|
end_renaming(&rd);
|
||||||
}
|
}
|
||||||
if (err)
|
if (err)
|
||||||
goto kill_whiteout;
|
goto kill_whiteout;
|
||||||
|
|
@ -388,6 +393,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
|
||||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||||
struct dentry *workdir = ovl_workdir(dentry);
|
struct dentry *workdir = ovl_workdir(dentry);
|
||||||
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
|
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
|
||||||
|
struct renamedata rd = {};
|
||||||
struct path upperpath;
|
struct path upperpath;
|
||||||
struct dentry *upper;
|
struct dentry *upper;
|
||||||
struct dentry *opaquedir;
|
struct dentry *opaquedir;
|
||||||
|
|
@ -413,7 +419,11 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
|
||||||
if (IS_ERR(opaquedir))
|
if (IS_ERR(opaquedir))
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
err = ovl_lock_rename_workdir(workdir, opaquedir, upperdir, upper);
|
rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
|
||||||
|
rd.old_parent = workdir;
|
||||||
|
rd.new_parent = upperdir;
|
||||||
|
rd.flags = RENAME_EXCHANGE;
|
||||||
|
err = start_renaming_two_dentries(&rd, opaquedir, upper);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup_unlocked;
|
goto out_cleanup_unlocked;
|
||||||
|
|
||||||
|
|
@ -431,8 +441,8 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup;
|
goto out_cleanup;
|
||||||
|
|
||||||
err = ovl_do_rename(ofs, workdir, opaquedir, upperdir, upper, RENAME_EXCHANGE);
|
err = ovl_do_rename_rd(&rd);
|
||||||
unlock_rename(workdir, upperdir);
|
end_renaming(&rd);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup_unlocked;
|
goto out_cleanup_unlocked;
|
||||||
|
|
||||||
|
|
@ -445,7 +455,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
|
||||||
return opaquedir;
|
return opaquedir;
|
||||||
|
|
||||||
out_cleanup:
|
out_cleanup:
|
||||||
unlock_rename(workdir, upperdir);
|
end_renaming(&rd);
|
||||||
out_cleanup_unlocked:
|
out_cleanup_unlocked:
|
||||||
ovl_cleanup(ofs, workdir, opaquedir);
|
ovl_cleanup(ofs, workdir, opaquedir);
|
||||||
dput(opaquedir);
|
dput(opaquedir);
|
||||||
|
|
@ -468,6 +478,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
|
||||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||||
struct dentry *workdir = ovl_workdir(dentry);
|
struct dentry *workdir = ovl_workdir(dentry);
|
||||||
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
|
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
|
||||||
|
struct renamedata rd = {};
|
||||||
struct dentry *upper;
|
struct dentry *upper;
|
||||||
struct dentry *newdentry;
|
struct dentry *newdentry;
|
||||||
int err;
|
int err;
|
||||||
|
|
@ -499,7 +510,11 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
|
||||||
if (IS_ERR(newdentry))
|
if (IS_ERR(newdentry))
|
||||||
goto out_dput;
|
goto out_dput;
|
||||||
|
|
||||||
err = ovl_lock_rename_workdir(workdir, newdentry, upperdir, upper);
|
rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
|
||||||
|
rd.old_parent = workdir;
|
||||||
|
rd.new_parent = upperdir;
|
||||||
|
rd.flags = 0;
|
||||||
|
err = start_renaming_two_dentries(&rd, newdentry, upper);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup_unlocked;
|
goto out_cleanup_unlocked;
|
||||||
|
|
||||||
|
|
@ -536,16 +551,16 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup;
|
goto out_cleanup;
|
||||||
|
|
||||||
err = ovl_do_rename(ofs, workdir, newdentry, upperdir, upper,
|
rd.flags = RENAME_EXCHANGE;
|
||||||
RENAME_EXCHANGE);
|
err = ovl_do_rename_rd(&rd);
|
||||||
unlock_rename(workdir, upperdir);
|
end_renaming(&rd);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup_unlocked;
|
goto out_cleanup_unlocked;
|
||||||
|
|
||||||
ovl_cleanup(ofs, workdir, upper);
|
ovl_cleanup(ofs, workdir, upper);
|
||||||
} else {
|
} else {
|
||||||
err = ovl_do_rename(ofs, workdir, newdentry, upperdir, upper, 0);
|
err = ovl_do_rename_rd(&rd);
|
||||||
unlock_rename(workdir, upperdir);
|
end_renaming(&rd);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup_unlocked;
|
goto out_cleanup_unlocked;
|
||||||
}
|
}
|
||||||
|
|
@ -565,7 +580,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
out_cleanup:
|
out_cleanup:
|
||||||
unlock_rename(workdir, upperdir);
|
end_renaming(&rd);
|
||||||
out_cleanup_unlocked:
|
out_cleanup_unlocked:
|
||||||
ovl_cleanup(ofs, workdir, newdentry);
|
ovl_cleanup(ofs, workdir, newdentry);
|
||||||
dput(newdentry);
|
dput(newdentry);
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,8 @@ int start_renaming(struct renamedata *rd, int lookup_flags,
|
||||||
struct qstr *old_last, struct qstr *new_last);
|
struct qstr *old_last, struct qstr *new_last);
|
||||||
int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
|
int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
|
||||||
struct dentry *old_dentry, struct qstr *new_last);
|
struct dentry *old_dentry, struct qstr *new_last);
|
||||||
|
int start_renaming_two_dentries(struct renamedata *rd,
|
||||||
|
struct dentry *old_dentry, struct dentry *new_dentry);
|
||||||
void end_renaming(struct renamedata *rd);
|
void end_renaming(struct renamedata *rd);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -506,6 +506,7 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir;
|
struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir;
|
||||||
|
struct renamedata rd = {};
|
||||||
unsigned int bool_num = 0;
|
unsigned int bool_num = 0;
|
||||||
char **bool_names = NULL;
|
char **bool_names = NULL;
|
||||||
int *bool_values = NULL;
|
int *bool_values = NULL;
|
||||||
|
|
@ -539,9 +540,14 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
lock_rename(tmp_parent, fsi->sb->s_root);
|
rd.old_parent = tmp_parent;
|
||||||
|
rd.new_parent = fsi->sb->s_root;
|
||||||
|
|
||||||
/* booleans */
|
/* booleans */
|
||||||
|
ret = start_renaming_two_dentries(&rd, tmp_bool_dir, fsi->bool_dir);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
d_exchange(tmp_bool_dir, fsi->bool_dir);
|
d_exchange(tmp_bool_dir, fsi->bool_dir);
|
||||||
|
|
||||||
swap(fsi->bool_num, bool_num);
|
swap(fsi->bool_num, bool_num);
|
||||||
|
|
@ -549,12 +555,17 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
|
||||||
swap(fsi->bool_pending_values, bool_values);
|
swap(fsi->bool_pending_values, bool_values);
|
||||||
|
|
||||||
fsi->bool_dir = tmp_bool_dir;
|
fsi->bool_dir = tmp_bool_dir;
|
||||||
|
end_renaming(&rd);
|
||||||
|
|
||||||
/* classes */
|
/* classes */
|
||||||
|
ret = start_renaming_two_dentries(&rd, tmp_class_dir, fsi->class_dir);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
d_exchange(tmp_class_dir, fsi->class_dir);
|
d_exchange(tmp_class_dir, fsi->class_dir);
|
||||||
fsi->class_dir = tmp_class_dir;
|
fsi->class_dir = tmp_class_dir;
|
||||||
|
|
||||||
unlock_rename(tmp_parent, fsi->sb->s_root);
|
end_renaming(&rd);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
sel_remove_old_bool_data(bool_num, bool_names, bool_values);
|
sel_remove_old_bool_data(bool_num, bool_names, bool_values);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user