| From: Al Viro <viro@zeniv.linux.org.uk> |
| Date: Sun, 26 Jan 2020 09:29:34 -0500 |
| Subject: do_last(): fetch directory ->i_mode and ->i_uid before it's too late |
| |
| commit d0cb50185ae942b03c4327be322055d622dc79f6 upstream. |
| |
| may_create_in_sticky() call is done when we already have dropped the |
| reference to dir. |
| |
| Fixes: 30aba6656f61e (namei: allow restricted O_CREAT of FIFOs and regular files) |
| Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| fs/namei.c | 17 ++++++++++------- |
| 1 file changed, 10 insertions(+), 7 deletions(-) |
| |
| --- a/fs/namei.c |
| +++ b/fs/namei.c |
| @@ -843,7 +843,8 @@ static int may_linkat(struct path *link) |
| * may_create_in_sticky - Check whether an O_CREAT open in a sticky directory |
| * should be allowed, or not, on files that already |
| * exist. |
| - * @dir: the sticky parent directory |
| + * @dir_mode: mode bits of directory |
| + * @dir_uid: owner of directory |
| * @inode: the inode of the file to open |
| * |
| * Block an O_CREAT open of a FIFO (or a regular file) when: |
| @@ -859,18 +860,18 @@ static int may_linkat(struct path *link) |
| * |
| * Returns 0 if the open is allowed, -ve on error. |
| */ |
| -static int may_create_in_sticky(struct dentry * const dir, |
| +static int may_create_in_sticky(umode_t dir_mode, kuid_t dir_uid, |
| struct inode * const inode) |
| { |
| if ((!sysctl_protected_fifos && S_ISFIFO(inode->i_mode)) || |
| (!sysctl_protected_regular && S_ISREG(inode->i_mode)) || |
| - likely(!(dir->d_inode->i_mode & S_ISVTX)) || |
| - uid_eq(inode->i_uid, dir->d_inode->i_uid) || |
| + likely(!(dir_mode & S_ISVTX)) || |
| + uid_eq(inode->i_uid, dir_uid) || |
| uid_eq(current_fsuid(), inode->i_uid)) |
| return 0; |
| |
| - if (likely(dir->d_inode->i_mode & 0002) || |
| - (dir->d_inode->i_mode & 0020 && |
| + if (likely(dir_mode & 0002) || |
| + (dir_mode & 0020 && |
| ((sysctl_protected_fifos >= 2 && S_ISFIFO(inode->i_mode)) || |
| (sysctl_protected_regular >= 2 && S_ISREG(inode->i_mode))))) { |
| return -EACCES; |
| @@ -2944,6 +2945,8 @@ static int do_last(struct nameidata *nd, |
| int *opened, struct filename *name) |
| { |
| struct dentry *dir = nd->path.dentry; |
| + kuid_t dir_uid = dir->d_inode->i_uid; |
| + umode_t dir_mode = dir->d_inode->i_mode; |
| int open_flag = op->open_flag; |
| bool will_truncate = (open_flag & O_TRUNC) != 0; |
| bool got_write = false; |
| @@ -3102,7 +3105,7 @@ finish_open: |
| error = -EISDIR; |
| if (d_is_dir(nd->path.dentry)) |
| goto out; |
| - error = may_create_in_sticky(dir, |
| + error = may_create_in_sticky(dir_mode, dir_uid, |
| d_backing_inode(nd->path.dentry)); |
| if (unlikely(error)) |
| goto out; |