keys: Add a keyctl to move a key between keyrings
authorDavid Howells <dhowells@redhat.com>
Mon, 20 May 2019 20:51:50 +0000 (21:51 +0100)
committerDavid Howells <dhowells@redhat.com>
Thu, 30 May 2019 21:44:48 +0000 (22:44 +0100)
Add a keyctl to atomically move a link to a key from one keyring to
another.  The key must exist in "from" keyring and a flag can be given to
cause the operation to fail if there's a matching key already in the "to"
keyring.

This can be done with:

keyctl(KEYCTL_MOVE,
       key_serial_t key,
       key_serial_t from_keyring,
       key_serial_t to_keyring,
       unsigned int flags);

The key being moved must grant Link permission and both keyrings must grant
Write permission.

flags should be 0 or KEYCTL_MOVE_EXCL, with the latter preventing
displacement of a matching key from the "to" keyring.

Signed-off-by: David Howells <dhowells@redhat.com>
Documentation/security/keys/core.rst
include/linux/key.h
include/uapi/linux/keyctl.h
security/keys/compat.c
security/keys/internal.h
security/keys/keyctl.c
security/keys/keyring.c

index 9521c42..823d29b 100644 (file)
@@ -577,6 +577,27 @@ The keyctl syscall functions are:
      added.
 
 
+  *  Move a key from one keyring to another::
+
+       long keyctl(KEYCTL_MOVE,
+                   key_serial_t id,
+                   key_serial_t from_ring_id,
+                   key_serial_t to_ring_id,
+                   unsigned int flags);
+
+     Move the key specified by "id" from the keyring specified by
+     "from_ring_id" to the keyring specified by "to_ring_id".  If the two
+     keyrings are the same, nothing is done.
+
+     "flags" can have KEYCTL_MOVE_EXCL set in it to cause the operation to fail
+     with EEXIST if a matching key exists in the destination keyring, otherwise
+     such a key will be replaced.
+
+     A process must have link permission on the key for this function to be
+     successful and write permission on both keyrings.  Any errors that can
+     occur from KEYCTL_LINK also apply on the destination keyring here.
+
+
   *  Unlink a key or keyring from another keyring::
 
        long keyctl(KEYCTL_UNLINK, key_serial_t keyring, key_serial_t key);
