VFS: change vfs_mkdir() to unlock on failure.

vfs_mkdir() already drops the reference to the dentry on failure but it
leaves the parent locked.
This complicates end_creating() which needs to unlock the parent even
though the dentry is no longer available.

If we change vfs_mkdir() to unlock on failure as well as releasing the
dentry, we can remove the "parent" arg from end_creating() and simplify
the rules for calling it.

Note that cachefiles_get_directory() can choose to substitute an error
instead of actually calling vfs_mkdir(), for fault injection.  In that
case it needs to call end_creating(), just as vfs_mkdir() now does on
error.

ovl_create_real() will now unlock on error.  So the conditional
end_creating() after the call is removed, and end_creating() is called
internally on error.

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Tested-by: syzbot@syzkaller.appspotmail.com
Signed-off-by: NeilBrown <neil@brown.name>
Link: https://patch.msgid.link/20251113002050.676694-15-neilb@ownmail.net
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
NeilBrown 2025-11-13 11:18:37 +11:00 committed by Christian Brauner
parent f046fbb4d8
commit fe497f0759
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
16 changed files with 61 additions and 58 deletions

View File

@ -1309,3 +1309,16 @@ a different length, use
vfs_parse_fs_qstr(fc, key, &QSTR_LEN(value, len)) vfs_parse_fs_qstr(fc, key, &QSTR_LEN(value, len))
instead. instead.
---
**mandatory**
vfs_mkdir() now returns a dentry - the one returned by ->mkdir(). If
that dentry is different from the dentry passed in, including if it is
an IS_ERR() dentry pointer, the original dentry is dput().
When vfs_mkdir() returns an error, and so both dputs() the original
dentry and doesn't provide a replacement, it also unlocks the parent.
Consequently the return value from vfs_mkdir() can be passed to
end_creating() and the parent will be unlocked precisely when necessary.

View File

@ -935,7 +935,7 @@ static noinline int btrfs_mksubvol(struct dentry *parent,
out_up_read: out_up_read:
up_read(&fs_info->subvol_sem); up_read(&fs_info->subvol_sem);
out_dput: out_dput:
end_creating(dentry, parent); end_creating(dentry);
return ret; return ret;
} }

View File

