From 6ebe32be2b7bdc1d9e2505fd9ca24dd60e173d6f Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Mon, 30 Jan 2012 08:17:26 -0800 Subject: [PATCH] Add PR_{GET,SET}_NO_NEW_PRIVS to prevent execve from granting privs With this change, calling prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) disables privilege granting operations at execve-time. For example, a process will not be able to execute a setuid binary to change their uid or gid if this bit is set. The same is true for file capabilities. Additionally, LSM_UNSAFE_NO_NEW_PRIVS is defined to ensure that LSMs respect the requested behavior. To determine if the NO_NEW_PRIVS bit is set, a task may call prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); It returns 1 if set and 0 if it is not set. If any of the arguments are non-zero, it will return -1 and set errno to -EINVAL. (PR_SET_NO_NEW_PRIVS behaves similarly.) This functionality is desired for the proposed seccomp filter patch series. By using PR_SET_NO_NEW_PRIVS, it allows a task to modify the system call behavior for itself and its child tasks without being able to impact the behavior of a more privileged task. Another potential use is making certain privileged operations unprivileged. For example, chroot may be considered "safe" if it cannot affect privileged tasks. Note, this patch causes execve to fail when PR_SET_NO_NEW_PRIVS is set and AppArmor is in use. It is fixed in a subsequent patch. Signed-off-by: Andy Lutomirski Signed-off-by: Will Drewry Acked-by: Eric Paris v18: updated change desc v17: using new define values as per 3.4 EDIT: Modified slightly for Samsung/klte sources. Conflicts: include/linux/prctl.h kernel/sys.c Signed-off-by: Kevin F. Haggerty Change-Id: Iae0eadc19ee163fea9401082e4ced374280d61fe Signed-off-by: Kevin F. Haggerty --- fs/exec.c | 75 ++++++++++++++++---------------------- include/linux/prctl.h | 15 ++++++++ include/linux/sched.h | 2 + include/linux/security.h | 1 + kernel/sys.c | 11 +++++- security/apparmor/domain.c | 4 ++ security/commoncap.c | 7 +++- security/selinux/hooks.c | 10 ++++- 8 files changed, 76 insertions(+), 49 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 3c0bb970990..9eda957adfc 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1251,45 +1251,6 @@ void install_exec_creds(struct linux_binprm *bprm) } EXPORT_SYMBOL(install_exec_creds); -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; - - 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; - } -} - /* * determine how safe it is to execute the proposed program * - the caller must hold ->cred_guard_mutex to protect against @@ -1308,6 +1269,13 @@ static int check_unsafe_exec(struct linux_binprm *bprm) bprm->unsafe |= LSM_UNSAFE_PTRACE; } + /* + * This isn't strictly necessary, but it makes it harder for LSMs to + * mess up. + */ + if (task_no_new_privs(current)) + bprm->unsafe |= LSM_UNSAFE_NO_NEW_PRIVS; + n_fs = 1; spin_lock(&p->fs->lock); rcu_read_lock(); @@ -1339,19 +1307,38 @@ static 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; - - bprm_fill_uid(bprm); - + /* 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) && + !task_no_new_privs(current)) { + /* 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; + } + } /* fill in binprm security blob */ retval = security_bprm_set_creds(bprm); if (retval) return retval; bprm->cred_prepared = 1; - memset(bprm->buf, 0, BINPRM_BUF_SIZE); return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE); } diff --git a/include/linux/prctl.h b/include/linux/prctl.h index 8879b340a56..8b41a9b80d8 100644 --- a/include/linux/prctl.h +++ b/include/linux/prctl.h @@ -135,4 +135,19 @@ #define PR_SET_VMA 0x53564d41 # define PR_SET_VMA_ANON_NAME 0 +/* + * If no_new_privs is set, then operations that grant new privileges (i.e. + * execve) will either fail or not grant them. This affects suid/sgid, + * file capabilities, and LSMs. + * + * Operations that merely manipulate or drop existing privileges (setresuid, + * capset, etc.) will still work. Drop those privileges if you want them gone. + * + * Changing LSM security domain is considered a new privilege. So, for example, + * asking selinux for a specific new context (e.g. with runcon) will result + * in execve returning -EPERM. + */ +#define PR_SET_NO_NEW_PRIVS 38 +#define PR_GET_NO_NEW_PRIVS 39 + #endif /* _LINUX_PRCTL_H */ diff --git a/include/linux/sched.h b/include/linux/sched.h index d2e7ccdd918..39fe11aa7b2 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1367,6 +1367,8 @@ struct task_struct { * execve */ unsigned in_iowait:1; + /* task may not gain privileges */ + unsigned no_new_privs:1; /* Revert to default priority/policy when forking */ unsigned sched_reset_on_fork:1; diff --git a/include/linux/security.h b/include/linux/security.h index 0fe0a70fcf4..73f9143cfc9 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -145,6 +145,7 @@ struct request_sock; #define LSM_UNSAFE_SHARE 1 #define LSM_UNSAFE_PTRACE 2 #define LSM_UNSAFE_PTRACE_CAP 4 +#define LSM_UNSAFE_NO_NEW_PRIVS 8 #ifdef CONFIG_MMU extern int mmap_min_addr_handler(struct ctl_table *table, int write, diff --git a/kernel/sys.c b/kernel/sys.c index eefa93030aa..4e683559736 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -2272,7 +2272,6 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, break; /* remove this case because of sidesync call mute for H-projects */ -#ifndef CONFIG_SEC_H_PROJECT case PR_SET_TIMERSLACK_PID: if (task_pid_vnr(current) != (pid_t)arg3 && !capable(CAP_SYS_NICE)) @@ -2293,7 +2292,15 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, put_task_struct(tsk); error = 0; break; -#endif + case PR_SET_NO_NEW_PRIVS: + if (arg2 != 1 || arg3 || arg4 || arg5) + return -EINVAL; + task_set_no_new_privs(current); + break; + case PR_GET_NO_NEW_PRIVS: + if (arg2 || arg3 || arg4 || arg5) + return -EINVAL; + return task_no_new_privs(current) ? 1 : 0; default: error = -EINVAL; break; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 6327685c101..18c88d06e88 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -360,6 +360,10 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) if (bprm->cred_prepared) return 0; + /* XXX: no_new_privs is not usable with AppArmor yet */ + if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) + return -EPERM; + cxt = bprm->cred->security; BUG_ON(!cxt); diff --git a/security/commoncap.c b/security/commoncap.c index 0051ac2d058..98ff4630f9f 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -523,14 +523,17 @@ skip: /* Don't let someone trace a set[ug]id/setpcap binary with the revised - * credentials unless they have the appropriate permit + * credentials unless they have the appropriate permit. + * + * In addition, if NO_NEW_PRIVS, then ensure we get no new privs. */ if ((new->euid != old->uid || new->egid != old->gid || !cap_issubset(new->cap_permitted, old->cap_permitted)) && bprm->unsafe & ~LSM_UNSAFE_PTRACE_CAP) { /* downgrade; they get no more than they had, and maybe less */ - if (!capable(CAP_SETUID)) { + if (!capable(CAP_SETUID) || + (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) { new->euid = new->uid; new->egid = new->gid; } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index fc22555f01d..2160a352336 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2164,6 +2164,13 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) new_tsec->sid = old_tsec->exec_sid; /* Reset exec SID on execve. */ new_tsec->exec_sid = 0; + + /* + * Minimize confusion: if no_new_privs and a transition is + * explicitly requested, then fail the exec. + */ + if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) + return -EPERM; } else { /* Check for a default transition on this program. */ rc = security_transition_sid(old_tsec->sid, isec->sid, @@ -2177,7 +2184,8 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) ad.selinux_audit_data = &sad; ad.u.path = bprm->file->f_path; - if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) + if ((bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) || + (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) new_tsec->sid = old_tsec->sid; if (new_tsec->sid == old_tsec->sid) {