Преглед изворни кода

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>
Jan Kara пре 9 година
родитељ
комит
073931017b
16 измењених фајлова са 89 додато и 102 уклоњено
  1. 17 23
      fs/9p/acl.c
  2. 2 4
      fs/btrfs/acl.c
  3. 2 4
      fs/ceph/acl.c
  4. 4 8
      fs/ext2/acl.c
  5. 4 8
      fs/ext4/acl.c
  6. 2 4
      fs/f2fs/acl.c
  7. 3 9
      fs/gfs2/acl.c
  8. 2 2
      fs/hfsplus/posix_acl.c
  9. 4 5
      fs/jffs2/acl.c
  10. 2 4
      fs/jfs/acl.c
  11. 4 6
      fs/ocfs2/acl.c
  12. 5 10
      fs/orangefs/acl.c
  13. 31 0
      fs/posix_acl.c
  14. 2 6
      fs/reiserfs/xattr_acl.c
  15. 4 9
      fs/xfs/xfs_acl.c
  16. 1 0
      include/linux/posix_acl.h

+ 17 - 23
fs/9p/acl.c

@@ -276,32 +276,26 @@ static int v9fs_xattr_set_acl(const struct xattr_handler *handler,
 	switch (handler->flags) {
 	switch (handler->flags) {
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		if (acl) {
 		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;
 				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;
 		break;
 	case ACL_TYPE_DEFAULT:
 	case ACL_TYPE_DEFAULT:

+ 2 - 4
fs/btrfs/acl.c

@@ -79,11 +79,9 @@ static int __btrfs_set_acl(struct btrfs_trans_handle *trans,
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				return ret;
-			if (ret == 0)
-				acl = NULL;
 		}
 		}
 		ret = 0;
 		ret = 0;
 		break;
 		break;

+ 2 - 4
fs/ceph/acl.c

@@ -95,11 +95,9 @@ int ceph_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				goto out;
-			if (ret == 0)
-				acl = NULL;
 		}
 		}
 		break;
 		break;
 	case ACL_TYPE_DEFAULT:
 	case ACL_TYPE_DEFAULT:

+ 4 - 8
fs/ext2/acl.c

@@ -190,15 +190,11 @@ ext2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 		case ACL_TYPE_ACCESS:
 		case ACL_TYPE_ACCESS:
 			name_index = EXT2_XATTR_INDEX_POSIX_ACL_ACCESS;
 			name_index = EXT2_XATTR_INDEX_POSIX_ACL_ACCESS;
 			if (acl) {
 			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;
 					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;
 			break;
 
 

+ 4 - 8
fs/ext4/acl.c

@@ -193,15 +193,11 @@ __ext4_set_acl(handle_t *handle, struct inode *inode, int type,
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
 		name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				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;
 		break;
 
 

+ 2 - 4
fs/f2fs/acl.c

@@ -210,12 +210,10 @@ static int __f2fs_set_acl(struct inode *inode, int type,
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		name_index = F2FS_XATTR_INDEX_POSIX_ACL_ACCESS;
 		name_index = F2FS_XATTR_INDEX_POSIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				return error;
 			set_acl_inode(inode, inode->i_mode);
 			set_acl_inode(inode, inode->i_mode);
-			if (error == 0)
-				acl = NULL;
 		}
 		}
 		break;
 		break;
 
 

+ 3 - 9
fs/gfs2/acl.c

@@ -92,17 +92,11 @@ int __gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 	if (type == ACL_TYPE_ACCESS) {
 	if (type == ACL_TYPE_ACCESS) {
 		umode_t mode = inode->i_mode;
 		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;
 			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);
 			mark_inode_dirty(inode);
-		}
 	}
 	}
 
 
 	if (acl) {
 	if (acl) {

+ 2 - 2
fs/hfsplus/posix_acl.c

@@ -65,8 +65,8 @@ int hfsplus_set_posix_acl(struct inode *inode, struct posix_acl *acl,
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
 		xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				return err;
 		}
 		}
 		err = 0;
 		err = 0;

+ 4 - 5
fs/jffs2/acl.c

@@ -233,9 +233,10 @@ int jffs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		xprefix = JFFS2_XPREFIX_ACL_ACCESS;
 		xprefix = JFFS2_XPREFIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				return rc;
 			if (inode->i_mode != mode) {
 			if (inode->i_mode != mode) {
 				struct iattr attr;
 				struct iattr attr;
@@ -247,8 +248,6 @@ int jffs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 				if (rc < 0)
 				if (rc < 0)
 					return rc;
 					return rc;
 			}
 			}
-			if (rc == 0)
-				acl = NULL;
 		}
 		}
 		break;
 		break;
 	case ACL_TYPE_DEFAULT:
 	case ACL_TYPE_DEFAULT:

