diff options
Diffstat (limited to 'security/keys/permission.c')
-rw-r--r-- | security/keys/permission.c | 361 |
1 files changed, 325 insertions, 36 deletions
diff --git a/security/keys/permission.c b/security/keys/permission.c index 085f907b64ac..fd8a5dc6910a 100644 --- a/security/keys/permission.c +++ b/security/keys/permission.c @@ -7,13 +7,67 @@ #include <linux/export.h> #include <linux/security.h> +#include <linux/user_namespace.h> +#include <linux/uaccess.h> #include "internal.h" +struct key_acl default_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~KEY_ACE_JOIN), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; +EXPORT_SYMBOL(default_key_acl); + +struct key_acl joinable_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~KEY_ACE_JOIN), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_LINK | KEY_ACE_JOIN), + } +}; +EXPORT_SYMBOL(joinable_keyring_acl); + +struct key_acl internal_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH), + } +}; +EXPORT_SYMBOL(internal_key_acl); + +struct key_acl internal_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH), + } +}; +EXPORT_SYMBOL(internal_keyring_acl); + +struct key_acl internal_writable_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | KEY_ACE_SEARCH), + } +}; +EXPORT_SYMBOL(internal_writable_keyring_acl); + /** * key_task_permission - Check a key can be used * @key_ref: The key to check. * @cred: The credentials to use. - * @perm: The permissions to check for. + * @desired_perm: The permission to check for. * * Check to see whether permission is granted to use a key in the desired way, * but permit the security modules to override. @@ -24,53 +78,73 @@ * permissions bits or the LSM check. */ int key_task_permission(const key_ref_t key_ref, const struct cred *cred, - unsigned perm) + unsigned int desired_perm) { - struct key *key; - key_perm_t kperm; - int ret; + const struct key_acl *acl; + const struct key *key; + unsigned int allow = 0; + int i; + + BUILD_BUG_ON(KEY_NEED_VIEW != KEY_ACE_VIEW || + KEY_NEED_READ != KEY_ACE_READ || + KEY_NEED_WRITE != KEY_ACE_WRITE || + KEY_NEED_SEARCH != KEY_ACE_SEARCH || + KEY_NEED_LINK != KEY_ACE_LINK || + KEY_NEED_SETSEC != KEY_ACE_SET_SECURITY || + KEY_NEED_INVAL != KEY_ACE_INVAL || + KEY_NEED_REVOKE != KEY_ACE_REVOKE || + KEY_NEED_JOIN != KEY_ACE_JOIN || + KEY_NEED_CLEAR != KEY_ACE_CLEAR); key = key_ref_to_ptr(key_ref); - /* use the second 8-bits of permissions for keys the caller owns */ - if (uid_eq(key->uid, cred->fsuid)) { - kperm = key->perm >> 16; - goto use_these_perms; - } + rcu_read_lock(); - /* use the third 8-bits of permissions for keys the caller has a group - * membership in common with */ - if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) { - if (gid_eq(key->gid, cred->fsgid)) { - kperm = key->perm >> 8; - goto use_these_perms; - } + acl = rcu_dereference(key->acl); + if (!acl || acl->nr_ace == 0) + goto no_access_rcu; - ret = groups_search(cred->group_info, key->gid); - if (ret) { - kperm = key->perm >> 8; - goto use_these_perms; + for (i = 0; i < acl->nr_ace; i++) { + const struct key_ace *ace = &acl->aces[i]; + + switch (ace->type) { + case KEY_ACE_SUBJ_STANDARD: + switch (ace->subject_id) { + case KEY_ACE_POSSESSOR: + if (is_key_possessed(key_ref)) + allow |= ace->perm; + break; + case KEY_ACE_OWNER: + if (uid_eq(key->uid, cred->fsuid)) + allow |= ace->perm; + break; + case KEY_ACE_GROUP: + if (gid_valid(key->gid)) { + if (gid_eq(key->gid, cred->fsgid)) + allow |= ace->perm; + else if (groups_search(cred->group_info, key->gid)) + allow |= ace->perm; + } + break; + case KEY_ACE_EVERYONE: + allow |= ace->perm; + break; + } + break; } } - /* otherwise use the least-significant 8-bits */ - kperm = key->perm; - -use_these_perms: + rcu_read_unlock(); - /* use the top 8-bits of permissions for keys the caller possesses - * - possessor permissions are additive with other permissions - */ - if (is_key_possessed(key_ref)) - kperm |= key->perm >> 24; + if (!(allow & desired_perm)) + goto no_access; - kperm = kperm & perm & KEY_NEED_ALL; + return security_key_permission(key_ref, cred, desired_perm); - if (kperm != perm) - return -EACCES; - - /* let LSM be the final arbiter */ - return security_key_permission(key_ref, cred, perm); +no_access_rcu: + rcu_read_unlock(); +no_access: + return -EACCES; } EXPORT_SYMBOL(key_task_permission); @@ -104,3 +178,218 @@ int key_validate(const struct key *key) return 0; } EXPORT_SYMBOL(key_validate); + +/* + * Roughly render an ACL to an old-style permissions mask. We cannot + * accurately render what the ACL, particularly if it has ACEs that represent + * subjects outside of { poss, user, group, other }. + */ +unsigned int key_acl_to_perm(const struct key_acl *acl) +{ + unsigned int perm = 0, tperm; + int i; + + BUILD_BUG_ON(KEY_OTH_VIEW != KEY_ACE_VIEW || + KEY_OTH_READ != KEY_ACE_READ || + KEY_OTH_WRITE != KEY_ACE_WRITE || + KEY_OTH_SEARCH != KEY_ACE_SEARCH || + KEY_OTH_LINK != KEY_ACE_LINK || + KEY_OTH_SETATTR != KEY_ACE_SET_SECURITY); + + if (!acl || acl->nr_ace == 0) + return 0; + + for (i = 0; i < acl->nr_ace; i++) { + const struct key_ace *ace = &acl->aces[i]; + + switch (ace->type) { + case KEY_ACE_SUBJ_STANDARD: + tperm = ace->perm & KEY_OTH_ALL; + + /* Invalidation and joining were allowed by SEARCH */ + if (ace->perm & (KEY_ACE_INVAL | KEY_ACE_JOIN)) + tperm |= KEY_OTH_SEARCH; + + /* Revocation was allowed by either SETATTR or WRITE */ + if ((ace->perm & KEY_ACE_REVOKE) && !(tperm & KEY_OTH_SETATTR)) + tperm |= KEY_OTH_WRITE; + + /* Clearing was allowed by WRITE */ + if (ace->perm & KEY_ACE_CLEAR) + tperm |= KEY_OTH_WRITE; + + switch (ace->subject_id) { + case KEY_ACE_POSSESSOR: + perm |= tperm << 24; + break; + case KEY_ACE_OWNER: + perm |= tperm << 16; + break; + case KEY_ACE_GROUP: + perm |= tperm << 8; + break; + case KEY_ACE_EVERYONE: + perm |= tperm << 0; + break; + } + } + } + + return perm; +} + +/* + * Destroy a key's ACL. + */ +void key_put_acl(struct key_acl *acl) +{ + if (acl && refcount_dec_and_test(&acl->usage)) + kfree_rcu(acl, rcu); +} + +/* + * Try to set the ACL. This either attaches or discards the proposed ACL. + */ +long key_set_acl(struct key *key, struct key_acl *acl) +{ + int i; + + /* If we're not the sysadmin, we can only change a key that we own. */ + if (!capable(CAP_SYS_ADMIN) && !uid_eq(key->uid, current_fsuid())) { + key_put_acl(acl); + return -EACCES; + } + + for (i = 0; i < acl->nr_ace; i++) { + const struct key_ace *ace = &acl->aces[i]; + if (ace->type == KEY_ACE_SUBJ_STANDARD && + ace->subject_id == KEY_ACE_POSSESSOR) { + if (ace->perm & KEY_ACE_VIEW) + acl->possessor_viewable = true; + break; + } + } + + rcu_swap_protected(key->acl, acl, lockdep_is_held(&key->sem)); + 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; +} |