Skip to content

Commit 337490f

Browse files
ebiedermSerge Hallyn
authored andcommitted
exec: Correct the permission check for unsafe exec
Max Kellerman recently experienced a problem[1] when calling exec with differing uid and euid's and he triggered the logic that is supposed to only handle setuid executables. When exec isn't changing anything in struct cred it doesn't make sense to go into the code that is there to handle the case when the credentials change. When looking into the history of the code I discovered that this issue was not present in Linux-2.4.0-test12 and was introduced in Linux-2.4.0-prerelease when the logic for handling this case was moved from prepare_binprm to compute_creds in fs/exec.c. The bug introdused was to comparing euid in the new credentials with uid instead of euid in the old credentials, when testing if setuid had changed the euid. Since triggering the keep ptrace limping along case for setuid executables makes no sense when it was not a setuid exec revert back to the logic present in Linux-2.4.0-test12. This removes the confusingly named and subtlety incorrect helpers is_setuid and is_setgid, that helped this bug to persist. The varaiable is_setid is renamed to id_changed (it's Linux-2.4.0-test12) as the old name describes what matters rather than it's cause. The code removed in Linux-2.4.0-prerelease was: - /* Set-uid? */ - if (mode & S_ISUID) { - bprm->e_uid = inode->i_uid; - if (bprm->e_uid != current->euid) - id_change = 1; - } - - /* 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->e_gid = inode->i_gid; - if (!in_group_p(bprm->e_gid)) - id_change = 1; Linux-2.4.0-prerelease added the current logic as: + if (bprm->e_uid != current->uid || bprm->e_gid != current->gid || + !cap_issubset(new_permitted, current->cap_permitted)) { + current->dumpable = 0; + + lock_kernel(); + if (must_not_trace_exec(current) + || atomic_read(&current->fs->count) > 1 + || atomic_read(&current->files->count) > 1 + || atomic_read(&current->sig->count) > 1) { + if(!capable(CAP_SETUID)) { + bprm->e_uid = current->uid; + bprm->e_gid = current->gid; + } + if(!capable(CAP_SETPCAP)) { + new_permitted = cap_intersect(new_permitted, + current->cap_permitted); + } + } + do_unlock = 1; + } I have condenced the logic from Linux-2.4.0-test12 to just: id_changed = !uid_eq(new->euid, old->euid) || !in_group_p(new->egid); This change is userspace visible, but I don't expect anyone to care. For the bug that is being fixed to trigger bprm->unsafe has to be set. The variable bprm->unsafe is set when ptracing an executable, when sharing a working directory, or when no_new_privs is set. Properly testing for cases that are safe even in those conditions and doing nothing special should not affect anyone. Especially if they were previously ok with their credentials getting munged To minimize behavioural changes the code continues to set secureexec when euid != uid or when egid != gid. [1] https://lkml.kernel.org/r/[email protected] Reported-by: Max Kellermann <[email protected]> Fixes: 64444d3d0d7f ("Linux version 2.4.0-prerelease") v1: https://lkml.kernel.org/r/[email protected] Reviewed-by: Serge Hallyn <[email protected]> Signed-off-by: Eric W. Biederman <[email protected]> Reviewed-by: Jann Horn <[email protected]> Acked-by: Kees Cook <[email protected]>
1 parent 19272b3 commit 337490f

File tree

1 file changed

+8
-12
lines changed

1 file changed

+8
-12
lines changed

security/commoncap.c

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -856,12 +856,6 @@ static void handle_privileged_root(struct linux_binprm *bprm, bool has_fcap,
856856
#define __cap_full(field, cred) \
857857
cap_issubset(CAP_FULL_SET, cred->cap_##field)
858858

859-
static inline bool __is_setuid(struct cred *new, const struct cred *old)
860-
{ return !uid_eq(new->euid, old->uid); }
861-
862-
static inline bool __is_setgid(struct cred *new, const struct cred *old)
863-
{ return !gid_eq(new->egid, old->gid); }
864-
865859
/*
866860
* 1) Audit candidate if current->cap_effective is set
867861
*
@@ -891,7 +885,7 @@ static inline bool nonroot_raised_pE(struct cred *new, const struct cred *old,
891885
(root_privileged() &&
892886
__is_suid(root, new) &&
893887
!__cap_full(effective, new)) ||
894-
(!__is_setuid(new, old) &&
888+
(uid_eq(new->euid, old->euid) &&
895889
((has_fcap &&
896890
__cap_gained(permitted, new, old)) ||
897891
__cap_gained(ambient, new, old))))
@@ -917,7 +911,7 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)
917911
/* Process setpcap binaries and capabilities for uid 0 */
918912
const struct cred *old = current_cred();
919913
struct cred *new = bprm->cred;
920-
bool effective = false, has_fcap = false, is_setid;
914+
bool effective = false, has_fcap = false, id_changed;
921915
int ret;
922916
kuid_t root_uid;
923917

@@ -941,9 +935,9 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)
941935
*
942936
* In addition, if NO_NEW_PRIVS, then ensure we get no new privs.
943937
*/
944-
is_setid = __is_setuid(new, old) || __is_setgid(new, old);
938+
id_changed = !uid_eq(new->euid, old->euid) || !in_group_p(new->egid);
945939

946-
if ((is_setid || __cap_gained(permitted, new, old)) &&
940+
if ((id_changed || __cap_gained(permitted, new, old)) &&
947941
((bprm->unsafe & ~LSM_UNSAFE_PTRACE) ||
948942
!ptracer_capable(current, new->user_ns))) {
949943
/* downgrade; they get no more than they had, and maybe less */
@@ -960,7 +954,7 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)
960954
new->sgid = new->fsgid = new->egid;
961955

962956
/* File caps or setid cancels ambient. */
963-
if (has_fcap || is_setid)
957+
if (has_fcap || id_changed)
964958
cap_clear(new->cap_ambient);
965959

966960
/*
@@ -993,7 +987,9 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)
993987
return -EPERM;
994988

995989
/* Check for privilege-elevated exec. */
996-
if (is_setid ||
990+
if (id_changed ||
991+
!uid_eq(new->euid, old->uid) ||
992+
!gid_eq(new->egid, old->gid) ||
997993
(!__is_real(root_uid, new) &&
998994
(effective ||
999995
__cap_grew(permitted, ambient, new))))

0 commit comments

Comments
 (0)