]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
keys: Provide KEYCTL_GRANT_PERMISSION
authorDavid Howells <dhowells@redhat.com>
Thu, 27 Jun 2019 22:03:07 +0000 (23:03 +0100)
committerDavid Howells <dhowells@redhat.com>
Wed, 3 Jul 2019 12:05:22 +0000 (13:05 +0100)
Provide a keyctl() operation to grant/remove permissions.  The grant
operation, wrapped by libkeyutils, looks like:

int ret = keyctl_grant_permission(key_serial_t key,
  enum key_ace_subject_type type,
  unsigned int subject,
  unsigned int perm);

Where key is the key to be modified, type and subject represent the subject
to which permission is to be granted (or removed) and perm is the set of
permissions to be granted.  0 is returned on success.  SET_SECURITY
permission is required for this.

The subject type currently must be KEY_ACE_SUBJ_STANDARD for the moment
(other subject types will come along later).

For subject type KEY_ACE_SUBJ_STANDARD, the following subject values are
available:

KEY_ACE_POSSESSOR The possessor of the key
KEY_ACE_OWNER The owner of the key
KEY_ACE_GROUP The key's group
KEY_ACE_EVERYONE Everyone

perm lists the permissions to be granted:

KEY_ACE_VIEW Can view the key metadata
KEY_ACE_READ Can read the key content
KEY_ACE_WRITE Can update/modify the key content
KEY_ACE_SEARCH Can find the key by searching/requesting
KEY_ACE_LINK Can make a link to the key
KEY_ACE_SET_SECURITY Can set security
KEY_ACE_INVAL Can invalidate
KEY_ACE_REVOKE Can revoke
KEY_ACE_JOIN Can join this keyring
KEY_ACE_CLEAR Can clear this keyring

If an ACE already exists for the subject, then the permissions mask will be
overwritten; if perm is 0, it will be deleted.

Currently, the internal ACL is limited to a maximum of 16 entries.

For example:

int ret = keyctl_grant_permission(key,
  KEY_ACE_SUBJ_STANDARD,
  KEY_ACE_OWNER,
  KEY_ACE_VIEW | KEY_ACE_READ);

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

