| From a09f99eddef44035ec764075a37bace8181bec38 Mon Sep 17 00:00:00 2001 |
| From: Miklos Szeredi <mszeredi@redhat.com> |
| Date: Sat, 1 Oct 2016 07:32:32 +0200 |
| Subject: fuse: fix killing s[ug]id in setattr |
| |
| From: Miklos Szeredi <mszeredi@redhat.com> |
| |
| commit a09f99eddef44035ec764075a37bace8181bec38 upstream. |
| |
| Fuse allowed VFS to set mode in setattr in order to clear suid/sgid on |
| chown and truncate, and (since writeback_cache) write. The problem with |
| this is that it'll potentially restore a stale mode. |
| |
| The poper fix would be to let the filesystems do the suid/sgid clearing on |
| the relevant operations. Possibly some are already doing it but there's no |
| way we can detect this. |
| |
| So fix this by refreshing and recalculating the mode. Do this only if |
| ATTR_KILL_S[UG]ID is set to not destroy performance for writes. This is |
| still racy but the size of the window is reduced. |
| |
| Signed-off-by: Miklos Szeredi <mszeredi@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/fuse/dir.c | 32 ++++++++++++++++++++++++++++---- |
| 1 file changed, 28 insertions(+), 4 deletions(-) |
| |
| --- a/fs/fuse/dir.c |
| +++ b/fs/fuse/dir.c |
| @@ -1701,16 +1701,40 @@ error: |
| static int fuse_setattr(struct dentry *entry, struct iattr *attr) |
| { |
| struct inode *inode = d_inode(entry); |
| + struct file *file = (attr->ia_valid & ATTR_FILE) ? attr->ia_file : NULL; |
| int ret; |
| |
| if (!fuse_allow_current_process(get_fuse_conn(inode))) |
| return -EACCES; |
| |
| - if (attr->ia_valid & ATTR_FILE) |
| - ret = fuse_do_setattr(inode, attr, attr->ia_file); |
| - else |
| - ret = fuse_do_setattr(inode, attr, NULL); |
| + if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) { |
| + int kill; |
| |
| + attr->ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID | |
| + ATTR_MODE); |
| + /* |
| + * ia_mode calculation may have used stale i_mode. Refresh and |
| + * recalculate. |
| + */ |
| + ret = fuse_do_getattr(inode, NULL, file); |
| + if (ret) |
| + return ret; |
| + |
| + attr->ia_mode = inode->i_mode; |
| + kill = should_remove_suid(entry); |
| + if (kill & ATTR_KILL_SUID) { |
| + attr->ia_valid |= ATTR_MODE; |
| + attr->ia_mode &= ~S_ISUID; |
| + } |
| + if (kill & ATTR_KILL_SGID) { |
| + attr->ia_valid |= ATTR_MODE; |
| + attr->ia_mode &= ~S_ISGID; |
| + } |
| + } |
| + if (!attr->ia_valid) |
| + return 0; |
| + |
| + ret = fuse_do_setattr(inode, attr, file); |
| if (!ret) { |
| /* Directory mode changed, may need to revalidate access */ |
| if (d_is_dir(entry) && (attr->ia_valid & ATTR_MODE)) |