fs: take i_mutex during prepare_binprm for set[ug]id executables
authorJann Horn <jann@thejh.net>
Sun, 19 Apr 2015 00:48:39 +0000 (02:48 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 4 Jul 2015 02:48:09 +0000 (19:48 -0700)
commit 8b01fc86b9f425899f8a3a8fc1c47d73c2c20543 upstream.

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>
Signed-off-by: Charles Williams <ciwillia@brocade.com>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Signed-off-by: Sheng Yong <shengyong1@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/exec.c

index dd6aa61c85486b74ce70ae802966dc85814c8317..acbd7ac2deda4cbed77aecf25c22d2469f7d7234 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1265,6 +1265,53 @@ static 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;
+       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 (current->no_new_privs)
+               return;
+
+       inode = file_inode(bprm->file);
+       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);
+
+       /* 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
@@ -1273,39 +1320,12 @@ static int check_unsafe_exec(struct linux_binprm *bprm)
  */
 int prepare_binprm(struct linux_binprm *bprm)
 {
-       umode_t mode;
-       struct inode * inode = file_inode(bprm->file);
        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 &&
-           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);