index e783bf957da82b7bfe5c04f74547cc0797f0c5a9..1f7a4e7372147f69639dce69145822b61e3fad32 100644 (file)
@@ -132,6 +132,7 @@ enum key_ace_standard_subject {
 #define KEYCTL_RESTRICT_KEYRING                29      /* Restrict keys allowed to link to a keyring */
 #define KEYCTL_MOVE                    30      /* Move keys between keyrings */
 #define KEYCTL_CAPABILITIES            31      /* Find capabilities of keyrings subsystem */
+#define KEYCTL_GRANT_PERMISSION                32      /* Grant a permit to a key */
 
 /* keyctl structures */
 struct keyctl_dh_params {
@@ -193,5 +194,6 @@ struct keyctl_pkey_params {
 #define KEYCTL_CAPS0_MOVE              0x80 /* KEYCTL_MOVE supported */
 #define KEYCTL_CAPS1_NS_KEYRING_NAME   0x01 /* Keyring names are per-user_namespace */
 #define KEYCTL_CAPS1_NS_KEY_TAG                0x02 /* Key indexing can include a namespace tag */
+#define KEYCTL_CAPS1_ACL_ALTERABLE     0x04 /* Keys have internal ACL that can be altered */
 
 #endif /*  _LINUX_KEYCTL_H */
index a53e30da20c516225fd95219ea236e20a58f8a6f..1eebb9a237b81449a0284e9e2b4752693841142e 100644 (file)
@@ -161,6 +161,8 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
 
        case KEYCTL_MOVE:
                return keyctl_keyring_move(arg2, arg3, arg4, arg5);
+       case KEYCTL_GRANT_PERMISSION:
+               return keyctl_grant_permission(arg2, arg3, arg4, arg5);
 
        case KEYCTL_CAPABILITIES:
                return keyctl_capabilities(compat_ptr(arg2), arg3);
index 9375d6289bb9940bc0779270a897a1cd290c32ac..5e27ebdf1937677253ebdbb3ac019885dc9d9bff 100644 (file)
@@ -342,6 +342,11 @@ static inline long keyctl_pkey_e_d_s(int op,
 
 extern long keyctl_capabilities(unsigned char __user *_buffer, size_t buflen);
 
+extern long keyctl_grant_permission(key_serial_t keyid,
+                                   enum key_ace_subject_type type,
+                                   unsigned int subject,
+                                   unsigned int perm);
+
 /*
  * Debugging key validation
  */
index c8911b430e5992e4609546855a331b5ac1d134ba..aa096c4080b2de223b43ceaae04809424a4eeb52 100644 (file)
@@ -41,7 +41,8 @@ static const unsigned char keyrings_capabilities[2] = {
               KEYCTL_CAPS0_MOVE
               ),
        [1] = (KEYCTL_CAPS1_NS_KEYRING_NAME |
-              KEYCTL_CAPS1_NS_KEY_TAG),
+              KEYCTL_CAPS1_NS_KEY_TAG |
+              KEYCTL_CAPS1_ACL_ALTERABLE),
 };
 
 static int key_get_type_from_user(char *type,
@@ -1891,6 +1892,11 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
                                           (key_serial_t)arg3,
                                           (key_serial_t)arg4,
                                           (unsigned int)arg5);
+       case KEYCTL_GRANT_PERMISSION:
+               return keyctl_grant_permission((key_serial_t)arg2,
+                                              (enum key_ace_subject_type)arg3,
+                                              (unsigned int)arg4,
+                                              (unsigned int)arg5);
 
        case KEYCTL_CAPABILITIES:
                return keyctl_capabilities((unsigned char __user *)arg2, (size_t)arg3);
index e3237bb2e9702a869f404395031491c3e0cbd4d8..11655a827ba13890b1a5692b6fabef0e151ec8ff 100644 (file)
@@ -278,3 +278,122 @@ long key_set_acl(struct key *key, struct key_acl *acl)
        key_put_acl(acl);
        return 0;
 }
+
+/*
+ * Allocate a new ACL with an extra ACE slot.
+ */
+static struct key_acl *key_alloc_acl(const struct key_acl *old_acl, int nr, int skip)
+{
+       struct key_acl *acl;
+       int nr_ace, i, j = 0;
+
+       nr_ace = old_acl->nr_ace + nr;
+       if (nr_ace > 16)
+               return ERR_PTR(-EINVAL);
+
+       acl = kzalloc(struct_size(acl, aces, nr_ace), GFP_KERNEL);
+       if (!acl)
+               return ERR_PTR(-ENOMEM);
+
+       refcount_set(&acl->usage, 1);
+       acl->nr_ace = nr_ace;
+       for (i = 0; i < old_acl->nr_ace; i++) {
+               if (i == skip)
+                       continue;
+               acl->aces[j] = old_acl->aces[i];
+               j++;
+       }
+       return acl;
+}
+
+/*
+ * Generate the revised ACL.
+ */
+static long key_change_acl(struct key *key, struct key_ace *new_ace)
+{
+       struct key_acl *acl, *old;
+       int i;
+
+       old = rcu_dereference_protected(key->acl, lockdep_is_held(&key->sem));
+
+       for (i = 0; i < old->nr_ace; i++)
+               if (old->aces[i].type == new_ace->type &&
+                   old->aces[i].subject_id == new_ace->subject_id)
+                       goto found_match;
+
+       if (new_ace->perm == 0)
+               return 0; /* No permissions to remove.  Add deny record? */
+
+       acl = key_alloc_acl(old, 1, -1);
+       if (IS_ERR(acl))
+               return PTR_ERR(acl);
+       acl->aces[i] = *new_ace;
+       goto change;
+
+found_match:
+       if (new_ace->perm == 0)
+               goto delete_ace;
+       if (new_ace->perm == old->aces[i].perm)
+               return 0;
+       acl = key_alloc_acl(old, 0, -1);
+       if (IS_ERR(acl))
+               return PTR_ERR(acl);
+       acl->aces[i].perm = new_ace->perm;
+       goto change;
+
+delete_ace:
+       acl = key_alloc_acl(old, -1, i);
+       if (IS_ERR(acl))
+               return PTR_ERR(acl);
+       goto change;
+
+change:
+       return key_set_acl(key, acl);
+}
+
+/*
+ * Add, alter or remove (if perm == 0) an ACE in a key's ACL.
+ */
+long keyctl_grant_permission(key_serial_t keyid,
+                            enum key_ace_subject_type type,
+                            unsigned int subject,
+                            unsigned int perm)
+{
+       struct key_ace new_ace;
+       struct key *key;
+       key_ref_t key_ref;
+       long ret;
+
+       new_ace.type = type;
+       new_ace.perm = perm;
+
+       switch (type) {
+       case KEY_ACE_SUBJ_STANDARD:
+               if (subject >= nr__key_ace_standard_subject)
+                       return -ENOENT;
+               new_ace.subject_id = subject;
+               break;
+
+       default:
+               return -ENOENT;
+       }
+
+       key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_SETSEC);
+       if (IS_ERR(key_ref)) {
+               ret = PTR_ERR(key_ref);
+               goto error;
+       }
+
+       key = key_ref_to_ptr(key_ref);
+
+       down_write(&key->sem);
+
+       /* If we're not the sysadmin, we can only change a key that we own */
+       ret = -EACCES;
+       if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid()))
+               ret = key_change_acl(key, &new_ace);
+       up_write(&key->sem);
+       key_put(key);
+error:
+       return ret;
+}