In 0ee50b47532a ("namei: change filename_parentat() calling conventions"), filename_parentat() was made to always call putname() on the filename before returning, and kern_path_locked() was migrated to this calling convention. However, kern_path_locked() uses the "last" parameter to lookup and potentially create a new dentry. The last parameter contains the last component of the path and points within the filename, which was recently freed at the end of filename_parentat(). Thus, when kern_path_locked() calls __lookup_hash(), it is using the filename after it has already been freed. In this case, filename_parentat() is fundamentally broken due to this use after free. So, remove it, and rename __filename_parentat to filename_parentat, migrating all callers. Adjust kern_path_locked to put the filename once all users are done with it. Fixes: 0ee50b47532a ("namei: change filename_parentat() calling conventions") Link: https://lore.kernel.org/linux-fsdevel/YS9D4AlEsaCxLFV0@xxxxxxxxxxxxx/ Link: https://lore.kernel.org/linux-fsdevel/YS+csMTV2tTXKg3s@xxxxxxxxxxxxxxxxxxxxx/ Cc: Christoph Hellwig <hch@xxxxxxxxxxxxx> Cc: Al Viro <viro@xxxxxxxxxxxxxxxxxx> Reported-by: syzbot+fb0d60a179096e8c2731@xxxxxxxxxxxxxxxxxxxxxxxxx Signed-off-by: Stephen Brennan <stephen.s.brennan@xxxxxxxxxx> Co-authored-by: Dmitry Kadashev <dkadashev@xxxxxxxxx> --- fs/namei.c | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index d049d3972695..f2af301cc79f 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2514,9 +2514,10 @@ static int path_parentat(struct nameidata *nd, unsigned flags, return err; } -static int __filename_parentat(int dfd, struct filename *name, - unsigned int flags, struct path *parent, - struct qstr *last, int *type) +/* Note: this does not consume "name" */ +static int filename_parentat(int dfd, struct filename *name, + unsigned int flags, struct path *parent, + struct qstr *last, int *type) { int retval; struct nameidata nd; @@ -2538,30 +2539,24 @@ static int __filename_parentat(int dfd, struct filename *name, return retval; } -static int filename_parentat(int dfd, struct filename *name, - unsigned int flags, struct path *parent, - struct qstr *last, int *type) -{ - int retval = __filename_parentat(dfd, name, flags, parent, last, type); - - putname(name); - return retval; -} - /* does lookup, returns the object with parent locked */ struct dentry *kern_path_locked(const char *name, struct path *path) { + struct filename *filename; struct dentry *d; struct qstr last; int type, error; - error = filename_parentat(AT_FDCWD, getname_kernel(name), 0, path, - &last, &type); - if (error) - return ERR_PTR(error); + filename = getname_kernel(name); + error = filename_parentat(AT_FDCWD, filename, 0, path, &last, &type); + if (error) { + d = ERR_PTR(error); + goto out; + } if (unlikely(type != LAST_NORM)) { path_put(path); - return ERR_PTR(-EINVAL); + d = ERR_PTR(-EINVAL); + goto out; } inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT); d = __lookup_hash(&last, path->dentry, 0); @@ -2569,6 +2564,8 @@ struct dentry *kern_path_locked(const char *name, struct path *path) inode_unlock(path->dentry->d_inode); path_put(path); } +out: + putname(filename); return d; } @@ -3634,7 +3631,7 @@ static struct dentry *__filename_create(int dfd, struct filename *name, */ lookup_flags &= LOOKUP_REVAL; - error = __filename_parentat(dfd, name, lookup_flags, path, &last, &type); + error = filename_parentat(dfd, name, lookup_flags, path, &last, &type); if (error) return ERR_PTR(error); @@ -3996,7 +3993,7 @@ int do_rmdir(int dfd, struct filename *name) int type; unsigned int lookup_flags = 0; retry: - error = __filename_parentat(dfd, name, lookup_flags, &path, &last, &type); + error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type); if (error) goto exit1; @@ -4135,7 +4132,7 @@ int do_unlinkat(int dfd, struct filename *name) struct inode *delegated_inode = NULL; unsigned int lookup_flags = 0; retry: - error = __filename_parentat(dfd, name, lookup_flags, &path, &last, &type); + error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type); if (error) goto exit1; @@ -4683,13 +4680,13 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd, target_flags = 0; retry: - error = __filename_parentat(olddfd, from, lookup_flags, &old_path, - &old_last, &old_type); + error = filename_parentat(olddfd, from, lookup_flags, &old_path, + &old_last, &old_type); if (error) goto put_names; - error = __filename_parentat(newdfd, to, lookup_flags, &new_path, &new_last, - &new_type); + error = filename_parentat(newdfd, to, lookup_flags, &new_path, &new_last, + &new_type); if (error) goto exit1; -- 2.30.2