| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * pkey base: debug feature, pkey handler registry |
| * |
| * Copyright IBM Corp. 2024 |
| */ |
| |
| #define KMSG_COMPONENT "pkey" |
| #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
| |
| #include <linux/cpufeature.h> |
| #include <linux/init.h> |
| #include <linux/list.h> |
| #include <linux/module.h> |
| #include <linux/rculist.h> |
| |
| #include "pkey_base.h" |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("IBM Corporation"); |
| MODULE_DESCRIPTION("s390 protected key base and api"); |
| |
| /* |
| * pkey debug feature |
| */ |
| debug_info_t *pkey_dbf_info; |
| EXPORT_SYMBOL(pkey_dbf_info); |
| |
| /* |
| * pkey handler registry |
| */ |
| |
| static DEFINE_SPINLOCK(handler_list_write_lock); |
| static LIST_HEAD(handler_list); |
| |
| int pkey_handler_register(struct pkey_handler *handler) |
| { |
| const struct pkey_handler *h; |
| |
| if (!handler || |
| !handler->is_supported_key || |
| !handler->is_supported_keytype) |
| return -EINVAL; |
| |
| if (!try_module_get(handler->module)) |
| return -ENXIO; |
| |
| spin_lock(&handler_list_write_lock); |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(h, &handler_list, list) { |
| if (h == handler) { |
| rcu_read_unlock(); |
| spin_unlock(&handler_list_write_lock); |
| module_put(handler->module); |
| return -EEXIST; |
| } |
| } |
| rcu_read_unlock(); |
| |
| list_add_rcu(&handler->list, &handler_list); |
| spin_unlock(&handler_list_write_lock); |
| synchronize_rcu(); |
| |
| module_put(handler->module); |
| |
| PKEY_DBF_INFO("%s pkey handler '%s' registered\n", __func__, |
| handler->name ?: "<no name>"); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(pkey_handler_register); |
| |
| int pkey_handler_unregister(struct pkey_handler *handler) |
| { |
| spin_lock(&handler_list_write_lock); |
| list_del_rcu(&handler->list); |
| INIT_LIST_HEAD_RCU(&handler->list); |
| spin_unlock(&handler_list_write_lock); |
| synchronize_rcu(); |
| |
| PKEY_DBF_INFO("%s pkey handler '%s' unregistered\n", __func__, |
| handler->name ?: "<no name>"); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(pkey_handler_unregister); |
| |
| /* |
| * Handler invocation functions. |
| */ |
| |
| const struct pkey_handler *pkey_handler_get_keybased(const u8 *key, u32 keylen) |
| { |
| const struct pkey_handler *h; |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(h, &handler_list, list) { |
| if (!try_module_get(h->module)) |
| continue; |
| if (h->is_supported_key(key, keylen)) { |
| rcu_read_unlock(); |
| return h; |
| } |
| module_put(h->module); |
| } |
| rcu_read_unlock(); |
| |
| return NULL; |
| } |
| EXPORT_SYMBOL(pkey_handler_get_keybased); |
| |
| const struct pkey_handler *pkey_handler_get_keytypebased(enum pkey_key_type kt) |
| { |
| const struct pkey_handler *h; |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(h, &handler_list, list) { |
| if (!try_module_get(h->module)) |
| continue; |
| if (h->is_supported_keytype(kt)) { |
| rcu_read_unlock(); |
| return h; |
| } |
| module_put(h->module); |
| } |
| rcu_read_unlock(); |
| |
| return NULL; |
| } |
| EXPORT_SYMBOL(pkey_handler_get_keytypebased); |
| |
| void pkey_handler_put(const struct pkey_handler *handler) |
| { |
| const struct pkey_handler *h; |
| |
| if (!handler) |
| return; |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(h, &handler_list, list) { |
| if (h == handler) { |
| module_put(h->module); |
| break; |
| } |
| } |
| rcu_read_unlock(); |
| } |
| EXPORT_SYMBOL(pkey_handler_put); |
| |
| int pkey_handler_key_to_protkey(const struct pkey_apqn *apqns, size_t nr_apqns, |
| const u8 *key, u32 keylen, |
| u8 *protkey, u32 *protkeylen, u32 *protkeytype) |
| { |
| const struct pkey_handler *h; |
| int rc = -ENODEV; |
| |
| h = pkey_handler_get_keybased(key, keylen); |
| if (h && h->key_to_protkey) { |
| rc = h->key_to_protkey(apqns, nr_apqns, key, keylen, |
| protkey, protkeylen, |
| protkeytype); |
| } |
| pkey_handler_put(h); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(pkey_handler_key_to_protkey); |
| |
| /* |
| * This handler invocation is special as there may be more than |
| * one handler providing support for the very same key (type). |
| * And the handler may not respond true on is_supported_key(), |
| * so simple try and check return value here. |
| */ |
| int pkey_handler_slowpath_key_to_protkey(const struct pkey_apqn *apqns, |
| size_t nr_apqns, |
| const u8 *key, u32 keylen, |
| u8 *protkey, u32 *protkeylen, |
| u32 *protkeytype) |
| { |
| const struct pkey_handler *h, *htmp[10]; |
| int i, n = 0, rc = -ENODEV; |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(h, &handler_list, list) { |
| if (!try_module_get(h->module)) |
| continue; |
| if (h->slowpath_key_to_protkey && n < ARRAY_SIZE(htmp)) |
| htmp[n++] = h; |
| else |
| module_put(h->module); |
| } |
| rcu_read_unlock(); |
| |
| for (i = 0; i < n; i++) { |
| h = htmp[i]; |
| if (rc) |
| rc = h->slowpath_key_to_protkey(apqns, nr_apqns, |
| key, keylen, |
| protkey, protkeylen, |
| protkeytype); |
| module_put(h->module); |
| } |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(pkey_handler_slowpath_key_to_protkey); |
| |
| int pkey_handler_gen_key(const struct pkey_apqn *apqns, size_t nr_apqns, |
| u32 keytype, u32 keysubtype, |
| u32 keybitsize, u32 flags, |
| u8 *keybuf, u32 *keybuflen, u32 *keyinfo) |
| { |
| const struct pkey_handler *h; |
| int rc = -ENODEV; |
| |
| h = pkey_handler_get_keytypebased(keysubtype); |
| if (h && h->gen_key) { |
| rc = h->gen_key(apqns, nr_apqns, keytype, keysubtype, |
| keybitsize, flags, |
| keybuf, keybuflen, keyinfo); |
| } |
| pkey_handler_put(h); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(pkey_handler_gen_key); |
| |
| int pkey_handler_clr_to_key(const struct pkey_apqn *apqns, size_t nr_apqns, |
| u32 keytype, u32 keysubtype, |
| u32 keybitsize, u32 flags, |
| const u8 *clrkey, u32 clrkeylen, |
| u8 *keybuf, u32 *keybuflen, u32 *keyinfo) |
| { |
| const struct pkey_handler *h; |
| int rc = -ENODEV; |
| |
| h = pkey_handler_get_keytypebased(keysubtype); |
| if (h && h->clr_to_key) { |
| rc = h->clr_to_key(apqns, nr_apqns, keytype, keysubtype, |
| keybitsize, flags, clrkey, clrkeylen, |
| keybuf, keybuflen, keyinfo); |
| } |
| pkey_handler_put(h); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(pkey_handler_clr_to_key); |
| |
| int pkey_handler_verify_key(const u8 *key, u32 keylen, |
| u16 *card, u16 *dom, |
| u32 *keytype, u32 *keybitsize, u32 *flags) |
| { |
| const struct pkey_handler *h; |
| int rc = -ENODEV; |
| |
| h = pkey_handler_get_keybased(key, keylen); |
| if (h && h->verify_key) { |
| rc = h->verify_key(key, keylen, card, dom, |
| keytype, keybitsize, flags); |
| } |
| pkey_handler_put(h); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(pkey_handler_verify_key); |
| |
| int pkey_handler_apqns_for_key(const u8 *key, u32 keylen, u32 flags, |
| struct pkey_apqn *apqns, size_t *nr_apqns) |
| { |
| const struct pkey_handler *h; |
| int rc = -ENODEV; |
| |
| h = pkey_handler_get_keybased(key, keylen); |
| if (h && h->apqns_for_key) |
| rc = h->apqns_for_key(key, keylen, flags, apqns, nr_apqns); |
| pkey_handler_put(h); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(pkey_handler_apqns_for_key); |
| |
| int pkey_handler_apqns_for_keytype(enum pkey_key_type keysubtype, |
| u8 cur_mkvp[32], u8 alt_mkvp[32], u32 flags, |
| struct pkey_apqn *apqns, size_t *nr_apqns) |
| { |
| const struct pkey_handler *h; |
| int rc = -ENODEV; |
| |
| h = pkey_handler_get_keytypebased(keysubtype); |
| if (h && h->apqns_for_keytype) { |
| rc = h->apqns_for_keytype(keysubtype, |
| cur_mkvp, alt_mkvp, flags, |
| apqns, nr_apqns); |
| } |
| pkey_handler_put(h); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(pkey_handler_apqns_for_keytype); |
| |
| void pkey_handler_request_modules(void) |
| { |
| #ifdef CONFIG_MODULES |
| static const char * const pkey_handler_modules[] = { |
| "pkey_cca", "pkey_ep11", "pkey_pckmo" }; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(pkey_handler_modules); i++) { |
| const struct pkey_handler *h; |
| bool found = false; |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(h, &handler_list, list) { |
| if (h->module && |
| !strcmp(h->module->name, pkey_handler_modules[i])) { |
| found = true; |
| break; |
| } |
| } |
| rcu_read_unlock(); |
| if (!found) { |
| pr_debug("request_module(%s)\n", pkey_handler_modules[i]); |
| request_module(pkey_handler_modules[i]); |
| } |
| } |
| #endif |
| } |
| EXPORT_SYMBOL(pkey_handler_request_modules); |
| |
| /* |
| * Module init |
| */ |
| static int __init pkey_init(void) |
| { |
| int rc; |
| |
| /* init debug feature */ |
| pkey_dbf_info = debug_register("pkey", 1, 1, 5 * sizeof(long)); |
| debug_register_view(pkey_dbf_info, &debug_sprintf_view); |
| debug_set_level(pkey_dbf_info, 4); |
| |
| /* the handler registry does not need any init */ |
| |
| rc = pkey_api_init(); |
| if (rc) |
| debug_unregister(pkey_dbf_info); |
| |
| return rc; |
| } |
| |
| /* |
| * Module exit |
| */ |
| static void __exit pkey_exit(void) |
| { |
| pkey_api_exit(); |
| } |
| |
| module_cpu_feature_match(S390_CPU_FEATURE_MSA, pkey_init); |
| module_exit(pkey_exit); |