keys: PTR_ERR return of wrong pointer in keyctl_get_security()
[safe/jmp/linux-2.6] / security / keys / keyctl.c
index 070a53e..5f830bc 100644 (file)
@@ -103,7 +103,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
        }
 
        /* find the target keyring (which must be writable) */
-       keyring_ref = lookup_user_key(ringid, 1, 0, KEY_WRITE);
+       keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
        if (IS_ERR(keyring_ref)) {
                ret = PTR_ERR(keyring_ref);
                goto error3;
@@ -185,7 +185,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
        /* get the destination keyring if specified */
        dest_ref = NULL;
        if (destringid) {
-               dest_ref = lookup_user_key(destringid, 1, 0, KEY_WRITE);
+               dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE,
+                                          KEY_WRITE);
                if (IS_ERR(dest_ref)) {
                        ret = PTR_ERR(dest_ref);
                        goto error3;
@@ -233,9 +234,11 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
 long keyctl_get_keyring_ID(key_serial_t id, int create)
 {
        key_ref_t key_ref;
+       unsigned long lflags;
        long ret;
 
-       key_ref = lookup_user_key(id, create, 0, KEY_SEARCH);
+       lflags = create ? KEY_LOOKUP_CREATE : 0;
+       key_ref = lookup_user_key(id, lflags, KEY_SEARCH);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
                goto error;
@@ -270,6 +273,7 @@ long keyctl_join_session_keyring(const char __user *_name)
 
        /* join the session */
        ret = join_session_keyring(name);
+       kfree(name);
 
  error:
        return ret;
@@ -308,7 +312,7 @@ long keyctl_update_key(key_serial_t id,
        }
 
        /* find the target key (which must be writable) */
-       key_ref = lookup_user_key(id, 0, 0, KEY_WRITE);
+       key_ref = lookup_user_key(id, 0, KEY_WRITE);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
                goto error2;
@@ -336,10 +340,16 @@ long keyctl_revoke_key(key_serial_t id)
        key_ref_t key_ref;
        long ret;
 
-       key_ref = lookup_user_key(id, 0, 0, KEY_WRITE);
+       key_ref = lookup_user_key(id, 0, KEY_WRITE);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
-               goto error;
+               if (ret != -EACCES)
+                       goto error;
+               key_ref = lookup_user_key(id, 0, KEY_SETATTR);
+               if (IS_ERR(key_ref)) {
+                       ret = PTR_ERR(key_ref);
+                       goto error;
+               }
        }
 
        key_revoke(key_ref_to_ptr(key_ref));
@@ -362,7 +372,7 @@ long keyctl_keyring_clear(key_serial_t ringid)
        key_ref_t keyring_ref;
        long ret;
 
-       keyring_ref = lookup_user_key(ringid, 1, 0, KEY_WRITE);
+       keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
        if (IS_ERR(keyring_ref)) {
                ret = PTR_ERR(keyring_ref);
                goto error;
@@ -388,13 +398,13 @@ long keyctl_keyring_link(key_serial_t id, key_serial_t ringid)
        key_ref_t keyring_ref, key_ref;
        long ret;
 
-       keyring_ref = lookup_user_key(ringid, 1, 0, KEY_WRITE);
+       keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
        if (IS_ERR(keyring_ref)) {
                ret = PTR_ERR(keyring_ref);
                goto error;
        }
 
-       key_ref = lookup_user_key(id, 1, 0, KEY_LINK);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_LINK);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
                goto error2;
@@ -422,13 +432,13 @@ long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid)
        key_ref_t keyring_ref, key_ref;
        long ret;
 
-       keyring_ref = lookup_user_key(ringid, 0, 0, KEY_WRITE);
+       keyring_ref = lookup_user_key(ringid, 0, KEY_WRITE);
        if (IS_ERR(keyring_ref)) {
                ret = PTR_ERR(keyring_ref);
                goto error;
        }
 
-       key_ref = lookup_user_key(id, 0, 0, 0);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_FOR_UNLINK, 0);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
                goto error2;