@ -128,10 +128,12 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
if (ret < 0) if (ret < 0)
goto mkdir_error; goto mkdir_error;
ret = cachefiles_inject_write_error(); ret = cachefiles_inject_write_error();
if (ret == 0) if (ret == 0) {
subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700); subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700);
else } else {
end_creating(subdir);
subdir = ERR_PTR(ret); subdir = ERR_PTR(ret);
}
if (IS_ERR(subdir)) { if (IS_ERR(subdir)) {
trace_cachefiles_vfs_error(NULL, d_inode(dir), ret, trace_cachefiles_vfs_error(NULL, d_inode(dir), ret,
cachefiles_trace_mkdir_error); cachefiles_trace_mkdir_error);
@ -140,7 +142,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
trace_cachefiles_mkdir(dir, subdir); trace_cachefiles_mkdir(dir, subdir);
if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) { if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) {
end_creating(subdir, dir); end_creating(subdir);
goto retry; goto retry;
} }
ASSERT(d_backing_inode(subdir)); ASSERT(d_backing_inode(subdir));
@ -154,7 +156,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
/* Tell rmdir() it's not allowed to delete the subdir */ /* Tell rmdir() it's not allowed to delete the subdir */
inode_lock(d_inode(subdir)); inode_lock(d_inode(subdir));
dget(subdir); dget(subdir);
end_creating(subdir, dir); end_creating(subdir);
if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) { if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {
pr_notice("cachefiles: Inode already in use: %pd (B=%lx)\n", pr_notice("cachefiles: Inode already in use: %pd (B=%lx)\n",
@ -196,7 +198,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
return ERR_PTR(-EBUSY); return ERR_PTR(-EBUSY);
mkdir_error: mkdir_error:
end_creating(subdir, dir); end_creating(subdir);
pr_err("mkdir %s failed with error %d\n", dirname, ret); pr_err("mkdir %s failed with error %d\n", dirname, ret);
return ERR_PTR(ret); return ERR_PTR(ret);
@ -699,7 +701,7 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
if (ret < 0) if (ret < 0)
goto out_end; goto out_end;
end_creating(dentry, fan); end_creating(dentry);
ret = cachefiles_inject_read_error(); ret = cachefiles_inject_read_error();
if (ret == 0) if (ret == 0)
@ -733,7 +735,7 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
} }
out_end: out_end:
end_creating(dentry, fan); end_creating(dentry);
out: out:
_leave(" = %u", success); _leave(" = %u", success);
return success; return success;

View File

@ -211,7 +211,7 @@ ecryptfs_do_create(struct inode *directory_inode,
fsstack_copy_attr_times(directory_inode, lower_dir); fsstack_copy_attr_times(directory_inode, lower_dir);
fsstack_copy_inode_size(directory_inode, lower_dir); fsstack_copy_inode_size(directory_inode, lower_dir);
out_lock: out_lock:
end_creating(lower_dentry, NULL); end_creating(lower_dentry);
return inode; return inode;
} }
@ -456,7 +456,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink); ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink);
i_size_write(d_inode(new_dentry), file_size_save); i_size_write(d_inode(new_dentry), file_size_save);
out_lock: out_lock:
end_creating(lower_new_dentry, NULL); end_creating(lower_new_dentry);
return rc; return rc;
} }
@ -500,7 +500,7 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
fsstack_copy_attr_times(dir, lower_dir); fsstack_copy_attr_times(dir, lower_dir);
fsstack_copy_inode_size(dir, lower_dir); fsstack_copy_inode_size(dir, lower_dir);
out_lock: out_lock:
end_creating(lower_dentry, NULL); end_creating(lower_dentry);
if (d_really_is_negative(dentry)) if (d_really_is_negative(dentry))
d_drop(dentry); d_drop(dentry);
return rc; return rc;
@ -534,7 +534,7 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
fsstack_copy_inode_size(dir, lower_dir); fsstack_copy_inode_size(dir, lower_dir);
set_nlink(dir, lower_dir->i_nlink); set_nlink(dir, lower_dir->i_nlink);
out: out:
end_creating(lower_dentry, lower_dir_dentry); end_creating(lower_dentry);
if (d_really_is_negative(dentry)) if (d_really_is_negative(dentry))
d_drop(dentry); d_drop(dentry);
return ERR_PTR(rc); return ERR_PTR(rc);

View File

@ -4832,7 +4832,7 @@ EXPORT_SYMBOL(start_creating_path);
*/ */
void end_creating_path(const struct path *path, struct dentry *dentry) void end_creating_path(const struct path *path, struct dentry *dentry)
{ {
end_creating(dentry, path->dentry); end_creating(dentry);
mnt_drop_write(path->mnt); mnt_drop_write(path->mnt);
path_put(path); path_put(path);
} }
@ -5034,7 +5034,7 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
return dentry; return dentry;
err: err:
dput(dentry); end_creating(dentry);
return ERR_PTR(error); return ERR_PTR(error);
} }
EXPORT_SYMBOL(vfs_mkdir); EXPORT_SYMBOL(vfs_mkdir);

View File

@ -364,7 +364,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs); status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs);
out: out:
end_creating(child, parent); end_creating(child);
out_write: out_write:
fh_drop_write(fhp); fh_drop_write(fhp);
return status; return status;

View File

@ -376,7 +376,7 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (attrs.na_aclerr) if (attrs.na_aclerr)
open->op_bmval[0] &= ~FATTR4_WORD0_ACL; open->op_bmval[0] &= ~FATTR4_WORD0_ACL;
out: out:
end_creating(child, parent); end_creating(child);
nfsd_attrs_free(&attrs); nfsd_attrs_free(&attrs);
out_write: out_write:
fh_drop_write(fhp); fh_drop_write(fhp);

View File

@ -215,7 +215,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
if (IS_ERR(dentry)) if (IS_ERR(dentry))
status = PTR_ERR(dentry); status = PTR_ERR(dentry);
out_end: out_end:
end_creating(dentry, dir); end_creating(dentry);
out: out:
if (status == 0) { if (status == 0) {
if (nn->in_grace) if (nn->in_grace)

View File

@ -421,7 +421,7 @@ nfsd_proc_create(struct svc_rqst *rqstp)
} }
out_unlock: out_unlock:
end_creating(dchild, dirfhp->fh_dentry); end_creating(dchild);
out_write: out_write:
fh_drop_write(dirfhp); fh_drop_write(dirfhp);
done: done:

