Merge tag 'keys-misc-20190619' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowe...
[linux-block.git] / security / keys / keyctl.c
index 5aa605ef8d9d1572d29207e1bb61a6fc3740788f..741e4ba382df5c38fca63884daa2739ab1f85fb2 100644 (file)
 
 #define KEY_MAX_DESC_SIZE 4096
 
+static const unsigned char keyrings_capabilities[1] = {
+       [0] = (KEYCTL_CAPS0_CAPABILITIES |
+              (IS_ENABLED(CONFIG_PERSISTENT_KEYRINGS)  ? KEYCTL_CAPS0_PERSISTENT_KEYRINGS : 0) |
+              (IS_ENABLED(CONFIG_KEY_DH_OPERATIONS)    ? KEYCTL_CAPS0_DIFFIE_HELLMAN : 0) |
+              (IS_ENABLED(CONFIG_ASYMMETRIC_KEY_TYPE)  ? KEYCTL_CAPS0_PUBLIC_KEY : 0) |
+              (IS_ENABLED(CONFIG_BIG_KEYS)             ? KEYCTL_CAPS0_BIG_KEY : 0) |
+              KEYCTL_CAPS0_INVALIDATE |
+              KEYCTL_CAPS0_RESTRICT_KEYRING |
+              KEYCTL_CAPS0_MOVE
+              ),
+};
+
 static int key_get_type_from_user(char *type,
                                  const char __user *_type,
                                  unsigned len)
@@ -568,6 +580,52 @@ error:
        return ret;
 }
 
+/*
+ * Move a link to a key from one keyring to another, displacing any matching
+ * key from the destination keyring.
+ *
+ * The key must grant the caller Link permission and both keyrings must grant
+ * the caller Write permission.  There must also be a link in the from keyring
+ * to the key.  If both keyrings are the same, nothing is done.
+ *
+ * If successful, 0 will be returned.
+ */
+long keyctl_keyring_move(key_serial_t id, key_serial_t from_ringid,
+                        key_serial_t to_ringid, unsigned int flags)
+{
+       key_ref_t key_ref, from_ref, to_ref;
+       long ret;
+
+       if (flags & ~KEYCTL_MOVE_EXCL)
+               return -EINVAL;
+
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_NEED_LINK);
+       if (IS_ERR(key_ref))
+               return PTR_ERR(key_ref);
+
+       from_ref = lookup_user_key(from_ringid, 0, KEY_NEED_WRITE);
+       if (IS_ERR(from_ref)) {
+               ret = PTR_ERR(from_ref);
+               goto error2;
+       }
+
+       to_ref = lookup_user_key(to_ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
+       if (IS_ERR(to_ref)) {
+               ret = PTR_ERR(to_ref);
+               goto error3;
+       }
+
+       ret = key_move(key_ref_to_ptr(key_ref), key_ref_to_ptr(from_ref),
+                      key_ref_to_ptr(to_ref), flags);
+
+       key_ref_put(to_ref);
+error3:
+       key_ref_put(from_ref);
+error2:
+       key_ref_put(key_ref);
+       return ret;
+}
+
 /*
  * Return a description of a key to userspace.
  *
@@ -1520,7 +1578,8 @@ long keyctl_session_to_parent(void)
 
        ret = -EPERM;
        oldwork = NULL;
-       parent = me->real_parent;
+       parent = rcu_dereference_protected(me->real_parent,
+                                          lockdep_is_held(&tasklist_lock));
 
        /* the parent mustn't be init and mustn't be a kernel thread */
        if (parent->pid <= 1 || !parent->mm)
@@ -1627,6 +1686,26 @@ error:
        return ret;
 }
 
+/*
+ * Get keyrings subsystem capabilities.
+ */
+long keyctl_capabilities(unsigned char __user *_buffer, size_t buflen)
+{
+       size_t size = buflen;
+
+       if (size > 0) {
+               if (size > sizeof(keyrings_capabilities))
+                       size = sizeof(keyrings_capabilities);
+               if (copy_to_user(_buffer, keyrings_capabilities, size) != 0)
+                       return -EFAULT;
+               if (size < buflen &&
+                   clear_user(_buffer + size, buflen - size) != 0)
+                       return -EFAULT;
+       }
+
+       return sizeof(keyrings_capabilities);
+}
+
 /*
  * The key control system call
  */
@@ -1767,6 +1846,15 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
                        (const void __user *)arg4,
                        (const void __user *)arg5);
 
+       case KEYCTL_MOVE:
+               return keyctl_keyring_move((key_serial_t)arg2,
+                                          (key_serial_t)arg3,
+                                          (key_serial_t)arg4,
+                                          (unsigned int)arg5);
+
+       case KEYCTL_CAPABILITIES:
+               return keyctl_capabilities((unsigned char __user *)arg2, (size_t)arg3);
+
        default:
                return -EOPNOTSUPP;
        }