@@ -464,7 +474,7 @@ long keyctl_describe_key(key_serial_t keyid,
        char *tmpbuf;
        long ret;
 
-       key_ref = lookup_user_key(keyid, 0, 1, KEY_VIEW);
+       key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_VIEW);
        if (IS_ERR(key_ref)) {
                /* viewing a key under construction is permitted if we have the
                 * authorisation token handy */
@@ -473,7 +483,8 @@ long keyctl_describe_key(key_serial_t keyid,
                        if (!IS_ERR(instkey)) {
                                key_put(instkey);
                                key_ref = lookup_user_key(keyid,
-                                                         0, 1, 0);
+                                                         KEY_LOOKUP_PARTIAL,
+                                                         0);
                                if (!IS_ERR(key_ref))
                                        goto okay;
                        }
@@ -557,7 +568,7 @@ long keyctl_keyring_search(key_serial_t ringid,
        }
 
        /* get the keyring at which to begin the search */
-       keyring_ref = lookup_user_key(ringid, 0, 0, KEY_SEARCH);
+       keyring_ref = lookup_user_key(ringid, 0, KEY_SEARCH);
        if (IS_ERR(keyring_ref)) {
                ret = PTR_ERR(keyring_ref);
                goto error2;
@@ -566,7 +577,8 @@ long keyctl_keyring_search(key_serial_t ringid,
        /* get the destination keyring if specified */
        dest_ref = NULL;
        if (destringid) {
-               dest_ref = lookup_user_key(destringid, 1, 0, KEY_WRITE);
+               dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE,
+                                          KEY_WRITE);
                if (IS_ERR(dest_ref)) {
                        ret = PTR_ERR(dest_ref);
                        goto error3;
@@ -636,7 +648,7 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
        long ret;
 
        /* find the key first */
-       key_ref = lookup_user_key(keyid, 0, 0, 0);
+       key_ref = lookup_user_key(keyid, 0, 0);
        if (IS_ERR(key_ref)) {
                ret = -ENOKEY;
                goto error;
@@ -699,7 +711,8 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
        if (uid == (uid_t) -1 && gid == (gid_t) -1)
                goto error;
 
-       key_ref = lookup_user_key(id, 1, 1, KEY_SETATTR);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
+                                 KEY_SETATTR);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
                goto error;
@@ -725,7 +738,7 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
        /* change the UID */
        if (uid != (uid_t) -1 && uid != key->uid) {
                ret = -ENOMEM;
-               newowner = key_user_lookup(uid);
+               newowner = key_user_lookup(uid, current_user_ns());
                if (!newowner)
                        goto error_put;
 
@@ -804,7 +817,8 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
        if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
                goto error;
 
-       key_ref = lookup_user_key(id, 1, 1, KEY_SETATTR);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
+                                 KEY_SETATTR);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
                goto error;
@@ -846,7 +860,7 @@ static long get_instantiation_keyring(key_serial_t ringid,
 
        /* if a specific keyring is nominated by ID, then use that */
        if (ringid > 0) {
-               dkref = lookup_user_key(ringid, 1, 0, KEY_WRITE);
+               dkref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
                if (IS_ERR(dkref))
                        return PTR_ERR(dkref);
                *_dest_keyring = key_ref_to_ptr(dkref);
@@ -859,7 +873,7 @@ static long get_instantiation_keyring(key_serial_t ringid,
        /* otherwise specify the destination keyring recorded in the
         * authorisation key (any KEY_SPEC_*_KEYRING) */
        if (ringid >= KEY_SPEC_REQUESTOR_KEYRING) {
-               *_dest_keyring = rka->dest_keyring;
+               *_dest_keyring = key_get(rka->dest_keyring);
                return 0;
        }
 
@@ -1082,7 +1096,8 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
        time_t expiry;
        long ret;
 
-       key_ref = lookup_user_key(id, 1, 1, KEY_SETATTR);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
+                                 KEY_SETATTR);
        if (IS_ERR(key_ref)) {
                ret = PTR_ERR(key_ref);
                goto error;
@@ -1100,6 +1115,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
        }
 
        key->expiry = expiry;
+       key_schedule_gc(key->expiry + key_gc_delay);
 
        up_write(&key->sem);
        key_put(key);
@@ -1169,7 +1185,7 @@ long keyctl_get_security(key_serial_t keyid,
        char *context;
        long ret;
 
-       key_ref = lookup_user_key(keyid, 0, 1, KEY_VIEW);
+       key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_VIEW);
        if (IS_ERR(key_ref)) {
                if (PTR_ERR(key_ref) != -EACCES)
                        return PTR_ERR(key_ref);
@@ -1178,10 +1194,10 @@ long keyctl_get_security(key_serial_t keyid,
                 * have the authorisation token handy */
                instkey = key_get_instantiation_authkey(keyid);
                if (IS_ERR(instkey))
-                       return PTR_ERR(key_ref);
+                       return PTR_ERR(instkey);
                key_put(instkey);
 
-               key_ref = lookup_user_key(keyid, 0, 1, 0);
+               key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 0);
                if (IS_ERR(key_ref))
                        return PTR_ERR(key_ref);
        }
@@ -1212,12 +1228,112 @@ long keyctl_get_security(key_serial_t keyid,
        return ret;
 }
 
+/*
+ * attempt to install the calling process's session keyring on the process's
+ * parent process
+ * - the keyring must exist and must grant us LINK permission
+ * - implements keyctl(KEYCTL_SESSION_TO_PARENT)
+ */
+long keyctl_session_to_parent(void)
+{
+       struct task_struct *me, *parent;
+       const struct cred *mycred, *pcred;
+       struct cred *cred, *oldcred;
+       key_ref_t keyring_r;
+       int ret;
+
+       keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK);
+       if (IS_ERR(keyring_r))
+               return PTR_ERR(keyring_r);
+
+       /* our parent is going to need a new cred struct, a new tgcred struct
+        * and new security data, so we allocate them here to prevent ENOMEM in
+        * our parent */
+       ret = -ENOMEM;
+       cred = cred_alloc_blank();
+       if (!cred)
+               goto error_keyring;
+
+       cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r);
+       keyring_r = NULL;
+
+       me = current;
+       write_lock_irq(&tasklist_lock);
+
+       parent = me->real_parent;
+       ret = -EPERM;
+
+       /* the parent mustn't be init and mustn't be a kernel thread */
+       if (parent->pid <= 1 || !parent->mm)
+               goto not_permitted;
+
+       /* the parent must be single threaded */
+       if (atomic_read(&parent->signal->count) != 1)
+               goto not_permitted;
+
+       /* the parent and the child must have different session keyrings or
+        * there's no point */
+       mycred = current_cred();
+       pcred = __task_cred(parent);
+       if (mycred == pcred ||
+           mycred->tgcred->session_keyring == pcred->tgcred->session_keyring)
+               goto already_same;
+
+       /* the parent must have the same effective ownership and mustn't be
+        * SUID/SGID */
+       if (pcred-> uid != mycred->euid ||
+           pcred->euid != mycred->euid ||
+           pcred->suid != mycred->euid ||
+           pcred-> gid != mycred->egid ||
+           pcred->egid != mycred->egid ||
+           pcred->sgid != mycred->egid)
+               goto not_permitted;
+
+       /* the keyrings must have the same UID */
+       if (pcred ->tgcred->session_keyring->uid != mycred->euid ||
+           mycred->tgcred->session_keyring->uid != mycred->euid)
+               goto not_permitted;
+
+       /* the LSM must permit the replacement of the parent's keyring with the
+        * keyring from this process */
+       ret = security_key_session_to_parent(mycred, pcred,
+                                            key_ref_to_ptr(keyring_r));
+       if (ret < 0)
+               goto not_permitted;
+
+       /* if there's an already pending keyring replacement, then we replace
+        * that */
+       oldcred = parent->replacement_session_keyring;
+
+       /* the replacement session keyring is applied just prior to userspace
+        * restarting */
+       parent->replacement_session_keyring = cred;
+       cred = NULL;
+       set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME);
+
+       write_unlock_irq(&tasklist_lock);
+       if (oldcred)
+               put_cred(oldcred);
+       return 0;
+
+already_same:
+       ret = 0;
+not_permitted:
+       write_unlock_irq(&tasklist_lock);
+       put_cred(cred);
+       return ret;
+
+error_keyring:
+       key_ref_put(keyring_r);
+       return ret;
+}
+
 /*****************************************************************************/
 /*
  * the key control system call
  */
-asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
-                          unsigned long arg4, unsigned long arg5)
+SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
+               unsigned long, arg4, unsigned long, arg5)
 {
        switch (option) {
        case KEYCTL_GET_KEYRING_ID:
@@ -1297,6 +1413,9 @@ asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
                                           (char __user *) arg3,
                                           (size_t) arg4);
 
+       case KEYCTL_SESSION_TO_PARENT:
+               return keyctl_session_to_parent();
+
        default:
                return -EOPNOTSUPP;
        }