View File

@ -1589,7 +1589,7 @@ nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp,
out: out:
if (!err) if (!err)
fh_fill_post_attrs(fhp); fh_fill_post_attrs(fhp);
end_creating(dchild, dentry); end_creating(dchild);
return err; return err;
out_nfserr: out_nfserr:
@ -1646,7 +1646,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
return err; return err;
out_unlock: out_unlock:
end_creating(dchild, dentry); end_creating(dchild);
return err; return err;
} }
@ -1747,7 +1747,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
nfsd_create_setattr(rqstp, fhp, resfhp, attrs); nfsd_create_setattr(rqstp, fhp, resfhp, attrs);
fh_fill_post_attrs(fhp); fh_fill_post_attrs(fhp);
out_unlock: out_unlock:
end_creating(dnew, dentry); end_creating(dnew);
if (!err) if (!err)
err = nfserrno(commit_metadata(fhp)); err = nfserrno(commit_metadata(fhp));
if (!err) if (!err)
@ -1824,7 +1824,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
host_err = vfs_link(dold, &nop_mnt_idmap, dirp, dnew, NULL); host_err = vfs_link(dold, &nop_mnt_idmap, dirp, dnew, NULL);
fh_fill_post_attrs(ffhp); fh_fill_post_attrs(ffhp);
out_unlock: out_unlock:
end_creating(dnew, ddir); end_creating(dnew);
if (!host_err) { if (!host_err) {
host_err = commit_metadata(ffhp); host_err = commit_metadata(ffhp);
if (!host_err) if (!host_err)

View File

@ -624,7 +624,7 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
ovl_dentry_set_upper_alias(c->dentry); ovl_dentry_set_upper_alias(c->dentry);
ovl_dentry_update_reval(c->dentry, upper); ovl_dentry_update_reval(c->dentry, upper);
} }
end_creating(upper, upperdir); end_creating(upper);
} }
if (err) if (err)
goto out; goto out;
@ -891,7 +891,7 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
err = PTR_ERR(upper); err = PTR_ERR(upper);
if (!IS_ERR(upper)) { if (!IS_ERR(upper)) {
err = ovl_do_link(ofs, temp, udir, upper); err = ovl_do_link(ofs, temp, udir, upper);
end_creating(upper, c->destdir); end_creating(upper);
} }
if (err) if (err)

View File

@ -91,7 +91,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
err = ovl_do_whiteout(ofs, wdir, whiteout); err = ovl_do_whiteout(ofs, wdir, whiteout);
if (!err) if (!err)
ofs->whiteout = dget(whiteout); ofs->whiteout = dget(whiteout);
end_creating(whiteout, workdir); end_creating(whiteout);
if (err) if (err)
return ERR_PTR(err); return ERR_PTR(err);
} }
@ -103,7 +103,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
err = ovl_do_link(ofs, ofs->whiteout, wdir, link); err = ovl_do_link(ofs, ofs->whiteout, wdir, link);
if (!err) if (!err)
whiteout = dget(link); whiteout = dget(link);
end_creating(link, workdir); end_creating(link);
if (!err) if (!err)
return whiteout;; return whiteout;;
@ -187,7 +187,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) { if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) {
pr_warn_ratelimited("wrong inherited casefold (%pd2)\n", pr_warn_ratelimited("wrong inherited casefold (%pd2)\n",
newdentry); newdentry);
dput(newdentry); end_creating(newdentry);
err = -EINVAL; err = -EINVAL;
} }
break; break;
@ -237,8 +237,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
} }
out: out:
if (err) { if (err) {
if (!IS_ERR(newdentry)) end_creating(newdentry);
dput(newdentry);
return ERR_PTR(err); return ERR_PTR(err);
} }
return newdentry; return newdentry;
@ -254,7 +253,7 @@ struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
ret = ovl_create_real(ofs, workdir, ret, attr); ret = ovl_create_real(ofs, workdir, ret, attr);
if (!IS_ERR(ret)) if (!IS_ERR(ret))
dget(ret); dget(ret);
end_creating(ret, workdir); end_creating(ret);
return ret; return ret;
} }
@ -362,12 +361,11 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
if (IS_ERR(newdentry)) if (IS_ERR(newdentry))
return PTR_ERR(newdentry); return PTR_ERR(newdentry);
newdentry = ovl_create_real(ofs, upperdir, newdentry, attr); newdentry = ovl_create_real(ofs, upperdir, newdentry, attr);
if (IS_ERR(newdentry)) { if (IS_ERR(newdentry))
end_creating(newdentry, upperdir);
return PTR_ERR(newdentry); return PTR_ERR(newdentry);
}
dget(newdentry); dget(newdentry);
end_creating(newdentry, upperdir); end_creating(newdentry);
if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry) && if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry) &&
!ovl_allow_offline_changes(ofs)) { !ovl_allow_offline_changes(ofs)) {

View File

@ -320,7 +320,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
if (work->d_inode) { if (work->d_inode) {
dget(work); dget(work);
end_creating(work, ofs->workbasedir); end_creating(work);
if (persist) if (persist)
return work; return work;
err = -EEXIST; err = -EEXIST;
@ -338,7 +338,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
work = ovl_do_mkdir(ofs, dir, work, attr.ia_mode); work = ovl_do_mkdir(ofs, dir, work, attr.ia_mode);
if (!IS_ERR(work)) if (!IS_ERR(work))
dget(work); dget(work);
end_creating(work, ofs->workbasedir); end_creating(work);
err = PTR_ERR(work); err = PTR_ERR(work);
if (IS_ERR(work)) if (IS_ERR(work))
goto out_err; goto out_err;
@ -632,7 +632,7 @@ static struct dentry *ovl_lookup_or_create(struct ovl_fs *ofs,
OVL_CATTR(mode)); OVL_CATTR(mode));
if (!IS_ERR(child)) if (!IS_ERR(child))
dget(child); dget(child);
end_creating(child, parent); end_creating(child);
} }
dput(parent); dput(parent);

