Skip to content

Commit c9ba789

Browse files
neilbrownbrauner
authored andcommitted
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>
1 parent bd6ede8 commit c9ba789

5 files changed

Lines changed: 74 additions & 38 deletions

File tree

fs/fuse/dir.c

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,27 +1397,25 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
13971397
if (!parent)
13981398
return -ENOENT;
13991399

1400-
inode_lock_nested(parent, I_MUTEX_PARENT);
14011400
if (!S_ISDIR(parent->i_mode))
1402-
goto unlock;
1401+
goto put_parent;
14031402

14041403
err = -ENOENT;
14051404
dir = d_find_alias(parent);
14061405
if (!dir)
1407-
goto unlock;
1406+
goto put_parent;
14081407

1409-
name->hash = full_name_hash(dir, name->name, name->len);
1410-
entry = d_lookup(dir, name);
1408+
entry = start_removing_noperm(dir, name);
14111409
dput(dir);
1412-
if (!entry)
1413-
goto unlock;
1410+
if (IS_ERR(entry))
1411+
goto put_parent;
14141412

14151413
fuse_dir_changed(parent);
14161414
if (!(flags & FUSE_EXPIRE_ONLY))
14171415
d_invalidate(entry);
14181416
fuse_invalidate_entry_cache(entry);
14191417

1420-
if (child_nodeid != 0 && d_really_is_positive(entry)) {
1418+
if (child_nodeid != 0) {
14211419
inode_lock(d_inode(entry));
14221420
if (get_node_id(d_inode(entry)) != child_nodeid) {
14231421
err = -ENOENT;
@@ -1445,10 +1443,9 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
14451443
} else {
14461444
err = 0;
14471445
}
1448-
dput(entry);
14491446

1450-
unlock:
1451-
inode_unlock(parent);
1447+
end_removing(entry);
1448+
put_parent:
14521449
iput(parent);
14531450
return err;
14541451
}

fs/namei.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3275,6 +3275,54 @@ struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
32753275
}
32763276
EXPORT_SYMBOL(start_removing);
32773277

3278+
/**
3279+
* start_creating_noperm - prepare to create a given name without permission checking
3280+
* @parent: directory in which to prepare to create the name
3281+
* @name: the name to be created
3282+
*
3283+
* Locks are taken and a lookup in performed prior to creating
3284+
* an object in a directory.
3285+
*
3286+
* If the name already exists, a positive dentry is returned.
3287+
*
3288+
* Returns: a negative or positive dentry, or an error.
3289+
*/
3290+
struct dentry *start_creating_noperm(struct dentry *parent,
3291+
struct qstr *name)
3292+
{
3293+
int err = lookup_noperm_common(name, parent);
3294+
3295+
if (err)
3296+
return ERR_PTR(err);
3297+
return start_dirop(parent, name, LOOKUP_CREATE);
3298+
}
3299+
EXPORT_SYMBOL(start_creating_noperm);
3300+
3301+
/**
3302+
* start_removing_noperm - prepare to remove a given name without permission checking
3303+
* @parent: directory in which to find the name
3304+
* @name: the name to be removed
3305+
*
3306+
* Locks are taken and a lookup in performed prior to removing
3307+
* an object from a directory.
3308+
*
3309+
* If the name doesn't exist, an error is returned.
3310+
*
3311+
* end_removing() should be called when removal is complete, or aborted.
3312+
*
3313+
* Returns: a positive dentry, or an error.
3314+
*/
3315+
struct dentry *start_removing_noperm(struct dentry *parent,
3316+
struct qstr *name)
3317+
{
3318+
int err = lookup_noperm_common(name, parent);
3319+
3320+
if (err)
3321+
return ERR_PTR(err);
3322+
return start_dirop(parent, name, 0);
3323+
}
3324+
EXPORT_SYMBOL(start_removing_noperm);
3325+
32783326
#ifdef CONFIG_UNIX98_PTYS
32793327
int path_pts(struct path *path)
32803328
{

fs/xfs/scrub/orphanage.c

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,10 @@ xrep_orphanage_create(
152152
}
153153

154154
/* Try to find the orphanage directory. */
155-
inode_lock_nested(root_inode, I_MUTEX_PARENT);
156-
orphanage_dentry = lookup_noperm(&QSTR(ORPHANAGE), root_dentry);
155+
orphanage_dentry = start_creating_noperm(root_dentry, &QSTR(ORPHANAGE));
157156
if (IS_ERR(orphanage_dentry)) {
158157
error = PTR_ERR(orphanage_dentry);
159-
goto out_unlock_root;
158+
goto out_dput_root;
160159
}
161160

162161
/*
@@ -170,7 +169,7 @@ xrep_orphanage_create(
170169
orphanage_dentry, 0750);
171170
error = PTR_ERR(orphanage_dentry);
172171
if (IS_ERR(orphanage_dentry))
173-
goto out_unlock_root;
172+
goto out_dput_orphanage;
174173
}
175174

176175
/* Not a directory? Bail out. */
@@ -200,9 +199,7 @@ xrep_orphanage_create(
200199
sc->orphanage_ilock_flags = 0;
201200

202201
out_dput_orphanage:
203-
dput(orphanage_dentry);
204-
out_unlock_root:
205-
inode_unlock(VFS_I(sc->mp->m_rootip));
202+
end_creating(orphanage_dentry, root_dentry);
206203
out_dput_root:
207204
dput(root_dentry);
208205
out:

include/linux/namei.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
9292
struct qstr *name);
9393
struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
9494
struct qstr *name);
95+
struct dentry *start_creating_noperm(struct dentry *parent, struct qstr *name);
96+
struct dentry *start_removing_noperm(struct dentry *parent, struct qstr *name);
9597

