CVE-2014-4014: Permissions, Privileges, and Access Controls (CWE-264)
value: The capabilities implementation in the Linux kernel before 3.14.8 does not properly consider that namespaces are inapplicable to inodes, which allows local users to bypass intended chmod restrictions by first creating a user namespace, as demonstrated by setting the setgid bit on a file with group ownership of root.
CWE description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Published: 2014-06-23T11:21Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: fs/attr.c, fs/inode.c, fs/namei.c, fs/xfs/xfs_ioctl.c, include/linux/capability.h, kernel/capability.c
Commit date: 2014-06-10 13:57:22 -0700
Commit message: fs,userns: Change inode_capable to capable_wrt_inode_uidgid

The kernel has no concept of capabilities with respect to inodes; inodes
exist independently of namespaces.  For example, inode_capable(inode,
CAP_LINUX_IMMUTABLE) would be nonsense.

This patch changes inode_capable to check for uid and gid mappings and
renames it to capable_wrt_inode_uidgid, which should make it more
obvious what it does.

Fixes CVE-2014-4014.

Cc: Theodore Ts'o <tytso@mit.edu>
Cc: Serge Hallyn <serge.hallyn@ubuntu.com>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Dave Chinner <david@fromorbit.com>
Cc: stable@vger.kernel.org
Signed-off-by: Andy Lutomirski <luto@amacapital.net>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Diff: diff --git a/fs/attr.c b/fs/attr.c
index 5d4e59d56e854a..6530ced19697d4 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -48,28 +48,28 @@ int inode_change_ok(const struct inode *inode, struct iattr *attr)
 
 	/* Make sure a caller can chown. */
 	if ((ia_valid & ATTR_UID) &&
 	    (!uid_eq(current_fsuid(), inode->i_uid) ||
 	     !uid_eq(attr->ia_uid, inode->i_uid)) &&
-	    !inode_capable(inode, CAP_CHOWN))
+	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
 		return -EPERM;
 
 	/* Make sure caller can chgrp. */
 	if ((ia_valid & ATTR_GID) &&
 	    (!uid_eq(current_fsuid(), inode->i_uid) ||
 	    (!in_group_p(attr->ia_gid) && !gid_eq(attr->ia_gid, inode->i_gid))) &&
-	    !inode_capable(inode, CAP_CHOWN))
+	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
 		return -EPERM;
 
 	/* Make sure a caller can chmod. */
 	if (ia_valid & ATTR_MODE) {
 		if (!inode_owner_or_capable(inode))
 			return -EPERM;
 		/* Also check the setgid bit! */
 		if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
 				inode->i_gid) &&
-		    !inode_capable(inode, CAP_FSETID))
+		    !capable_wrt_inode_uidgid(inode, CAP_FSETID))
 			attr->ia_mode &= ~S_ISGID;
 	}
 
 	/* Check for setting the inode time. */
 	if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) {
@@ -158,11 +158,11 @@ void setattr_copy(struct inode *inode, const struct iattr *attr)
 						inode->i_sb->s_time_gran);
 	if (ia_valid & ATTR_MODE) {
 		umode_t mode = attr->ia_mode;
 
 		if (!in_group_p(inode->i_gid) &&
-		    !inode_capable(inode, CAP_FSETID))
+		    !capable_wrt_inode_uidgid(inode, CAP_FSETID))
 			mode &= ~S_ISGID;
 		inode->i_mode = mode;
 	}
 }
 EXPORT_SYMBOL(setattr_copy);
diff --git a/fs/inode.c b/fs/inode.c
index 2feb9b69f1be90..6eecb7ff0b9aa2 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -1837,18 +1837,22 @@ EXPORT_SYMBOL(inode_init_owner);
 
 /**
  * inode_owner_or_capable - check current task permissions to inode
  * @inode: inode being checked
  *
- * Return true if current either has CAP_FOWNER to the inode, or
- * owns the file.
+ * Return true if current either has CAP_FOWNER in a namespace with the
+ * inode owner uid mapped, or owns the file.
  */
 bool inode_owner_or_capable(const struct inode *inode)
 {
+	struct user_namespace *ns;
+
 	if (uid_eq(current_fsuid(), inode->i_uid))
 		return true;
-	if (inode_capable(inode, CAP_FOWNER))
+
+	ns = current_user_ns();
+	if (ns_capable(ns, CAP_FOWNER) && kuid_has_mapping(ns, inode->i_uid))
 		return true;
 	return false;
 }
 EXPORT_SYMBOL(inode_owner_or_capable);
 