View File

@ -199,7 +199,7 @@ xrep_orphanage_create(
sc->orphanage_ilock_flags = 0; sc->orphanage_ilock_flags = 0;
out_dput_orphanage: out_dput_orphanage:
end_creating(orphanage_dentry, root_dentry); end_creating(orphanage_dentry);
out_dput_root: out_dput_root:
dput(root_dentry); dput(root_dentry);
out: out:

View File

@ -105,34 +105,24 @@ struct dentry *start_creating_dentry(struct dentry *parent,
struct dentry *start_removing_dentry(struct dentry *parent, struct dentry *start_removing_dentry(struct dentry *parent,
struct dentry *child); struct dentry *child);
/** /* end_creating - finish action started with start_creating
* end_creating - finish action started with start_creating * @child: dentry returned by start_creating() or vfs_mkdir()
* @child: dentry returned by start_creating() or vfs_mkdir()
* @parent: dentry given to start_creating(),
* *
* Unlock and release the child. * Unlock and release the child. This can be called after
* * start_creating() whether that function succeeded or not,
* Unlike end_dirop() this can only be called if start_creating() succeeded. * but it is not needed on failure.
* It handles @child being and error as vfs_mkdir() might have converted the
* dentry to an error - in that case the parent still needs to be unlocked.
* *
* If vfs_mkdir() was called then the value returned from that function * If vfs_mkdir() was called then the value returned from that function
* should be given for @child rather than the original dentry, as vfs_mkdir() * should be given for @child rather than the original dentry, as vfs_mkdir()
* may have provided a new dentry. Even if vfs_mkdir() returns an error * may have provided a new dentry.
* it must be given to end_creating(). *
* *
* If vfs_mkdir() was not called, then @child will be a valid dentry and * If vfs_mkdir() was not called, then @child will be a valid dentry and
* @parent will be ignored. * @parent will be ignored.
*/ */
static inline void end_creating(struct dentry *child, struct dentry *parent) static inline void end_creating(struct dentry *child)
{ {
if (IS_ERR(child)) end_dirop(child);
/* The parent is still locked despite the error from
* vfs_mkdir() - must unlock it.
*/
inode_unlock(parent->d_inode);
else
end_dirop(child);
} }
/** /**

View File

@ -932,7 +932,7 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
put_unused_fd(fd); put_unused_fd(fd);
fd = error; fd = error;
} }
end_creating(path.dentry, root); end_creating(path.dentry);
if (!ro) if (!ro)
mnt_drop_write(mnt); mnt_drop_write(mnt);
out_putname: out_putname: