Moving the main logic to a helper function makes the whole thing much easier to follow. Cc: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> Cc: Al Viro <viro@xxxxxxxxxxxxxxxxxx> Cc: Christian Brauner <christian.brauner@xxxxxxxxxx> Suggested-by: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> Link: https://lore.kernel.org/io-uring/CAHk-=wiG+sN+2zSoAOggKCGue2kOJvw3rQySvQXsZstRQFTN+g@xxxxxxxxxxxxxx/ Signed-off-by: Dmitry Kadashev <dkadashev@xxxxxxxxx> --- fs/namei.c | 80 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index c9110ac83ccb..5e4fa8b65a8d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4380,48 +4380,22 @@ int vfs_link(struct dentry *old_dentry, struct user_namespace *mnt_userns, } EXPORT_SYMBOL(vfs_link); -/* - * Hardlinks are often used in delicate situations. We avoid - * security-related surprises by not following symlinks on the - * newname. --KAB - * - * We don't follow them on the oldname either to be compatible - * with linux 2.0, and to avoid hard-linking to directories - * and other special files. --ADM - */ -int do_linkat(int olddfd, struct filename *old, int newdfd, - struct filename *new, int flags) +static int linkat_helper(int olddfd, struct filename *old, int newdfd, + struct filename *new, unsigned int lookup_flags) { struct user_namespace *mnt_userns; struct dentry *new_dentry; struct path old_path, new_path; struct inode *delegated_inode = NULL; - int how = 0; int error; - if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0) { - error = -EINVAL; - goto out_putnames; - } - /* - * To use null names we require CAP_DAC_READ_SEARCH - * This ensures that not everyone will be able to create - * handlink using the passed filedescriptor. - */ - if (flags & AT_EMPTY_PATH && !capable(CAP_DAC_READ_SEARCH)) { - error = -ENOENT; - goto out_putnames; - } - - if (flags & AT_SYMLINK_FOLLOW) - how |= LOOKUP_FOLLOW; retry: - error = __filename_lookup(olddfd, old, how, &old_path, NULL); + error = __filename_lookup(olddfd, old, lookup_flags, &old_path, NULL); if (error) - goto out_putnames; + return error; new_dentry = __filename_create(newdfd, new, &new_path, - (how & LOOKUP_REVAL)); + (lookup_flags & LOOKUP_REVAL)); error = PTR_ERR(new_dentry); if (IS_ERR(new_dentry)) goto out_putpath; @@ -4447,14 +4421,48 @@ int do_linkat(int olddfd, struct filename *old, int newdfd, goto retry; } } +out_putpath: + path_put(&old_path); + return error; +} + +/* + * Hardlinks are often used in delicate situations. We avoid + * security-related surprises by not following symlinks on the + * newname. --KAB + * + * We don't follow them on the oldname either to be compatible + * with linux 2.0, and to avoid hard-linking to directories + * and other special files. --ADM + */ +int do_linkat(int olddfd, struct filename *old, int newdfd, + struct filename *new, int flags) +{ + int error, how = 0; + + if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0) { + error = -EINVAL; + goto out; + } + /* + * To use null names we require CAP_DAC_READ_SEARCH + * This ensures that not everyone will be able to create + * handlink using the passed filedescriptor. + */ + if (flags & AT_EMPTY_PATH && !capable(CAP_DAC_READ_SEARCH)) { + error = -ENOENT; + goto out; + } + + if (flags & AT_SYMLINK_FOLLOW) + how |= LOOKUP_FOLLOW; + + error = linkat_helper(olddfd, old, newdfd, new, how); if (retry_estale(error, how)) { - path_put(&old_path); how |= LOOKUP_REVAL; - goto retry; + error = linkat_helper(olddfd, old, newdfd, new, how); } -out_putpath: - path_put(&old_path); -out_putnames: +out: putname(old); putname(new); -- 2.30.2