diff --git a/fs/namei.c b/fs/namei.c
index 80168273396bba..985c6f3684859e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -330,32 +330,33 @@ int generic_permission(struct inode *inode, int mask)
 	if (ret != -EACCES)
 		return ret;
 
 	if (S_ISDIR(inode->i_mode)) {
 		/* DACs are overridable for directories */
-		if (inode_capable(inode, CAP_DAC_OVERRIDE))
+		if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
 			return 0;
 		if (!(mask & MAY_WRITE))
-			if (inode_capable(inode, CAP_DAC_READ_SEARCH))
+			if (capable_wrt_inode_uidgid(inode,
+						     CAP_DAC_READ_SEARCH))
 				return 0;
 		return -EACCES;
 	}
 	/*
 	 * Read/write DACs are always overridable.
 	 * Executable DACs are overridable when there is
 	 * at least one exec bit set.
 	 */
 	if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
-		if (inode_capable(inode, CAP_DAC_OVERRIDE))
+		if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
 			return 0;
 
 	/*
 	 * Searching includes executable on directories, else just read.
 	 */
 	mask &= MAY_READ | MAY_WRITE | MAY_EXEC;
 	if (mask == MAY_READ)
-		if (inode_capable(inode, CAP_DAC_READ_SEARCH))
+		if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH))
 			return 0;
 
 	return -EACCES;
 }
 EXPORT_SYMBOL(generic_permission);
@@ -2377,11 +2378,11 @@ static inline int check_sticky(struct inode *dir, struct inode *inode)
 		return 0;
 	if (uid_eq(inode->i_uid, fsuid))
 		return 0;
 	if (uid_eq(dir->i_uid, fsuid))
 		return 0;
-	return !inode_capable(inode, CAP_FOWNER);
+	return !capable_wrt_inode_uidgid(inode, CAP_FOWNER);
 }
 
 /*
  *	Check whether we can remove a link victim from directory dir, check
  *  whether the type of victim is right.
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 0b18776b075e44..6152cbe353e8ed 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -1213,11 +1213,11 @@ xfs_ioctl_setattr(
 		 *
 		 * The set-user-ID and set-group-ID bits of a file will be
 		 * cleared upon successful return from chown()
 		 */
 		if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) &&
-		    !inode_capable(VFS_I(ip), CAP_FSETID))
+		    !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID))
 			ip->i_d.di_mode &= ~(S_ISUID|S_ISGID);
 
 		/*
 		 * Change the ownerships and register quota modifications
 		 * in the transaction.
diff --git a/include/linux/capability.h b/include/linux/capability.h
index a6ee1f9a501854..84b13ad67c1cc4 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -208,11 +208,11 @@ extern bool has_ns_capability(struct task_struct *t,
 extern bool has_capability_noaudit(struct task_struct *t, int cap);
 extern bool has_ns_capability_noaudit(struct task_struct *t,
 				      struct user_namespace *ns, int cap);
 extern bool capable(int cap);
 extern bool ns_capable(struct user_namespace *ns, int cap);
-extern bool inode_capable(const struct inode *inode, int cap);
+extern bool capable_wrt_inode_uidgid(const struct inode *inode, int cap);
 extern bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap);
 
 /* audit system wants to get cap info from files as well */
 extern int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps);
 
