Message ID | 1430133127-13796-2-git-send-email-luis.henriques@canonical.com |
---|---|
State | New |
Headers | show |
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 --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);