mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
VFS: introduce start_creating_noperm() and start_removing_noperm()
xfs, fuse, ipc/mqueue need variants of start_creating or start_removing which do not check permissions. This patch adds _noperm versions of these functions. Note that do_mq_open() was only calling mntget() so it could call path_put() - it didn't really need an extra reference on the mnt. Now it doesn't call mntget() and uses end_creating() which does the dput() half of path_put(). Also mq_unlink() previously passed d_inode(dentry->d_parent) as the dir inode to vfs_unlink(). This is after locking d_inode(mnt->mnt_root) These two inodes are the same, but normally calls use the textual parent. So I've changes the vfs_unlink() call to be given d_inode(mnt->mnt_root). Reviewed-by: Amir Goldstein <amir73il@gmail.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: NeilBrown <neil@brown.name> -- changes since v2: - dir arg passed to vfs_unlink() in mq_unlink() changed to match the dir passed to lookup_noperm() - restore assignment to path->mnt even though the mntget() is removed. Link: https://patch.msgid.link/20251113002050.676694-7-neilb@ownmail.net Tested-by: syzbot@syzkaller.appspotmail.com Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
committed by
Christian Brauner
parent
bd6ede8a06
commit
c9ba789dad
@@ -1397,27 +1397,25 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
|
|||||||
if (!parent)
|
if (!parent)
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
|
|
||||||
inode_lock_nested(parent, I_MUTEX_PARENT);
|
|
||||||
if (!S_ISDIR(parent->i_mode))
|
if (!S_ISDIR(parent->i_mode))
|
||||||
goto unlock;
|
goto put_parent;
|
||||||
|
|
||||||
err = -ENOENT;
|
err = -ENOENT;
|
||||||
dir = d_find_alias(parent);
|
dir = d_find_alias(parent);
|
||||||
if (!dir)
|
if (!dir)
|
||||||
goto unlock;
|
goto put_parent;
|
||||||
|
|
||||||
name->hash = full_name_hash(dir, name->name, name->len);
|
entry = start_removing_noperm(dir, name);
|
||||||
entry = d_lookup(dir, name);
|
|
||||||
dput(dir);
|
dput(dir);
|
||||||
if (!entry)
|
if (IS_ERR(entry))
|
||||||
goto unlock;
|
goto put_parent;
|
||||||
|
|
||||||
fuse_dir_changed(parent);
|
fuse_dir_changed(parent);
|
||||||
if (!(flags & FUSE_EXPIRE_ONLY))
|
if (!(flags & FUSE_EXPIRE_ONLY))
|
||||||
d_invalidate(entry);
|
d_invalidate(entry);
|
||||||
fuse_invalidate_entry_cache(entry);
|
fuse_invalidate_entry_cache(entry);
|
||||||
|
|
||||||
if (child_nodeid != 0 && d_really_is_positive(entry)) {
|
if (child_nodeid != 0) {
|
||||||
inode_lock(d_inode(entry));
|
inode_lock(d_inode(entry));
|
||||||
if (get_node_id(d_inode(entry)) != child_nodeid) {
|
if (get_node_id(d_inode(entry)) != child_nodeid) {
|
||||||
err = -ENOENT;
|
err = -ENOENT;
|
||||||
@@ -1445,10 +1443,9 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
|
|||||||
} else {
|
} else {
|
||||||
err = 0;
|
err = 0;
|
||||||
}
|
}
|
||||||
dput(entry);
|
|
||||||
|
|
||||||
unlock:
|
end_removing(entry);
|
||||||
inode_unlock(parent);
|
put_parent:
|
||||||
iput(parent);
|
iput(parent);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
48
fs/namei.c
48
fs/namei.c
@@ -3275,6 +3275,54 @@ struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(start_removing);
|
EXPORT_SYMBOL(start_removing);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start_creating_noperm - prepare to create a given name without permission checking
|
||||||
|
* @parent: directory in which to prepare to create the name
|
||||||
|
* @name: the name to be created
|
||||||
|
*
|
||||||
|
* Locks are taken and a lookup in performed prior to creating
|
||||||
|
* an object in a directory.
|
||||||
|
*
|
||||||
|
* If the name already exists, a positive dentry is returned.
|
||||||
|
*
|
||||||
|
* Returns: a negative or positive dentry, or an error.
|
||||||
|
*/
|
||||||
|
struct dentry *start_creating_noperm(struct dentry *parent,
|
||||||
|
struct qstr *name)
|
||||||
|
{
|
||||||
|
int err = lookup_noperm_common(name, parent);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return ERR_PTR(err);
|
||||||
|
return start_dirop(parent, name, LOOKUP_CREATE);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(start_creating_noperm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start_removing_noperm - prepare to remove a given name without permission checking
|
||||||
|
* @parent: directory in which to find the name
|
||||||
|
* @name: the name to be removed
|
||||||
|
*
|
||||||
|
* Locks are taken and a lookup in performed prior to removing
|
||||||
|
* an object from a directory.
|
||||||
|
*
|
||||||
|
* If the name doesn't exist, an error is returned.
|
||||||
|
*
|
||||||
|
* end_removing() should be called when removal is complete, or aborted.
|
||||||
|
*
|
||||||
|
* Returns: a positive dentry, or an error.
|
||||||
|
*/
|
||||||
|
struct dentry *start_removing_noperm(struct dentry *parent,
|
||||||
|
struct qstr *name)
|
||||||
|
{
|
||||||
|
int err = lookup_noperm_common(name, parent);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return ERR_PTR(err);
|
||||||
|
return start_dirop(parent, name, 0);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(start_removing_noperm);
|
||||||
|
|
||||||
#ifdef CONFIG_UNIX98_PTYS
|
#ifdef CONFIG_UNIX98_PTYS
|
||||||
int path_pts(struct path *path)
|
int path_pts(struct path *path)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -152,11 +152,10 @@ xrep_orphanage_create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Try to find the orphanage directory. */
|
/* Try to find the orphanage directory. */
|
||||||
inode_lock_nested(root_inode, I_MUTEX_PARENT);
|
orphanage_dentry = start_creating_noperm(root_dentry, &QSTR(ORPHANAGE));
|
||||||
orphanage_dentry = lookup_noperm(&QSTR(ORPHANAGE), root_dentry);
|
|
||||||
if (IS_ERR(orphanage_dentry)) {
|
if (IS_ERR(orphanage_dentry)) {
|
||||||
error = PTR_ERR(orphanage_dentry);
|
error = PTR_ERR(orphanage_dentry);
|
||||||
goto out_unlock_root;
|
goto out_dput_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -170,7 +169,7 @@ xrep_orphanage_create(
|
|||||||
orphanage_dentry, 0750);
|
orphanage_dentry, 0750);
|
||||||
error = PTR_ERR(orphanage_dentry);
|
error = PTR_ERR(orphanage_dentry);
|
||||||
if (IS_ERR(orphanage_dentry))
|
if (IS_ERR(orphanage_dentry))
|
||||||
goto out_unlock_root;
|
goto out_dput_orphanage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Not a directory? Bail out. */
|
/* Not a directory? Bail out. */
|
||||||
@@ -200,9 +199,7 @@ xrep_orphanage_create(
|
|||||||
sc->orphanage_ilock_flags = 0;
|
sc->orphanage_ilock_flags = 0;
|
||||||
|
|
||||||
out_dput_orphanage:
|
out_dput_orphanage:
|
||||||
dput(orphanage_dentry);
|
end_creating(orphanage_dentry, root_dentry);
|
||||||
out_unlock_root:
|
|
||||||
inode_unlock(VFS_I(sc->mp->m_rootip));
|
|
||||||
out_dput_root:
|
out_dput_root:
|
||||||
dput(root_dentry);
|
dput(root_dentry);
|
||||||
out:
|
out:
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
|
|||||||
struct qstr *name);
|
struct qstr *name);
|
||||||
struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
|
struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
|
||||||
struct qstr *name);
|
struct qstr *name);
|
||||||
|
struct dentry *start_creating_noperm(struct dentry *parent, struct qstr *name);
|
||||||
|
struct dentry *start_removing_noperm(struct dentry *parent, struct qstr *name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* end_creating - finish action started with start_creating
|
* end_creating - finish action started with start_creating
|
||||||
|
|||||||
28
ipc/mqueue.c
28
ipc/mqueue.c
@@ -913,13 +913,12 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
|
|||||||
goto out_putname;
|
goto out_putname;
|
||||||
|
|
||||||
ro = mnt_want_write(mnt); /* we'll drop it in any case */
|
ro = mnt_want_write(mnt); /* we'll drop it in any case */
|
||||||
inode_lock(d_inode(root));
|
path.dentry = start_creating_noperm(root, &QSTR(name->name));
|
||||||
path.dentry = lookup_noperm(&QSTR(name->name), root);
|
|
||||||
if (IS_ERR(path.dentry)) {
|
if (IS_ERR(path.dentry)) {
|
||||||
error = PTR_ERR(path.dentry);
|
error = PTR_ERR(path.dentry);
|
||||||
goto out_putfd;
|
goto out_putfd;
|
||||||
}
|
}
|
||||||
path.mnt = mntget(mnt);
|
path.mnt = mnt;
|
||||||
error = prepare_open(path.dentry, oflag, ro, mode, name, attr);
|
error = prepare_open(path.dentry, oflag, ro, mode, name, attr);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
struct file *file = dentry_open(&path, oflag, current_cred());
|
struct file *file = dentry_open(&path, oflag, current_cred());
|
||||||
@@ -928,13 +927,12 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
|
|||||||
else
|
else
|
||||||
error = PTR_ERR(file);
|
error = PTR_ERR(file);
|
||||||
}
|
}
|
||||||
path_put(&path);
|
|
||||||
out_putfd:
|
out_putfd:
|
||||||
if (error) {
|
if (error) {
|
||||||
put_unused_fd(fd);
|
put_unused_fd(fd);
|
||||||
fd = error;
|
fd = error;
|
||||||
}
|
}
|
||||||
inode_unlock(d_inode(root));
|
end_creating(path.dentry, root);
|
||||||
if (!ro)
|
if (!ro)
|
||||||
mnt_drop_write(mnt);
|
mnt_drop_write(mnt);
|
||||||
out_putname:
|
out_putname:
|
||||||
@@ -957,7 +955,7 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
|
|||||||
int err;
|
int err;
|
||||||
struct filename *name;
|
struct filename *name;
|
||||||
struct dentry *dentry;
|
struct dentry *dentry;
|
||||||
struct inode *inode = NULL;
|
struct inode *inode;
|
||||||
struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns;
|
struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns;
|
||||||
struct vfsmount *mnt = ipc_ns->mq_mnt;
|
struct vfsmount *mnt = ipc_ns->mq_mnt;
|
||||||
|
|
||||||
@@ -969,26 +967,20 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
|
|||||||
err = mnt_want_write(mnt);
|
err = mnt_want_write(mnt);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_name;
|
goto out_name;
|
||||||
inode_lock_nested(d_inode(mnt->mnt_root), I_MUTEX_PARENT);
|
dentry = start_removing_noperm(mnt->mnt_root, &QSTR(name->name));
|
||||||
dentry = lookup_noperm(&QSTR(name->name), mnt->mnt_root);
|
|
||||||
if (IS_ERR(dentry)) {
|
if (IS_ERR(dentry)) {
|
||||||
err = PTR_ERR(dentry);
|
err = PTR_ERR(dentry);
|
||||||
goto out_unlock;
|
goto out_drop_write;
|
||||||
}
|
}
|
||||||
|
|
||||||
inode = d_inode(dentry);
|
inode = d_inode(dentry);
|
||||||
if (!inode) {
|
|
||||||
err = -ENOENT;
|
|
||||||
} else {
|
|
||||||
ihold(inode);
|
ihold(inode);
|
||||||
err = vfs_unlink(&nop_mnt_idmap, d_inode(dentry->d_parent),
|
err = vfs_unlink(&nop_mnt_idmap, d_inode(mnt->mnt_root),
|
||||||
dentry, NULL);
|
dentry, NULL);
|
||||||
}
|
end_removing(dentry);
|
||||||
dput(dentry);
|
|
||||||
|
|
||||||
out_unlock:
|
|
||||||
inode_unlock(d_inode(mnt->mnt_root));
|
|
||||||
iput(inode);
|
iput(inode);
|
||||||
|
|
||||||
|
out_drop_write:
|
||||||
mnt_drop_write(mnt);
|
mnt_drop_write(mnt);
|
||||||
out_name:
|
out_name:
|
||||||
putname(name);
|
putname(name);
|
||||||
|
|||||||
Reference in New Issue
Block a user