]> Pileus Git - ~andy/linux/commitdiff
Add PR_{GET,SET}_NO_NEW_PRIVS to prevent execve from granting privs
authorAndy Lutomirski <luto@amacapital.net>
Thu, 12 Apr 2012 21:47:50 +0000 (16:47 -0500)
committerJames Morris <james.l.morris@oracle.com>
Sat, 14 Apr 2012 01:13:18 +0000 (11:13 +1000)
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 <luto@amacapital.net>
Signed-off-by: Will Drewry <wad@chromium.org>
Acked-by: Eric Paris <eparis@redhat.com>
Acked-by: Kees Cook <keescook@chromium.org>
v18: updated change desc
v17: using new define values as per 3.4
Signed-off-by: James Morris <james.l.morris@oracle.com>
fs/exec.c
include/linux/prctl.h
include/linux/sched.h
include/linux/security.h
kernel/sys.c
security/apparmor/domain.c
security/commoncap.c
security/selinux/hooks.c

index b1fd2025e59a1e0ea0a2f475b812987dac176e9f..d038968b54b420a3844c6e07f6bf25bee34d7e5c 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1245,6 +1245,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 (current->no_new_privs)
+               bprm->unsafe |= LSM_UNSAFE_NO_NEW_PRIVS;
+
        n_fs = 1;
        spin_lock(&p->fs->lock);
        rcu_read_lock();
@@ -1288,7 +1295,8 @@ int prepare_binprm(struct linux_binprm *bprm)
        bprm->cred->euid = current_euid();
        bprm->cred->egid = current_egid();
 
-       if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
+       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;
index e0cfec2490aaeeeb73f234646e9e4806afaf148c..78b76e24cc7eed661d7696200c4c4f2291425a0d 100644 (file)
 #define PR_SET_CHILD_SUBREAPER 36
 #define PR_GET_CHILD_SUBREAPER 37
 
+/*
+ * 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 */
index 81a173c0897d91c2d5b22b5370d282c7346bd8a9..ba60897bb44789288e6cc4e0de14de88f223e714 100644 (file)
@@ -1341,6 +1341,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;
index 673afbb8238a38b5d8f4cb5be001523f34d4cba7..6e1dea93907a7005a00bcb07472b1a9dd3349171 100644 (file)
@@ -144,6 +144,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,
index e7006eb6c1e4fdc3fa7967eaa45541609b5d499f..b82568b7d201458d2a1c0db3fd5acb52f151ab35 100644 (file)
@@ -1979,6 +1979,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
                        error = put_user(me->signal->is_child_subreaper,
                                         (int __user *) arg2);
                        break;
+               case PR_SET_NO_NEW_PRIVS:
+                       if (arg2 != 1 || arg3 || arg4 || arg5)
+                               return -EINVAL;
+
+                       current->no_new_privs = 1;
+                       break;
+               case PR_GET_NO_NEW_PRIVS:
+                       if (arg2 || arg3 || arg4 || arg5)
+                               return -EINVAL;
+                       return current->no_new_privs ? 1 : 0;
                default:
                        error = -EINVAL;
                        break;
index 6327685c101e49cc720553c9db9f02da3fed1d24..18c88d06e88141f7bc71bf8f01dbf6b923b5f120 100644 (file)
@@ -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);
 
index 0cf4b53480a778ffeacd46149aa785c63db81c07..edd3918fac02793991db97ae0c937cb09f04c319 100644 (file)
@@ -506,14 +506,17 @@ int cap_bprm_set_creds(struct linux_binprm *bprm)
 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;
                }
index d85b793c9321c5866a2f6bd2ce60cb5aaff42e17..0b06685787b9fd6b4b56da8121bcf8d2338b08a7 100644 (file)
@@ -2016,6 +2016,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,
@@ -2029,7 +2036,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) {