diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 6466153f2bf0..ec45d24875b1 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -252,7 +252,7 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec, NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid)); NEW_AUX_ENT(AT_GID, from_kgid_munged(cred->user_ns, cred->gid)); NEW_AUX_ENT(AT_EGID, from_kgid_munged(cred->user_ns, cred->egid)); - NEW_AUX_ENT(AT_SECURE, security_bprm_secureexec(bprm)); + NEW_AUX_ENT(AT_SECURE, bprm->secureexec); NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes); #ifdef ELF_HWCAP2 NEW_AUX_ENT(AT_HWCAP2, ELF_HWCAP2); diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index cf93a4fad012..5aa9199dfb13 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -650,7 +650,7 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm, NEW_AUX_ENT(AT_EUID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->euid)); NEW_AUX_ENT(AT_GID, (elf_addr_t) from_kgid_munged(cred->user_ns, cred->gid)); NEW_AUX_ENT(AT_EGID, (elf_addr_t) from_kgid_munged(cred->user_ns, cred->egid)); - NEW_AUX_ENT(AT_SECURE, security_bprm_secureexec(bprm)); + NEW_AUX_ENT(AT_SECURE, bprm->secureexec); NEW_AUX_ENT(AT_EXECFN, bprm->exec); #ifdef ARCH_DLINFO diff --git a/fs/binfmt_flat.c b/fs/binfmt_flat.c index a1e6860b6f46..604a176df0c2 100644 --- a/fs/binfmt_flat.c +++ b/fs/binfmt_flat.c @@ -890,7 +890,7 @@ static int load_flat_shared_library(int id, struct lib_info *libs) * as we're past the point of no return and are dealing with shared * libraries. */ - bprm.cred_prepared = 1; + bprm.called_set_creds = 1; res = prepare_binprm(&bprm); diff --git a/fs/exec.c b/fs/exec.c index 62175cbcc801..01a9fb9d8ac3 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1259,6 +1259,12 @@ void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec) perf_event_comm(tsk, exec); } +/* + * Calling this is the point of no return. None of the failures will be + * seen by userspace since either the process is already taking a fatal + * signal (via de_thread() or coredump), or will have SEGV raised + * (after exec_mmap()) by search_binary_handlers (see below). + */ int flush_old_exec(struct linux_binprm * bprm) { int retval; @@ -1286,7 +1292,13 @@ int flush_old_exec(struct linux_binprm * bprm) if (retval) goto out; - bprm->mm = NULL; /* We're using it now */ + /* + * After clearing bprm->mm (to mark that current is using the + * prepared mm now), we have nothing left of the original + * process. If anything from here on returns an error, the check + * in search_binary_handler() will SEGV current. + */ + bprm->mm = NULL; set_fs(USER_DS); current->flags &= ~(PF_RANDOMIZE | PF_FORKNOEXEC | PF_KTHREAD | @@ -1331,15 +1343,38 @@ EXPORT_SYMBOL(would_dump); void setup_new_exec(struct linux_binprm * bprm) { + /* + * Once here, prepare_binrpm() will not be called any more, so + * the final state of setuid/setgid/fscaps can be merged into the + * secureexec flag. + */ + bprm->secureexec |= bprm->cap_elevated; + + if (bprm->secureexec) { + /* Make sure parent cannot signal privileged process. */ + current->pdeath_signal = 0; + + /* + * For secureexec, reset the stack limit to sane default to + * avoid bad behavior from the prior rlimits. This has to + * happen before arch_pick_mmap_layout(), which examines + * RLIMIT_STACK, but after the point of no return to avoid + * needing to clean up the change on failure. + */ + if (current->signal->rlim[RLIMIT_STACK].rlim_cur > _STK_LIM) + current->signal->rlim[RLIMIT_STACK].rlim_cur = _STK_LIM; + } + arch_pick_mmap_layout(current->mm); - /* This is the point of no return */ current->sas_ss_sp = current->sas_ss_size = 0; - if (uid_eq(current_euid(), current_uid()) && gid_eq(current_egid(), current_gid())) - set_dumpable(current->mm, SUID_DUMP_USER); - else + /* Figure out dumpability. */ + if (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP || + bprm->secureexec) set_dumpable(current->mm, suid_dumpable); + else + set_dumpable(current->mm, SUID_DUMP_USER); arch_setup_new_exec(); perf_event_exec(); @@ -1351,15 +1386,6 @@ void setup_new_exec(struct linux_binprm * bprm) */ current->mm->task_size = TASK_SIZE; - /* install the new credentials */ - if (!uid_eq(bprm->cred->uid, current_euid()) || - !gid_eq(bprm->cred->gid, current_egid())) { - current->pdeath_signal = 0; - } else { - if (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP) - set_dumpable(current->mm, suid_dumpable); - } - /* An exec changes our domain. We are no longer part of the thread group */ current->self_exec_id++; @@ -1548,7 +1574,7 @@ int prepare_binprm(struct linux_binprm *bprm) retval = security_bprm_set_creds(bprm); if (retval) return retval; - bprm->cred_prepared = 1; + bprm->called_set_creds = 1; memset(bprm->buf, 0, BINPRM_BUF_SIZE); return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE); diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index 3ae9013eeaaa..fb44d6180ca0 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -25,11 +25,25 @@ struct linux_binprm { struct mm_struct *mm; unsigned long p; /* current top of mem */ unsigned int - cred_prepared:1,/* true if creds already prepared (multiple - * preps happen for interpreters) */ - cap_effective:1;/* true if has elevated effective capabilities, - * false if not; except for init which inherits - * its parent's caps anyway */ + /* + * True after the bprm_set_creds hook has been called once + * (multiple calls can be made via prepare_binprm() for + * binfmt_script/misc). + */ + called_set_creds:1, + /* + * True if most recent call to the commoncaps bprm_set_creds + * hook (due to multiple prepare_binprm() calls from the + * binfmt_script/misc handlers) resulted in elevated + * privileges. + */ + cap_elevated:1, + /* + * Set by bprm_set_creds hook to indicate a privilege-gaining + * exec has happened. Used to sanitize execution environment + * and to set AT_SECURE auxv for glibc. + */ + secureexec:1; #ifdef __alpha__ unsigned int taso:1; #endif diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 3a90febadbe2..d1c7bef25691 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -43,7 +43,11 @@ * interpreters. The hook can tell whether it has already been called by * checking to see if @bprm->security is non-NULL. If so, then the hook * may decide either to retain the security information saved earlier or - * to replace it. + * to replace it. The hook must set @bprm->secureexec to 1 if a "secure + * exec" has happened as a result of this hook call. The flag is used to + * indicate the need for a sanitized execution environment, and is also + * passed in the ELF auxiliary table on the initial stack to indicate + * whether libc should enable secure mode. * @bprm contains the linux_binprm structure. * Return 0 if the hook is successful and permission is granted. * @bprm_check_security: @@ -71,12 +75,6 @@ * linux_binprm structure. This hook is a good place to perform state * changes on the process such as clearing out non-inheritable signal * state. This is called immediately after commit_creds(). - * @bprm_secureexec: - * Return a boolean value (0 or 1) indicating whether a "secure exec" - * is required. The flag is passed in the auxiliary table - * on the initial stack to the ELF interpreter to indicate whether libc - * should enable secure mode. - * @bprm contains the linux_binprm structure. * * Security hooks for filesystem operations. * @@ -1388,7 +1386,6 @@ union security_list_options { int (*bprm_set_creds)(struct linux_binprm *bprm); int (*bprm_check_security)(struct linux_binprm *bprm); - int (*bprm_secureexec)(struct linux_binprm *bprm); void (*bprm_committing_creds)(struct linux_binprm *bprm); void (*bprm_committed_creds)(struct linux_binprm *bprm); @@ -1710,7 +1707,6 @@ struct security_hook_heads { struct list_head vm_enough_memory; struct list_head bprm_set_creds; struct list_head bprm_check_security; - struct list_head bprm_secureexec; struct list_head bprm_committing_creds; struct list_head bprm_committed_creds; struct list_head sb_alloc_security; diff --git a/include/linux/security.h b/include/linux/security.h index b6ea1dc9cc9d..974bb9b0996c 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -85,7 +85,6 @@ extern int cap_capset(struct cred *new, const struct cred *old, const kernel_cap_t *inheritable, const kernel_cap_t *permitted); extern int cap_bprm_set_creds(struct linux_binprm *bprm); -extern int cap_bprm_secureexec(struct linux_binprm *bprm); extern int cap_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); extern int cap_inode_removexattr(struct dentry *dentry, const char *name); @@ -232,7 +231,6 @@ int security_bprm_set_creds(struct linux_binprm *bprm); int security_bprm_check(struct linux_binprm *bprm); void security_bprm_committing_creds(struct linux_binprm *bprm); void security_bprm_committed_creds(struct linux_binprm *bprm); -int security_bprm_secureexec(struct linux_binprm *bprm); int security_sb_alloc(struct super_block *sb); void security_sb_free(struct super_block *sb); int security_sb_copy_data(char *orig, char *copy); @@ -541,11 +539,6 @@ static inline void security_bprm_committed_creds(struct linux_binprm *bprm) { } -static inline int security_bprm_secureexec(struct linux_binprm *bprm) -{ - return cap_bprm_secureexec(bprm); -} - static inline int security_sb_alloc(struct super_block *sb) { return 0; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index d0594446ae3f..17a601c67b62 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -758,7 +758,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) file_inode(bprm->file)->i_mode }; - if (bprm->cred_prepared) + if (bprm->called_set_creds) return 0; ctx = cred_ctx(bprm->cred); @@ -807,7 +807,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) aa_label_printk(new, GFP_ATOMIC); dbg_printk("\n"); } - bprm->unsafe |= AA_SECURE_X_NEEDED; + bprm->secureexec = 1; } if (label->proxy != new->proxy) { @@ -843,23 +843,6 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) goto done; } -/** - * apparmor_bprm_secureexec - determine if secureexec is needed - * @bprm: binprm for exec (NOT NULL) - * - * Returns: %1 if secureexec is needed else %0 - */ -int apparmor_bprm_secureexec(struct linux_binprm *bprm) -{ - /* the decision to use secure exec is computed in set_creds - * and stored in bprm->unsafe. - */ - if (bprm->unsafe & AA_SECURE_X_NEEDED) - return 1; - - return 0; -} - /* * Functions for self directed profile change */ diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index bab5810b6e9a..24c5976d6143 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -30,7 +30,6 @@ struct aa_domain { #define AA_CHANGE_STACK 8 int apparmor_bprm_set_creds(struct linux_binprm *bprm); -int apparmor_bprm_secureexec(struct linux_binprm *bprm); void aa_free_domain_entries(struct aa_domain *domain); int aa_change_hat(const char *hats[], int count, u64 token, int flags); diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 001e40073ff9..4c2c8ac8842f 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -101,9 +101,6 @@ static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx) #define AA_X_INHERIT 0x4000 #define AA_X_UNCONFINED 0x8000 -/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */ -#define AA_SECURE_X_NEEDED 0x8000 - /* need to make conditional which ones are being set */ struct path_cond { kuid_t uid; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 867bcd154c7e..7a82c0f61452 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -694,7 +694,6 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bprm_set_creds, apparmor_bprm_set_creds), LSM_HOOK_INIT(bprm_committing_creds, apparmor_bprm_committing_creds), LSM_HOOK_INIT(bprm_committed_creds, apparmor_bprm_committed_creds), - LSM_HOOK_INIT(bprm_secureexec, apparmor_bprm_secureexec), LSM_HOOK_INIT(task_setrlimit, apparmor_task_setrlimit), }; diff --git a/security/commoncap.c b/security/commoncap.c index 7abebd782d5e..d8e26fb9781d 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -285,15 +285,6 @@ int cap_capset(struct cred *new, return 0; } -/* - * Clear proposed capability sets for execve(). - */ -static inline void bprm_clear_caps(struct linux_binprm *bprm) -{ - cap_clear(bprm->cred->cap_permitted); - bprm->cap_effective = false; -} - /** * cap_inode_need_killpriv - Determine if inode change affects privileges * @dentry: The inode/dentry in being changed with change marked ATTR_KILL_PRIV @@ -443,7 +434,7 @@ static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_c int rc = 0; struct cpu_vfs_cap_data vcaps; - bprm_clear_caps(bprm); + cap_clear(bprm->cred->cap_permitted); if (!file_caps_enabled) return 0; @@ -476,7 +467,7 @@ static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_c out: if (rc) - bprm_clear_caps(bprm); + cap_clear(bprm->cred->cap_permitted); return rc; } @@ -585,8 +576,6 @@ int cap_bprm_set_creds(struct linux_binprm *bprm) if (WARN_ON(!cap_ambient_invariant_ok(new))) return -EPERM; - bprm->cap_effective = effective; - /* * Audit candidate if current->cap_effective is set * @@ -614,33 +603,17 @@ int cap_bprm_set_creds(struct linux_binprm *bprm) if (WARN_ON(!cap_ambient_invariant_ok(new))) return -EPERM; - return 0; -} - -/** - * cap_bprm_secureexec - Determine whether a secure execution is required - * @bprm: The execution parameters - * - * Determine whether a secure execution is required, return 1 if it is, and 0 - * if it is not. - * - * The credentials have been committed by this point, and so are no longer - * available through @bprm->cred. - */ -int cap_bprm_secureexec(struct linux_binprm *bprm) -{ - const struct cred *cred = current_cred(); - kuid_t root_uid = make_kuid(cred->user_ns, 0); - - if (!uid_eq(cred->uid, root_uid)) { - if (bprm->cap_effective) - return 1; - if (!cap_issubset(cred->cap_permitted, cred->cap_ambient)) - return 1; + /* Check for privilege-elevated exec. */ + bprm->cap_elevated = 0; + if (is_setid) { + bprm->cap_elevated = 1; + } else if (!uid_eq(new->uid, root_uid)) { + if (effective || + !cap_issubset(new->cap_permitted, new->cap_ambient)) + bprm->cap_elevated = 1; } - return (!uid_eq(cred->euid, cred->uid) || - !gid_eq(cred->egid, cred->gid)); + return 0; } /** @@ -1079,7 +1052,6 @@ struct security_hook_list capability_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(capget, cap_capget), LSM_HOOK_INIT(capset, cap_capset), LSM_HOOK_INIT(bprm_set_creds, cap_bprm_set_creds), - LSM_HOOK_INIT(bprm_secureexec, cap_bprm_secureexec), LSM_HOOK_INIT(inode_need_killpriv, cap_inode_need_killpriv), LSM_HOOK_INIT(inode_killpriv, cap_inode_killpriv), LSM_HOOK_INIT(mmap_addr, cap_mmap_addr), diff --git a/security/security.c b/security/security.c index 30132378d103..afc34f46c6c5 100644 --- a/security/security.c +++ b/security/security.c @@ -351,11 +351,6 @@ void security_bprm_committed_creds(struct linux_binprm *bprm) call_void_hook(bprm_committed_creds, bprm); } -int security_bprm_secureexec(struct linux_binprm *bprm) -{ - return call_int_hook(bprm_secureexec, 0, bprm); -} - int security_sb_alloc(struct super_block *sb) { return call_int_hook(sb_alloc_security, 0, sb); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 2f2e1338cd3d..ad3b0f53ede0 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2356,7 +2356,7 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) /* SELinux context only depends on initial program or script and not * the script interpreter */ - if (bprm->cred_prepared) + if (bprm->called_set_creds) return 0; old_tsec = current_security(); @@ -2442,30 +2442,17 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) /* Clear any possibly unsafe personality bits on exec: */ bprm->per_clear |= PER_CLEAR_ON_SETID; - } - return 0; -} - -static int selinux_bprm_secureexec(struct linux_binprm *bprm) -{ - const struct task_security_struct *tsec = current_security(); - u32 sid, osid; - int atsecure = 0; - - sid = tsec->sid; - osid = tsec->osid; - - if (osid != sid) { /* Enable secure mode for SIDs transitions unless the noatsecure permission is granted between the two SIDs, i.e. ahp returns 0. */ - atsecure = avc_has_perm(osid, sid, - SECCLASS_PROCESS, - PROCESS__NOATSECURE, NULL); + rc = avc_has_perm(old_tsec->sid, new_tsec->sid, + SECCLASS_PROCESS, PROCESS__NOATSECURE, + NULL); + bprm->secureexec |= !!rc; } - return !!atsecure; + return 0; } static int match_file(const void *p, struct file *file, unsigned fd) @@ -6266,7 +6253,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bprm_set_creds, selinux_bprm_set_creds), LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds), LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds), - LSM_HOOK_INIT(bprm_secureexec, selinux_bprm_secureexec), LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security), LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 463af86812c7..319add31b4a4 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -917,7 +917,7 @@ static int smack_bprm_set_creds(struct linux_binprm *bprm) struct superblock_smack *sbsp; int rc; - if (bprm->cred_prepared) + if (bprm->called_set_creds) return 0; isp = inode->i_security; @@ -950,35 +950,9 @@ static int smack_bprm_set_creds(struct linux_binprm *bprm) bsp->smk_task = isp->smk_task; bprm->per_clear |= PER_CLEAR_ON_SETID; - return 0; -} - -/** - * smack_bprm_committing_creds - Prepare to install the new credentials - * from bprm. - * - * @bprm: binprm for exec - */ -static void smack_bprm_committing_creds(struct linux_binprm *bprm) -{ - struct task_smack *bsp = bprm->cred->security; - + /* Decide if this is a secure exec. */ if (bsp->smk_task != bsp->smk_forked) - current->pdeath_signal = 0; -} - -/** - * smack_bprm_secureexec - Return the decision to use secureexec. - * @bprm: binprm for exec - * - * Returns 0 on success. - */ -static int smack_bprm_secureexec(struct linux_binprm *bprm) -{ - struct task_smack *tsp = current_security(); - - if (tsp->smk_task != tsp->smk_forked) - return 1; + bprm->secureexec = 1; return 0; } @@ -4645,8 +4619,6 @@ static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(sb_parse_opts_str, smack_parse_opts_str), LSM_HOOK_INIT(bprm_set_creds, smack_bprm_set_creds), - LSM_HOOK_INIT(bprm_committing_creds, smack_bprm_committing_creds), - LSM_HOOK_INIT(bprm_secureexec, smack_bprm_secureexec), LSM_HOOK_INIT(inode_alloc_security, smack_inode_alloc_security), LSM_HOOK_INIT(inode_free_security, smack_inode_free_security), diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index 130b4fa4f65f..d25b705360e0 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -76,7 +76,7 @@ static int tomoyo_bprm_set_creds(struct linux_binprm *bprm) * Do only if this function is called for the first time of an execve * operation. */ - if (bprm->cred_prepared) + if (bprm->called_set_creds) return 0; #ifndef CONFIG_SECURITY_TOMOYO_OMIT_USERSPACE_LOADER /*