Skip to content

Commit

Permalink
posix_acl: Clear SGID bit when setting file permissions
Browse files Browse the repository at this point in the history
commit 073931017b49d9458aa351605b43a7e34598caef upstream.

When file permissions are modified via chmod(2) and the user is not in
the owning group or capable of CAP_FSETID, the setgid bit is cleared in
inode_change_ok().  Setting a POSIX ACL via setxattr(2) sets the file
permissions as well as the new ACL, but doesn't clear the setgid bit in
a similar way; this allows to bypass the check in chmod(2).  Fix that.

References: CVE-2016-7097
Reviewed-by: Christoph Hellwig <[email protected]>
Reviewed-by: Jeff Layton <[email protected]>
Signed-off-by: Jan Kara <[email protected]>
Signed-off-by: Andreas Gruenbacher <[email protected]>
Signed-off-by: Amit Pundir <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
jankara authored and gregkh committed May 8, 2017
1 parent 5a7b3b1 commit d8333c0
Show file tree
Hide file tree
Showing 15 changed files with 88 additions and 98 deletions.
40 changes: 17 additions & 23 deletions fs/9p/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -320,32 +320,26 @@ static int v9fs_xattr_set_acl(struct dentry *dentry, const char *name,
case ACL_TYPE_ACCESS:
name = POSIX_ACL_XATTR_ACCESS;
if (acl) {
umode_t mode = inode->i_mode;
retval = posix_acl_equiv_mode(acl, &mode);
if (retval < 0)
struct iattr iattr;

retval = posix_acl_update_mode(inode, &iattr.ia_mode, &acl);
if (retval)
goto err_out;
else {
struct iattr iattr;
if (retval == 0) {
/*
* ACL can be represented
* by the mode bits. So don't
* update ACL.
*/
acl = NULL;
value = NULL;
size = 0;
}
/* Updte the mode bits */
iattr.ia_mode = ((mode & S_IALLUGO) |
(inode->i_mode & ~S_IALLUGO));
iattr.ia_valid = ATTR_MODE;
/* FIXME should we update ctime ?
* What is the following setxattr update the
* mode ?
if (!acl) {
/*
* ACL can be represented
* by the mode bits. So don't
* update ACL.
*/
v9fs_vfs_setattr_dotl(dentry, &iattr);
value = NULL;
size = 0;
}
iattr.ia_valid = ATTR_MODE;
/* FIXME should we update ctime ?
* What is the following setxattr update the
* mode ?
*/
v9fs_vfs_setattr_dotl(dentry, &iattr);
}
break;
case ACL_TYPE_DEFAULT:
Expand Down
6 changes: 2 additions & 4 deletions fs/btrfs/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,9 @@ static int __btrfs_set_acl(struct btrfs_trans_handle *trans,
case ACL_TYPE_ACCESS:
name = POSIX_ACL_XATTR_ACCESS;
if (acl) {
ret = posix_acl_equiv_mode(acl, &inode->i_mode);
if (ret < 0)
ret = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (ret)
return ret;
if (ret == 0)
acl = NULL;
}
ret = 0;
break;
Expand Down
6 changes: 2 additions & 4 deletions fs/ceph/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,9 @@ int ceph_set_acl(struct inode *inode, struct posix_acl *acl, int type)
case ACL_TYPE_ACCESS:
name = POSIX_ACL_XATTR_ACCESS;
if (acl) {
ret = posix_acl_equiv_mode(acl, &new_mode);
if (ret < 0)
ret = posix_acl_update_mode(inode, &new_mode, &acl);
if (ret)
goto out;
if (ret == 0)
acl = NULL;
}
break;
case ACL_TYPE_DEFAULT:
Expand Down
12 changes: 4 additions & 8 deletions fs/ext2/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,11 @@ ext2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
case ACL_TYPE_ACCESS:
name_index = EXT2_XATTR_INDEX_POSIX_ACL_ACCESS;
if (acl) {
error = posix_acl_equiv_mode(acl, &inode->i_mode);
if (error < 0)
error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (error)
return error;
else {
inode->i_ctime = CURRENT_TIME_SEC;
mark_inode_dirty(inode);
if (error == 0)
acl = NULL;
}
inode->i_ctime = CURRENT_TIME_SEC;
mark_inode_dirty(inode);
}
break;

Expand Down
12 changes: 4 additions & 8 deletions fs/ext4/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,11 @@ __ext4_set_acl(handle_t *handle, struct inode *inode, int type,
case ACL_TYPE_ACCESS:
name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
if (acl) {
error = posix_acl_equiv_mode(acl, &inode->i_mode);
if (error < 0)
error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (error)
return error;
else {
inode->i_ctime = ext4_current_time(inode);
ext4_mark_inode_dirty(handle, inode);
if (error == 0)
acl = NULL;
}
inode->i_ctime = ext4_current_time(inode);
ext4_mark_inode_dirty(handle, inode);
}
break;

Expand Down
6 changes: 2 additions & 4 deletions fs/f2fs/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,10 @@ static int __f2fs_set_acl(struct inode *inode, int type,
case ACL_TYPE_ACCESS:
name_index = F2FS_XATTR_INDEX_POSIX_ACL_ACCESS;
if (acl) {
error = posix_acl_equiv_mode(acl, &inode->i_mode);
if (error < 0)
error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (error)
return error;
set_acl_inode(fi, inode->i_mode);
if (error == 0)
acl = NULL;
}
break;

Expand Down
12 changes: 3 additions & 9 deletions fs/gfs2/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,11 @@ int gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
if (type == ACL_TYPE_ACCESS) {
umode_t mode = inode->i_mode;

error = posix_acl_equiv_mode(acl, &mode);
if (error < 0)
error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (error)
return error;

if (error == 0)
acl = NULL;

if (mode != inode->i_mode) {
inode->i_mode = mode;
if (mode != inode->i_mode)
mark_inode_dirty(inode);
}
}

if (acl) {
Expand Down
4 changes: 2 additions & 2 deletions fs/hfsplus/posix_acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ int hfsplus_set_posix_acl(struct inode *inode, struct posix_acl *acl,
case ACL_TYPE_ACCESS:
xattr_name = POSIX_ACL_XATTR_ACCESS;
if (acl) {
err = posix_acl_equiv_mode(acl, &inode->i_mode);
if (err < 0)
err = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (err)
return err;
}
err = 0;
Expand Down
9 changes: 4 additions & 5 deletions fs/jffs2/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,10 @@ int jffs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
case ACL_TYPE_ACCESS:
xprefix = JFFS2_XPREFIX_ACL_ACCESS;
if (acl) {
umode_t mode = inode->i_mode;
rc = posix_acl_equiv_mode(acl, &mode);
if (rc < 0)
umode_t mode;

rc = posix_acl_update_mode(inode, &mode, &acl);
if (rc)
return rc;
if (inode->i_mode != mode) {
struct iattr attr;
Expand All @@ -249,8 +250,6 @@ int jffs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
if (rc < 0)
return rc;
}
if (rc == 0)
acl = NULL;
}
break;
case ACL_TYPE_DEFAULT:
Expand Down
6 changes: 2 additions & 4 deletions fs/jfs/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,11 @@ static int __jfs_set_acl(tid_t tid, struct inode *inode, int type,
case ACL_TYPE_ACCESS:
ea_name = POSIX_ACL_XATTR_ACCESS;
if (acl) {
rc = posix_acl_equiv_mode(acl, &inode->i_mode);
if (rc < 0)
rc = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (rc)
return rc;
inode->i_ctime = CURRENT_TIME;
mark_inode_dirty(inode);
if (rc == 0)
acl = NULL;
}
break;
case ACL_TYPE_DEFAULT:
Expand Down
20 changes: 8 additions & 12 deletions fs/ocfs2/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -241,20 +241,16 @@ int ocfs2_set_acl(handle_t *handle,
case ACL_TYPE_ACCESS:
name_index = OCFS2_XATTR_INDEX_POSIX_ACL_ACCESS;
if (acl) {
umode_t mode = inode->i_mode;
ret = posix_acl_equiv_mode(acl, &mode);
if (ret < 0)
return ret;
else {
if (ret == 0)
acl = NULL;
umode_t mode;

ret = ocfs2_acl_set_mode(inode, di_bh,
handle, mode);
if (ret)
return ret;
ret = posix_acl_update_mode(inode, &mode, &acl);
if (ret)
return ret;

}
ret = ocfs2_acl_set_mode(inode, di_bh,
handle, mode);
if (ret)
return ret;
}
break;
case ACL_TYPE_DEFAULT:
Expand Down
31 changes: 31 additions & 0 deletions fs/posix_acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,37 @@ posix_acl_create(struct inode *dir, umode_t *mode,
}
EXPORT_SYMBOL_GPL(posix_acl_create);

/**
* posix_acl_update_mode - update mode in set_acl
*
* Update the file mode when setting an ACL: compute the new file permission
* bits based on the ACL. In addition, if the ACL is equivalent to the new
* file mode, set *acl to NULL to indicate that no ACL should be set.
*
* As with chmod, clear the setgit bit if the caller is not in the owning group
* or capable of CAP_FSETID (see inode_change_ok).
*
* Called from set_acl inode operations.
*/
int posix_acl_update_mode(struct inode *inode, umode_t *mode_p,
struct posix_acl **acl)
{
umode_t mode = inode->i_mode;
int error;

error = posix_acl_equiv_mode(*acl, &mode);
if (error < 0)
return error;
if (error == 0)
*acl = NULL;
if (!in_group_p(inode->i_gid) &&
!capable_wrt_inode_uidgid(inode, CAP_FSETID))
mode &= ~S_ISGID;
*mode_p = mode;
return 0;
}
EXPORT_SYMBOL(posix_acl_update_mode);

/*
* Fix up the uids and gids in posix acl extended attributes in place.
*/
Expand Down
8 changes: 2 additions & 6 deletions fs/reiserfs/xattr_acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,9 @@ __reiserfs_set_acl(struct reiserfs_transaction_handle *th, struct inode *inode,
case ACL_TYPE_ACCESS:
name = POSIX_ACL_XATTR_ACCESS;
if (acl) {
error = posix_acl_equiv_mode(acl, &inode->i_mode);
if (error < 0)
error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
if (error)
return error;
else {
if (error == 0)
acl = NULL;
}
}
break;
case ACL_TYPE_DEFAULT:
Expand Down
13 changes: 4 additions & 9 deletions fs/xfs/xfs_acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,16 +286,11 @@ xfs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
return error;

if (type == ACL_TYPE_ACCESS) {
umode_t mode = inode->i_mode;
error = posix_acl_equiv_mode(acl, &mode);

if (error <= 0) {
acl = NULL;

if (error < 0)
return error;
}
umode_t mode;

error = posix_acl_update_mode(inode, &mode, &acl);
if (error)
return error;
error = xfs_set_mode(inode, mode);
if (error)
return error;
Expand Down
1 change: 1 addition & 0 deletions include/linux/posix_acl.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ extern int set_posix_acl(struct inode *, int, struct posix_acl *);
extern int posix_acl_chmod(struct inode *, umode_t);
extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **,
struct posix_acl **);
extern int posix_acl_update_mode(struct inode *, umode_t *, struct posix_acl **);

extern int simple_set_acl(struct inode *, struct posix_acl *, int);
extern int simple_acl_create(struct inode *, struct inode *);
Expand Down

0 comments on commit d8333c0

Please sign in to comment.