capabilities: do not grant full privs for setuid w/ file caps + no effective caps

A task (when !SECURE_NOROOT) which executes a setuid-root binary will
obtain root privileges while executing that binary.  If the binary also
has effective capabilities set, then only those capabilities will be
granted.  The rationale is that the same binary can carry both setuid-root
and the minimal file capability set, so that on a filesystem not
supporting file caps the binary can still be executed with privilege,
while on a filesystem supporting file caps it will run with minimal
privilege.

This special case currently does NOT happen if there are file capabilities
but no effective capabilities.  Since capability-aware programs can very
well start with empty pE but populated pP and move those caps to pE when
needed.  In other words, if the file has file capabilities but NOT
effective capabilities, then we should do the same thing as if there
were file capabilities, and not grant full root privileges.

This patchset does that.

(Changelog by Serge Hallyn).

Signed-off-by: Zhi Li <lizhi1215@gmail.com>
Acked-by: Serge Hallyn <serge.hallyn@canonical.com>
Signed-off-by: James Morris <jmorris@namei.org>
diff --git a/security/commoncap.c b/security/commoncap.c
index a93b3b7..0f620c5 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -332,7 +332,8 @@
  */
 static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps,
 					  struct linux_binprm *bprm,
-					  bool *effective)
+					  bool *effective,
+					  bool *has_cap)
 {
 	struct cred *new = bprm->cred;
 	unsigned i;
@@ -341,6 +342,9 @@
 	if (caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE)
 		*effective = true;
 
+	if (caps->magic_etc & VFS_CAP_REVISION_MASK)
+		*has_cap = true;
+
 	CAP_FOR_EACH_U32(i) {
 		__u32 permitted = caps->permitted.cap[i];
 		__u32 inheritable = caps->inheritable.cap[i];
@@ -424,7 +428,7 @@
  * its xattrs and, if present, apply them to the proposed credentials being
  * constructed by execve().
  */
-static int get_file_caps(struct linux_binprm *bprm, bool *effective)
+static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_cap)
 {
 	struct dentry *dentry;
 	int rc = 0;
@@ -450,7 +454,7 @@
 		goto out;
 	}
 
-	rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective);
+	rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective, has_cap);
 	if (rc == -EINVAL)
 		printk(KERN_NOTICE "%s: cap_from_disk returned %d for %s\n",
 		       __func__, rc, bprm->filename);
@@ -475,11 +479,11 @@
 {
 	const struct cred *old = current_cred();
 	struct cred *new = bprm->cred;
-	bool effective;
+	bool effective, has_cap;
 	int ret;
 
 	effective = false;
-	ret = get_file_caps(bprm, &effective);
+	ret = get_file_caps(bprm, &effective, &has_cap);
 	if (ret < 0)
 		return ret;
 
@@ -489,7 +493,7 @@
 		 * for a setuid root binary run by a non-root user.  Do set it
 		 * for a root user just to cause least surprise to an admin.
 		 */
-		if (effective && new->uid != 0 && new->euid == 0) {
+		if (has_cap && new->uid != 0 && new->euid == 0) {
 			warn_setuid_and_fcaps_mixed(bprm->filename);
 			goto skip;
 		}