CVE-2016-7097 (C, published 2016-10-16T21:59Z)
Description: value: The filesystem implementation in the Linux kernel through 4.8.2 preserves the setgid bit during a setxattr call, which allows local users to gain group privileges by leveraging the existence of a setgid program with restrictions on execute permissions.
Severity: LOW (CVSS v3 base score: 4.4)
Weakness: CWE-285 \u2014 Improper Authorization
The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.
Repository: https://github.com/torvalds/linux
Files changed:
fs/9p/acl.c
fs/btrfs/acl.c
fs/ceph/acl.c
fs/ext2/acl.c
fs/ext4/acl.c
fs/f2fs/acl.c
fs/gfs2/acl.c
fs/hfsplus/posix_acl.c
fs/jffs2/acl.c
fs/jfs/acl.c
fs/ocfs2/acl.c
fs/orangefs/acl.c
fs/posix_acl.c
fs/reiserfs/xattr_acl.c
fs/xfs/xfs_acl.c
include/linux/posix_acl.h
Commit message: posix_acl: Clear SGID bit when setting file permissions

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 <hch@lst.de>
Reviewed-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>


Vulnerable code:
			umode_t mode = inode->i_mode;
			retval = posix_acl_equiv_mode(acl, &mode);
			if (retval < 0)
			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 ?
				v9fs_vfs_setattr_dotl(dentry, &iattr);
			ret = posix_acl_equiv_mode(acl, &inode->i_mode);
			if (ret < 0)
			if (ret == 0)
				acl = NULL;
			ret = posix_acl_equiv_mode(acl, &new_mode);
			if (ret < 0)
			if (ret == 0)
				acl = NULL;
				error = posix_acl_equiv_mode(acl, &inode->i_mode);
				if (error < 0)
				else {
					inode->i_ctime = CURRENT_TIME_SEC;
					mark_inode_dirty(inode);
					if (error == 0)
						acl = NULL;
				}
			error = posix_acl_equiv_mode(acl, &inode->i_mode);
			if (error < 0)
			else {
				inode->i_ctime = ext4_current_time(inode);
				ext4_mark_inode_dirty(handle, inode);
				if (error == 0)
					acl = NULL;
			}
			error = posix_acl_equiv_mode(acl, &inode->i_mode);
			if (error < 0)
			if (error == 0)
				acl = NULL;
		error = posix_acl_equiv_mode(acl, &mode);
		if (error < 0)

		if (error == 0)
			acl = NULL;

		if (mode != inode->i_mode) {
			inode->i_mode = mode;
		}
			err = posix_acl_equiv_mode(acl, &inode->i_mode);
			if (err < 0)
			umode_t mode = inode->i_mode;
			rc = posix_acl_equiv_mode(acl, &mode);
			if (rc < 0)
			if (rc == 0)
				acl = NULL;
			rc = posix_acl_equiv_mode(acl, &inode->i_mode);
			if (rc < 0)
			if (rc == 0)
				acl = NULL;
			umode_t mode = inode->i_mode;
			ret = posix_acl_equiv_mode(acl, &mode);
			if (ret < 0)
				return ret;
			if (ret == 0)
				acl = NULL;
			umode_t mode = inode->i_mode;
			/*
			 * can we represent this with the traditional file
			 * mode permission bits?
			 */
			error = posix_acl_equiv_mode(acl, &mode);
			if (error < 0) {
				gossip_err("%s: posix_acl_equiv_mode err: %d\n",
			if (error == 0)
				acl = NULL;
			error = posix_acl_equiv_mode(acl, &inode->i_mode);
			if (error < 0)
			else {
				if (error == 0)
					acl = NULL;
			}
		umode_t mode = inode->i_mode;
		error = posix_acl_equiv_mode(acl, &mode);

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

			if (error < 0)
				return error;
		}

Fixed code:
			struct iattr iattr;

			retval = posix_acl_update_mode(inode, &iattr.ia_mode, &acl);
			if (retval)
			if (!acl) {
				/*
				 * ACL can be represented
				 * by the mode bits. So don't
				 * update ACL.
				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);
			ret = posix_acl_update_mode(inode, &inode->i_mode, &acl);
			if (ret)
			ret = posix_acl_update_mode(inode, &new_mode, &acl);
			if (ret)
				error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
				if (error)
				inode->i_ctime = CURRENT_TIME_SEC;
				mark_inode_dirty(inode);
			error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
			if (error)
			inode->i_ctime = ext4_current_time(inode);
			ext4_mark_inode_dirty(handle, inode);
			error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
			if (error)
		error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
		if (error)
		if (mode != inode->i_mode)
			err = posix_acl_update_mode(inode, &inode->i_mode, &acl);
			if (err)
			umode_t mode;

			rc = posix_acl_update_mode(inode, &mode, &acl);
			if (rc)
			rc = posix_acl_update_mode(inode, &inode->i_mode, &acl);
			if (rc)
			umode_t mode;
			ret = posix_acl_update_mode(inode, &mode, &acl);
			if (ret)
				return ret;
			umode_t mode;

			error = posix_acl_update_mode(inode, &mode, &acl);
			if (error) {
				gossip_err("%s: posix_acl_update_mode err: %d\n",
/**
 * 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);

			error = posix_acl_update_mode(inode, &inode->i_mode, &acl);
			if (error)
		umode_t mode;
		error = posix_acl_update_mode(inode, &mode, &acl);
		if (error)
			return error;
extern int posix_acl_update_mode(struct inode *, umode_t *, struct posix_acl **);