diff --git a/kernel/capability.c b/kernel/capability.c
index 84b2bbf443e716..a5cf13c018ceca 100644
--- a/kernel/capability.c
+++ b/kernel/capability.c
@@ -422,25 +422,21 @@ bool capable(int cap)
 	return ns_capable(&init_user_ns, cap);
 }
 EXPORT_SYMBOL(capable);
 
 /**
- * inode_capable - Check superior capability over inode
+ * capable_wrt_inode_uidgid - Check nsown_capable and uid and gid mapped
  * @inode: The inode in question
  * @cap: The capability in question
  *
- * Return true if the current task has the given superior capability
- * targeted at it's own user namespace and that the given inode is owned
- * by the current user namespace or a child namespace.
- *
- * Currently we check to see if an inode is owned by the current
- * user namespace by seeing if the inode's owner maps into the
- * current user namespace.
- *
+ * Return true if the current task has the given capability targeted at
+ * its own user namespace and that the given inode's uid and gid are
+ * mapped into the current user namespace.
  */
-bool inode_capable(const struct inode *inode, int cap)
+bool capable_wrt_inode_uidgid(const struct inode *inode, int cap)
 {
 	struct user_namespace *ns = current_user_ns();
 
-	return ns_capable(ns, cap) && kuid_has_mapping(ns, inode->i_uid);
+	return ns_capable(ns, cap) && kuid_has_mapping(ns, inode->i_uid) &&
+		kgid_has_mapping(ns, inode->i_gid);
 }
-EXPORT_SYMBOL(inode_capable);
+EXPORT_SYMBOL(capable_wrt_inode_uidgid);

Vulnerable code: 	    !inode_capable(inode, CAP_CHOWN))
	    !inode_capable(inode, CAP_CHOWN))
		    !inode_capable(inode, CAP_FSETID))
		    !inode_capable(inode, CAP_FSETID))
 * Return true if current either has CAP_FOWNER to the inode, or
 * owns the file.
	if (inode_capable(inode, CAP_FOWNER))
		if (inode_capable(inode, CAP_DAC_OVERRIDE))
			if (inode_capable(inode, CAP_DAC_READ_SEARCH))
		if (inode_capable(inode, CAP_DAC_OVERRIDE))
		if (inode_capable(inode, CAP_DAC_READ_SEARCH))
	return !inode_capable(inode, CAP_FOWNER);
		    !inode_capable(VFS_I(ip), CAP_FSETID))
extern bool inode_capable(const struct inode *inode, int cap);
 * inode_capable - Check superior capability over inode
 * Return true if the current task has the given superior capability
 * targeted at it's own user namespace and that the given inode is owned
 * by the current user namespace or a child namespace.
 *
 * Currently we check to see if an inode is owned by the current
 * user namespace by seeing if the inode's owner maps into the
 * current user namespace.
 *
bool inode_capable(const struct inode *inode, int cap)
	return ns_capable(ns, cap) && kuid_has_mapping(ns, inode->i_uid);
EXPORT_SYMBOL(inode_capable);
Fixed code: 	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
		    !capable_wrt_inode_uidgid(inode, CAP_FSETID))
		    !capable_wrt_inode_uidgid(inode, CAP_FSETID))
 * Return true if current either has CAP_FOWNER in a namespace with the
 * inode owner uid mapped, or owns the file.
	struct user_namespace *ns;


	ns = current_user_ns();
	if (ns_capable(ns, CAP_FOWNER) && kuid_has_mapping(ns, inode->i_uid))
		if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
			if (capable_wrt_inode_uidgid(inode,
						     CAP_DAC_READ_SEARCH))
		if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
		if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH))
	return !capable_wrt_inode_uidgid(inode, CAP_FOWNER);
		    !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID))
extern bool capable_wrt_inode_uidgid(const struct inode *inode, int cap);
 * capable_wrt_inode_uidgid - Check nsown_capable and uid and gid mapped
 * Return true if the current task has the given capability targeted at
 * its own user namespace and that the given inode's uid and gid are
 * mapped into the current user namespace.
bool capable_wrt_inode_uidgid(const struct inode *inode, int cap)
	return ns_capable(ns, cap) && kuid_has_mapping(ns, inode->i_uid) &&
		kgid_has_mapping(ns, inode->i_gid);
EXPORT_SYMBOL(capable_wrt_inode_uidgid);