index 1f09aad..612e1cf 100644 (file)
@@ -310,6 +310,11 @@ extern int key_update(key_ref_t key,
 extern int key_link(struct key *keyring,
                    struct key *key);
 
+extern int key_move(struct key *key,
+                   struct key *from_keyring,
+                   struct key *to_keyring,
+                   unsigned int flags);
+
 extern int key_unlink(struct key *keyring,
                      struct key *key);
 
index f45ee0f..fd9fb11 100644 (file)
@@ -67,6 +67,7 @@
 #define KEYCTL_PKEY_SIGN               27      /* Create a public key signature */
 #define KEYCTL_PKEY_VERIFY             28      /* Verify a public key signature */
 #define KEYCTL_RESTRICT_KEYRING                29      /* Restrict keys allowed to link to a keyring */
+#define KEYCTL_MOVE                    30      /* Move keys between keyrings */
 
 /* keyctl structures */
 struct keyctl_dh_params {
@@ -112,4 +113,6 @@ struct keyctl_pkey_params {
        __u32           __spare[7];
 };
 
+#define KEYCTL_MOVE_EXCL       0x00000001 /* Do not displace from the to-keyring */
+
 #endif /*  _LINUX_KEYCTL_H */
index 9482df6..b326bc4 100644 (file)
@@ -159,6 +159,9 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
                return keyctl_pkey_verify(compat_ptr(arg2), compat_ptr(arg3),
                                          compat_ptr(arg4), compat_ptr(arg5));
 
+       case KEYCTL_MOVE:
+               return keyctl_keyring_move(arg2, arg3, arg4, arg5);
+
        default:
                return -EOPNOTSUPP;
        }
index 25cdd0c..b54a58c 100644 (file)
@@ -95,6 +95,8 @@ extern void key_type_put(struct key_type *ktype);
 
 extern int __key_link_lock(struct key *keyring,
                           const struct keyring_index_key *index_key);
+extern int __key_move_lock(struct key *l_keyring, struct key *u_keyring,
+                          const struct keyring_index_key *index_key);
 extern int __key_link_begin(struct key *keyring,
                            const struct keyring_index_key *index_key,
                            struct assoc_array_edit **_edit);
@@ -217,6 +219,7 @@ extern long keyctl_update_key(key_serial_t, const void __user *, size_t);
 extern long keyctl_revoke_key(key_serial_t);
 extern long keyctl_keyring_clear(key_serial_t);
 extern long keyctl_keyring_link(key_serial_t, key_serial_t);
+extern long keyctl_keyring_move(key_serial_t, key_serial_t, key_serial_t, unsigned int);
 extern long keyctl_keyring_unlink(key_serial_t, key_serial_t);
 extern long keyctl_describe_key(key_serial_t, char __user *, size_t);
 extern long keyctl_keyring_search(key_serial_t, const char __user *,
index 0f947bc..bbfe7d9 100644 (file)
@@ -573,6 +573,52 @@ error:
 }
 
 /*
+ * 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.
  *
  * The key must grant the caller View permission for this to work.
@@ -1772,6 +1818,12 @@ 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);
+
        default:
                return -EOPNOTSUPP;
        }
index 12acad3..67066bb 100644 (file)
@@ -1222,6 +1222,40 @@ int __key_link_lock(struct key *keyring,
 }
 
 /*
+ * Lock keyrings for move (link/unlink combination).
+ */
+int __key_move_lock(struct key *l_keyring, struct key *u_keyring,
+                   const struct keyring_index_key *index_key)
+       __acquires(&l_keyring->sem)
+       __acquires(&u_keyring->sem)
+       __acquires(&keyring_serialise_link_lock)
+{
+       if (l_keyring->type != &key_type_keyring ||
+           u_keyring->type != &key_type_keyring)
+               return -ENOTDIR;
+
+       /* We have to be very careful here to take the keyring locks in the
+        * right order, lest we open ourselves to deadlocking against another
+        * move operation.
+        */
+       if (l_keyring < u_keyring) {
+               down_write(&l_keyring->sem);
+               down_write_nested(&u_keyring->sem, 1);
+       } else {
+               down_write(&u_keyring->sem);
+               down_write_nested(&l_keyring->sem, 1);
+       }
+
+       /* Serialise link/link calls to prevent parallel calls causing a cycle
+        * when linking two keyring in opposite orders.
+        */
+       if (index_key->type == &key_type_keyring)
+               mutex_lock(&keyring_serialise_link_lock);
+
+       return 0;
+}
+
+/*
  * Preallocate memory so that a key can be linked into to a keyring.
  */
 int __key_link_begin(struct key *keyring,
@@ -1495,6 +1529,80 @@ int key_unlink(struct key *keyring, struct key *key)
 EXPORT_SYMBOL(key_unlink);
 
 /**
+ * key_move - Move a key from one keyring to another
+ * @key: The key to move
+ * @from_keyring: The keyring to remove the link from.
+ * @to_keyring: The keyring to make the link in.
+ * @flags: Qualifying flags, such as KEYCTL_MOVE_EXCL.
+ *
+ * Make a link in @to_keyring to a key, such that the keyring holds a reference
+ * on that key and the key can potentially be found by searching that keyring
+ * whilst simultaneously removing a link to the key from @from_keyring.
+ *
+ * This function will write-lock both keyring's semaphores and will consume
+ * some of the user's key data quota to hold the link on @to_keyring.
+ *
+ * Returns 0 if successful, -ENOTDIR if either keyring isn't a keyring,
+ * -EKEYREVOKED if either keyring has been revoked, -ENFILE if the second
+ * keyring is full, -EDQUOT if there is insufficient key data quota remaining
+ * to add another link or -ENOMEM if there's insufficient memory.  If
+ * KEYCTL_MOVE_EXCL is set, then -EEXIST will be returned if there's already a
+ * matching key in @to_keyring.
+ *
+ * It is assumed that the caller has checked that it is permitted for a link to
+ * be made (the keyring should have Write permission and the key Link
+ * permission).
+ */
+int key_move(struct key *key,
+            struct key *from_keyring,
+            struct key *to_keyring,
+            unsigned int flags)
+{
+       struct assoc_array_edit *from_edit = NULL, *to_edit = NULL;
+       int ret;
+
+       kenter("%d,%d,%d", key->serial, from_keyring->serial, to_keyring->serial);
+
+       if (from_keyring == to_keyring)
+               return 0;
+
+       key_check(key);
+       key_check(from_keyring);
+       key_check(to_keyring);
+
+       ret = __key_move_lock(from_keyring, to_keyring, &key->index_key);
+       if (ret < 0)
+               goto out;
+       ret = __key_unlink_begin(from_keyring, key, &from_edit);
+       if (ret < 0)
+               goto error;
+       ret = __key_link_begin(to_keyring, &key->index_key, &to_edit);
+       if (ret < 0)
+               goto error;
+
+       ret = -EEXIST;
+       if (to_edit->dead_leaf && (flags & KEYCTL_MOVE_EXCL))
+               goto error;
+
+       ret = __key_link_check_restriction(to_keyring, key);
+       if (ret < 0)
+               goto error;
+       ret = __key_link_check_live_key(to_keyring, key);
+       if (ret < 0)
+               goto error;
+
+       __key_unlink(from_keyring, key, &from_edit);
+       __key_link(key, &to_edit);
+error:
+       __key_link_end(to_keyring, &key->index_key, to_edit);
+       __key_unlink_end(from_keyring, key, from_edit);
+out:
+       kleave(" = %d", ret);
+       return ret;
+}
+EXPORT_SYMBOL(key_move);
+
+/**
  * keyring_clear - Clear a keyring
  * @keyring: The keyring to clear.
  *