9698
/**
9799
* end_creating - finish action started with start_creating

ipc/mqueue.c

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -913,13 +913,12 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
913913
goto out_putname;
914914

915915
ro = mnt_want_write(mnt); /* we'll drop it in any case */
916-
inode_lock(d_inode(root));
917-
path.dentry = lookup_noperm(&QSTR(name->name), root);
916+
path.dentry = start_creating_noperm(root, &QSTR(name->name));
918917
if (IS_ERR(path.dentry)) {
919918
error = PTR_ERR(path.dentry);
920919
goto out_putfd;
921920
}
922-
path.mnt = mntget(mnt);
921+
path.mnt = mnt;
923922
error = prepare_open(path.dentry, oflag, ro, mode, name, attr);
924923
if (!error) {
925924
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,
928927
else
929928
error = PTR_ERR(file);
930929
}
931-
path_put(&path);
932930
out_putfd:
933931
if (error) {
934932
put_unused_fd(fd);
935933
fd = error;
936934
}
937-
inode_unlock(d_inode(root));
935+
end_creating(path.dentry, root);
938936
if (!ro)
939937
mnt_drop_write(mnt);
940938
out_putname:
@@ -957,7 +955,7 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
957955
int err;
958956
struct filename *name;
959957
struct dentry *dentry;
960-
struct inode *inode = NULL;
958+
struct inode *inode;
961959
struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns;
962960
struct vfsmount *mnt = ipc_ns->mq_mnt;
963961

@@ -969,26 +967,20 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
969967
err = mnt_want_write(mnt);
970968
if (err)
971969
goto out_name;
972-
inode_lock_nested(d_inode(mnt->mnt_root), I_MUTEX_PARENT);
973-
dentry = lookup_noperm(&QSTR(name->name), mnt->mnt_root);
970+
dentry = start_removing_noperm(mnt->mnt_root, &QSTR(name->name));
974971
if (IS_ERR(dentry)) {
975972
err = PTR_ERR(dentry);
976-
goto out_unlock;
973+
goto out_drop_write;
977974
}
978975

979976
inode = d_inode(dentry);
980-
if (!inode) {
981-
err = -ENOENT;
982-
} else {
983-
ihold(inode);
984-
err = vfs_unlink(&nop_mnt_idmap, d_inode(dentry->d_parent),
985-
dentry, NULL);
986-
}
987-
dput(dentry);
988-
989-
out_unlock:
990-
inode_unlock(d_inode(mnt->mnt_root));
977+
ihold(inode);
978+
err = vfs_unlink(&nop_mnt_idmap, d_inode(mnt->mnt_root),
979+
dentry, NULL);
980+
end_removing(dentry);
991981
iput(inode);
982+
983+
out_drop_write:
992984
mnt_drop_write(mnt);
993985
out_name:
994986
putname(name);

0 commit comments

Comments
 (0)