diff mbox

[Precise,CVE-2015-3339] fs: take i_mutex during prepare_binprm for set[ug]id executables

Message ID 1430133127-13796-2-git-send-email-luis.henriques@canonical.com
State New
Headers show

Commit Message

Luis Henriques April 27, 2015, 11:12 a.m. UTC
From: Jann Horn <jann@thejh.net>

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>
(backported from commit 8b01fc86b9f425899f8a3a8fc1c47d73c2c20543)
[ luis: backport to Precise:
  - replaced kuid_t/kgid_t by uid_t/gid_t
  - replaced READ_ONCE() by ACCESS_ONCE()
  - replaced task_no_new_privs() by current->no_new_privs
  - replaced file_inode() by bprm->file->f_path.dentry->d_inode
  - dropped user_ns bits ]
CVE-2015-3339
BugLink: https://bugs.launchpad.net/bugs/1447373
Signed-off-by: Luis Henriques <luis.henriques@canonical.com>
---
 fs/exec.c | 69 +++++++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 43 insertions(+), 26 deletions(-)

Comments

John Johansen April 27, 2015, 6:42 p.m. UTC | #1
On 04/27/2015 04:12 AM, Luis Henriques wrote:
> From: Jann Horn <jann@thejh.net>
> 
> 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>
> (backported from commit 8b01fc86b9f425899f8a3a8fc1c47d73c2c20543)
> [ luis: backport to Precise:
>   - replaced kuid_t/kgid_t by uid_t/gid_t
>   - replaced READ_ONCE() by ACCESS_ONCE()
>   - replaced task_no_new_privs() by current->no_new_privs
>   - replaced file_inode() by bprm->file->f_path.dentry->d_inode
>   - dropped user_ns bits ]
> CVE-2015-3339
> BugLink: https://bugs.launchpad.net/bugs/1447373
> Signed-off-by: Luis Henriques <luis.henriques@canonical.com>

Acked-by: John Johansen <john.johansen@canonical.com>

> ---
>  fs/exec.c | 69 +++++++++++++++++++++++++++++++++++++++------------------------
>  1 file changed, 43 insertions(+), 26 deletions(-)
> 
> diff --git a/fs/exec.c b/fs/exec.c
> index 25a52322ef59..bd70d8612e5a 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -1293,6 +1293,48 @@ int check_unsafe_exec(struct linux_binprm *bprm)
>  	return res;
>  }
>  
> +static void bprm_fill_uid(struct linux_binprm *bprm)
> +{
> +	struct inode *inode;
> +	unsigned int mode;
> +	uid_t uid;
> +	gid_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 (current->no_new_privs)
> +		return;
> +
> +	inode = bprm->file->f_path.dentry->d_inode;
> +	mode = ACCESS_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);
> +
> +	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
> @@ -1301,37 +1343,12 @@ int check_unsafe_exec(struct linux_binprm *bprm)
>   */
>  int prepare_binprm(struct linux_binprm *bprm)
>  {
> -	umode_t mode;
> -	struct inode * inode = bprm->file->f_path.dentry->d_inode;
>  	int retval;
>  
> -	mode = inode->i_mode;
>  	if (bprm->file->f_op == NULL)
>  		return -EACCES;
>  
> -	/* 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) &&
> -	    !current->no_new_privs) {
> -		/* 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);
>
diff mbox

Patch

diff --git a/fs/exec.c b/fs/exec.c
index 25a52322ef59..bd70d8612e5a 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1293,6 +1293,48 @@  int check_unsafe_exec(struct linux_binprm *bprm)
 	return res;
 }
 
+static void bprm_fill_uid(struct linux_binprm *bprm)
+{
+	struct inode *inode;
+	unsigned int mode;
+	uid_t uid;
+	gid_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 (current->no_new_privs)
+		return;
+
+	inode = bprm->file->f_path.dentry->d_inode;
+	mode = ACCESS_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);
+
+	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
@@ -1301,37 +1343,12 @@  int check_unsafe_exec(struct linux_binprm *bprm)
  */
 int prepare_binprm(struct linux_binprm *bprm)
 {
-	umode_t mode;
-	struct inode * inode = bprm->file->f_path.dentry->d_inode;
 	int retval;
 
-	mode = inode->i_mode;
 	if (bprm->file->f_op == NULL)
 		return -EACCES;
 
-	/* 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) &&
-	    !current->no_new_privs) {
-		/* 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);