+ 2 - 4
fs/jfs/acl.c

@@ -78,13 +78,11 @@ static int __jfs_set_acl(tid_t tid, struct inode *inode, int type,
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		ea_name = XATTR_NAME_POSIX_ACL_ACCESS;
 		ea_name = XATTR_NAME_POSIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				return rc;
 			inode->i_ctime = CURRENT_TIME;
 			inode->i_ctime = CURRENT_TIME;
 			mark_inode_dirty(inode);
 			mark_inode_dirty(inode);
-			if (rc == 0)
-				acl = NULL;
 		}
 		}
 		break;
 		break;
 	case ACL_TYPE_DEFAULT:
 	case ACL_TYPE_DEFAULT:

+ 4 - 6
fs/ocfs2/acl.c

@@ -241,13 +241,11 @@ int ocfs2_set_acl(handle_t *handle,
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		name_index = OCFS2_XATTR_INDEX_POSIX_ACL_ACCESS;
 		name_index = OCFS2_XATTR_INDEX_POSIX_ACL_ACCESS;
 		if (acl) {
 		if (acl) {
-			umode_t mode = inode->i_mode;
-			ret = posix_acl_equiv_mode(acl, &mode);
-			if (ret < 0)
-				return ret;
+			umode_t mode;
 
 
-			if (ret == 0)
-				acl = NULL;
+			ret = posix_acl_update_mode(inode, &mode, &acl);
+			if (ret)
+				return ret;
 
 
 			ret = ocfs2_acl_set_mode(inode, di_bh,
 			ret = ocfs2_acl_set_mode(inode, di_bh,
 						 handle, mode);
 						 handle, mode);

+ 5 - 10
fs/orangefs/acl.c

@@ -73,14 +73,11 @@ int orangefs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		if (acl) {
 		if (acl) {
-			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",
+			umode_t mode;
+
+			error = posix_acl_update_mode(inode, &mode, &acl);
+			if (error) {
+				gossip_err("%s: posix_acl_update_mode err: %d\n",
 					   __func__,
 					   __func__,
 					   error);
 					   error);
 				return error;
 				return error;
@@ -90,8 +87,6 @@ int orangefs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 				SetModeFlag(orangefs_inode);
 				SetModeFlag(orangefs_inode);
 			inode->i_mode = mode;
 			inode->i_mode = mode;
 			mark_inode_dirty_sync(inode);
 			mark_inode_dirty_sync(inode);
-			if (error == 0)
-				acl = NULL;
 		}
 		}
 		break;
 		break;
 	case ACL_TYPE_DEFAULT:
 	case ACL_TYPE_DEFAULT:

+ 31 - 0
fs/posix_acl.c

@@ -626,6 +626,37 @@ no_mem:
 }
 }
 EXPORT_SYMBOL_GPL(posix_acl_create);
 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.
  * Fix up the uids and gids in posix acl extended attributes in place.
  */
  */

+ 2 - 6
fs/reiserfs/xattr_acl.c

@@ -242,13 +242,9 @@ __reiserfs_set_acl(struct reiserfs_transaction_handle *th, struct inode *inode,
 	case ACL_TYPE_ACCESS:
 	case ACL_TYPE_ACCESS:
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		name = XATTR_NAME_POSIX_ACL_ACCESS;
 		if (acl) {
 		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;
 				return error;
-			else {
-				if (error == 0)
-					acl = NULL;
-			}
 		}
 		}
 		break;
 		break;
 	case ACL_TYPE_DEFAULT:
 	case ACL_TYPE_DEFAULT:

+ 4 - 9
fs/xfs/xfs_acl.c

@@ -257,16 +257,11 @@ xfs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 		return error;
 		return error;
 
 
 	if (type == ACL_TYPE_ACCESS) {
 	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);
 		error = xfs_set_mode(inode, mode);
 		if (error)
 		if (error)
 			return error;
 			return error;

+ 1 - 0
include/linux/posix_acl.h

@@ -93,6 +93,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_chmod(struct inode *, umode_t);
 extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **,
 extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **,
 		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_set_acl(struct inode *, struct posix_acl *, int);
 extern int simple_acl_create(struct inode *, struct inode *);
 extern int simple_acl_create(struct inode *, struct inode *);