Vulnerability: Race condition in the prepare_binprm function in fs/exec.c in the Linux kernel before 3.19.6 allows local users to gain privileges by executing a setuid program at a time instant when a chown to root is in progress, and the ownership is changed but the setuid bit is not yet stripped.
Vulnerability ID: CVE-2015-3339
CWE: CWE-362
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2015-05-27
File: fs/exec.c
Commit message: fs: take i_mutex during prepare_binprm for set[ug]id executables

This prevents a race between chown() and execve(), where chowning a
setuid-user binary to root would momentarily make the binary setuid
root.

This patch was mostly written by Linus Torvalds.

Signed-off-by: Jann Horn <jann@thejh.net>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Commit author: Jann Horn

Vulnerable version (before fix):
 static void check_unsafe_exec(struct linux_binprm *bprm) 	spin_unlock(&p->fs->lock); }  /*  * Fill the binprm structure from the inode.  * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes static void check_unsafe_exec(struct linux_binprm *bprm)  */ int prepare_binprm(struct linux_binprm *bprm) { 	struct inode *inode = file_inode(bprm->file); 	umode_t mode = inode->i_mode; 	int retval;   	/* clear any previous set[ug]id data from a previous binary */ 	bprm->cred->euid = current_euid(); 	bprm->cred->egid = current_egid();  	if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) && 	    !task_no_new_privs(current) && 	    kuid_has_mapping(bprm->cred->user_ns, inode->i_uid) && 	    kgid_has_mapping(bprm->cred->user_ns, inode->i_gid)) { 		/* Set-uid? */ 		if (mode & S_ISUID) { 			bprm->per_clear |= PER_CLEAR_ON_SETID; 			bprm->cred->euid = inode->i_uid; 		}  		/* Set-gid? */ 		/* 		 * If setgid is set but no group execute bit then this 		 * is a candidate for mandatory locking, not a setgid 		 * executable. 		 */ 		if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { 			bprm->per_clear |= PER_CLEAR_ON_SETID; 			bprm->cred->egid = inode->i_gid; 		} 	}  	/* fill in binprm security blob */ 	retval = security_bprm_set_creds(bprm);

Fixed version (after fix):
 static void check_unsafe_exec(struct linux_binprm *bprm) 	spin_unlock(&p->fs->lock); }  static void bprm_fill_uid(struct linux_binprm *bprm) { 	struct inode *inode; 	unsigned int mode; 	kuid_t uid; 	kgid_t gid;  	/* clear any previous set[ug]id data from a previous binary */ 	bprm->cred->euid = current_euid(); 	bprm->cred->egid = current_egid();  	if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) 		return;  	if (task_no_new_privs(current)) 		return;  	inode = file_inode(bprm->file); 	mode = READ_ONCE(inode->i_mode); 	if (!(mode & (S_ISUID|S_ISGID))) 		return;  	/* Be careful if suid/sgid is set */ 	mutex_lock(&inode->i_mutex);  	/* reload atomically mode/uid/gid now that lock held */ 	mode = inode->i_mode; 	uid = inode->i_uid; 	gid = inode->i_gid; 	mutex_unlock(&inode->i_mutex);  	/* We ignore suid/sgid if there are no mappings for them in the ns */ 	if (!kuid_has_mapping(bprm->cred->user_ns, uid) || 		 !kgid_has_mapping(bprm->cred->user_ns, gid)) 		return;  	if (mode & S_ISUID) { 		bprm->per_clear |= PER_CLEAR_ON_SETID; 		bprm->cred->euid = uid; 	}  	if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { 		bprm->per_clear |= PER_CLEAR_ON_SETID; 		bprm->cred->egid = gid; 	} }  /*  * Fill the binprm structure from the inode.  * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes static void check_unsafe_exec(struct linux_binprm *bprm)  */ int prepare_binprm(struct linux_binprm *bprm) { 	int retval;  	bprm_fill_uid(bprm);  	/* fill in binprm security blob */ 	retval = security_bprm_set_creds(bprm);

Patch:
patch: @@ -1275,6 +1275,53 @@ static void check_unsafe_exec(struct linux_binprm *bprm)
 	spin_unlock(&p->fs->lock);
 }
 
+static void bprm_fill_uid(struct linux_binprm *bprm)
+{
+	struct inode *inode;
+	unsigned int mode;
+	kuid_t uid;
+	kgid_t gid;
+
+	/* clear any previous set[ug]id data from a previous binary */
+	bprm->cred->euid = current_euid();
+	bprm->cred->egid = current_egid();
+
+	if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)
+		return;
+
+	if (task_no_new_privs(current))
+		return;
+
+	inode = file_inode(bprm->file);
+	mode = READ_ONCE(inode->i_mode);
+	if (!(mode & (S_ISUID|S_ISGID)))
+		return;
+
+	/* Be careful if suid/sgid is set */
+	mutex_lock(&inode->i_mutex);
+
+	/* reload atomically mode/uid/gid now that lock held */
+	mode = inode->i_mode;
+	uid = inode->i_uid;
+	gid = inode->i_gid;
+	mutex_unlock(&inode->i_mutex);
+
+	/* We ignore suid/sgid if there are no mappings for them in the ns */
+	if (!kuid_has_mapping(bprm->cred->user_ns, uid) ||
+		 !kgid_has_mapping(bprm->cred->user_ns, gid))
+		return;
+
+	if (mode & S_ISUID) {
+		bprm->per_clear |= PER_CLEAR_ON_SETID;
+		bprm->cred->euid = uid;
+	}
+
+	if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
+		bprm->per_clear |= PER_CLEAR_ON_SETID;
+		bprm->cred->egid = gid;
+	}
+}
+
 /*
  * Fill the binprm structure from the inode.
  * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
@@ -1283,36 +1330,9 @@ static void check_unsafe_exec(struct linux_binprm *bprm)
  */
 int prepare_binprm(struct linux_binprm *bprm)
 {
-	struct inode *inode = file_inode(bprm->file);
-	umode_t mode = inode->i_mode;
 	int retval;
 
-
-	/* clear any previous set[ug]id data from a previous binary */
-	bprm->cred->euid = current_euid();
-	bprm->cred->egid = current_egid();
-
-	if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) &&
-	    !task_no_new_privs(current) &&
-	    kuid_has_mapping(bprm->cred->user_ns, inode->i_uid) &&
-	    kgid_has_mapping(bprm->cred->user_ns, inode->i_gid)) {
-		/* Set-uid? */
-		if (mode & S_ISUID) {
-			bprm->per_clear |= PER_CLEAR_ON_SETID;
-			bprm->cred->euid = inode->i_uid;
-		}
-
-		/* Set-gid? */
-		/*
-		 * If setgid is set but no group execute bit then this
-		 * is a candidate for mandatory locking, not a setgid
-		 * executable.
-		 */
-		if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
-			bprm->per_clear |= PER_CLEAR_ON_SETID;
-			bprm->cred->egid = inode->i_gid;
-		}
-	}
+	bprm_fill_uid(bprm);
 
 	/* fill in binprm security blob */
 	retval = security_bprm_set_creds(bprm);
path: fs/exec.c