It's base on the kernel patches of Fedora 20.
This patch set add the support to MODSIGN mechanism for revoke kernel module by hash or public key. As MokListRT, EFI bootloader(e.g. shim) should maintain the MokListXRT container the format the same with dbx. The patches will check the hash of kernel module before load it.
I doesn't add the module hash blacklist support to dbx because UEFI BIOS of Intel DQ57TM, my development board, doesn't allow set hash of non-efi file to dbx.
v2: + Use the digest generated by mod_make_digest() to avoid computing the hash twice. + Also check the hash of whole module file is not in the module hash blacklist. + Change the statements of information log. + The hash type need the same with CONFIG_MODULE_SIG_HASH. + Handle the return value of func() + Move the logic of parsing EFI signature database to parse_efi_signature_db() for collecting certificates and hashes. + Still parsing hashes when signature parsing fail on MOKx.
Lee, Chun-Yi (2): MODSIGN: check hash of kernel module in blacklist MODSIGN: load hash blacklist of modules from MOKx
crypto/asymmetric_keys/efi_parser.c | 80 +++++++++++++++++++---------- include/linux/efi.h | 16 ++++++ kernel/modsign_uefi.c | 97 ++++++++++++++++++++++++++++++++++- kernel/module-internal.h | 13 +++++ kernel/module_signing.c | 86 ++++++++++++++++++++++++++++++- 5 files changed, 261 insertions(+), 31 deletions(-)
This patch introduces a blacklist list of kernel module's hash. It check the blacklist before checking kernel module signature. It didn't limit what hash algorithm used but the module of hash algorithm need build-in or put in initrd for verify kernel module in initrd.
v2: + Use the digest generated by mod_make_digest() to avoid computing the hash twice. + Also check the hash of whole module file is not in the module hash blacklist. + Change the statements of information log.
Signed-off-by: Lee, Chun-Yi jlee@suse.com --- kernel/module-internal.h | 14 ++++++++ kernel/module.c | 9 +++++- kernel/module_signing.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletions(-)
Index: linux-3.12-SLE12/kernel/module-internal.h =================================================================== --- linux-3.12-SLE12.orig/kernel/module-internal.h +++ linux-3.12-SLE12/kernel/module-internal.h @@ -9,4 +9,17 @@ * 2 of the Licence, or (at your option) any later version. */
+/* + * Module hash. + */ +struct module_hash { + struct list_head list; /* list of all hashs */ + u8 hash; /* Hash algorithm [enum pkey_hash_algo] */ + char *hash_name; /* nams string of hash */ + size_t size; /* size of hash */ + u8 hash_data[]; /* Hash data */ +}; + +extern struct list_head module_hash_blacklist; + extern int mod_verify_sig(const void *mod, unsigned long *_modlen); Index: linux-3.12-SLE12/kernel/module_signing.c =================================================================== --- linux-3.12-SLE12.orig/kernel/module_signing.c +++ linux-3.12-SLE12/kernel/module_signing.c @@ -11,12 +11,15 @@
#include <linux/kernel.h> #include <linux/err.h> +#include <linux/module.h> #include <crypto/public_key.h> #include <crypto/hash.h> #include <keys/asymmetric-type.h> #include <keys/system_keyring.h> #include "module-internal.h"
+LIST_HEAD(module_hash_blacklist); + /* * Module signature information block. * @@ -193,6 +196,82 @@ static struct key *request_asymmetric_ke return key_ref_to_ptr(key); }
+static int check_blacklist(const char *hash_algo_name, const void *hash) +{ + struct module_hash *module_hash; + int ret = 0; + + list_for_each_entry(module_hash, &module_hash_blacklist, list) { + if (strcmp(hash_algo_name, module_hash->hash_name)) + continue; + + if (!memcmp(hash, module_hash->hash_data, module_hash->size)) { + ret = -EKEYREJECTED; + pr_info("Module hash is in the module hash blacklist: " + "%*phN\n", (int)module_hash->size, + module_hash->hash_data); + break; + } + } + + return ret; +} + +static int mod_verify_hash(const void *mod, unsigned long modlen, + struct public_key_signature *pks) +{ + const char *pks_hash_algo = pkey_hash_algo_name[pks->pkey_hash_algo]; + struct crypto_shash *tfm; + struct shash_desc *desc; + size_t digest_size, desc_size; + u8 *digest; + int ret = 0; + + if (list_empty(&module_hash_blacklist)) + return 0; + + /* check digest of module is not in hash blacklist */ + ret = check_blacklist(pks_hash_algo, pks->digest); + if (ret) + goto error_return; + + /* check hash of whole module file */ + tfm = crypto_alloc_shash(pks_hash_algo, 0, 0); + if (IS_ERR(tfm)) + goto error_return; + + desc_size = crypto_shash_descsize(tfm) + sizeof(*desc); + digest_size = crypto_shash_digestsize(tfm); + digest = kzalloc(digest_size + desc_size, GFP_KERNEL); + if (!digest) { + pr_err("digest memory buffer allocate fail\n"); + ret = -ENOMEM; + goto error_digest; + } + desc = (void *)digest + digest_size; + desc->tfm = tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + ret = crypto_shash_init(desc); + if (ret < 0) + goto error_shash; + + ret = crypto_shash_finup(desc, mod, modlen, digest); + if (ret < 0) + goto error_shash; + + pr_debug("%ld digest: %*phN\n", modlen, (int) digest_size, digest); + + /* check the hash of whole module file including signature */ + ret = check_blacklist(pks_hash_algo, digest); + +error_shash: + kfree(digest); +error_digest: + crypto_free_shash(tfm); +error_return: + return ret; +} + /* * Verify the signature on a module. */ @@ -202,7 +281,7 @@ int mod_verify_sig(const void *mod, unsi struct module_signature ms; struct key *key; const void *sig; - size_t modlen = *_modlen, sig_len; + size_t modlen = *_modlen, sig_len, wholelen; int ret;
pr_devel("==>%s(,%zu)\n", __func__, modlen); @@ -210,6 +289,7 @@ int mod_verify_sig(const void *mod, unsi if (modlen <= sizeof(ms)) return -EBADMSG;
+ wholelen = modlen + sizeof(MODULE_SIG_STRING) - 1; memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms)); modlen -= sizeof(ms);
@@ -252,6 +332,10 @@ int mod_verify_sig(const void *mod, unsi ret = verify_signature(key, pks); pr_devel("verify_signature() = %d\n", ret);
+ /* check hash of module not in blacklist */ + if (!ret) + ret = mod_verify_hash(mod, wholelen, pks); + error_free_pks: mpi_free(pks->rsa.s); kfree(pks);
This patch add the support to load blacklisted hash or public key from MOKx that's maintained by bootloader.
v2: + The hash type need the same with CONFIG_MODULE_SIG_HASH. + Handle the return value of func() + Move the logic of parsing EFI signature database to parse_efi_signature_db() for collecting certificates and hashes. + Still parsing hashes when signature parsing fail on MOKx.
Signed-off-by: Lee, Chun-Yi jlee@suse.com --- include/linux/efi.h | 12 ++++ kernel/modsign_uefi.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 2 deletions(-)
Index: linux-3.12-SLE12/include/linux/efi.h =================================================================== --- linux-3.12-SLE12.orig/include/linux/efi.h +++ linux-3.12-SLE12/include/linux/efi.h @@ -392,6 +392,18 @@ typedef efi_status_t efi_query_variable_ #define EFI_CERT_SHA256_GUID \ EFI_GUID( 0xc1c41626, 0x504c, 0x4092, 0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28 )
+#define EFI_CERT_SHA1_GUID \ + EFI_GUID( 0x826ca512, 0xcf10, 0x4ac9, 0xb1, 0x87, 0xbe, 0x1, 0x49, 0x66, 0x31, 0xbd ) + +#define EFI_CERT_SHA512_GUID \ + EFI_GUID( 0x93e0fae, 0xa6c4, 0x4f50, 0x9f, 0x1b, 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a ) + +#define EFI_CERT_SHA224_GUID \ + EFI_GUID( 0xb6e5233, 0xa65c, 0x44c9, 0x94, 0x7, 0xd9, 0xab, 0x83, 0xbf, 0xc8, 0xbd ) + +#define EFI_CERT_SHA384_GUID \ + EFI_GUID( 0xff3e5307, 0x9fd0, 0x48c9, 0x85, 0xf1, 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x1 ) + #define EFI_CERT_X509_GUID \ EFI_GUID( 0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72 )
@@ -632,6 +644,10 @@ extern struct efi_memory_map memmap; struct key; extern int __init parse_efi_signature_list(const void *data, size_t size, struct key *keyring); +extern int __init parse_efi_signature_db(const void *data, size_t size, + struct key *keyring, efi_guid_t efi_cert_guid, + int (*func)(efi_guid_t, const efi_signature_data_t *, + size_t esize, struct key *keyring));
/** * efi_range_is_wc - check the WC bit on an address range Index: linux-3.12-SLE12/kernel/modsign_uefi.c =================================================================== --- linux-3.12-SLE12.orig/kernel/modsign_uefi.c +++ linux-3.12-SLE12/kernel/modsign_uefi.c @@ -4,10 +4,27 @@ #include <linux/err.h> #include <linux/efi.h> #include <linux/slab.h> +#include <crypto/public_key.h> #include <keys/asymmetric-type.h> #include <keys/system_keyring.h> #include "module-internal.h"
+struct efi_hash_type { + u8 hash; /* Hash algorithm [enum pkey_hash_algo] */ + efi_guid_t efi_cert_guid; /* EFI_CERT_*_GUID */ + char *hash_name; /* nams string of hash */ + size_t size; /* size of hash */ +}; + +static const struct efi_hash_type efi_hash_types[] = { + {PKEY_HASH_SHA256, EFI_CERT_SHA256_GUID, "sha256", 32}, + {PKEY_HASH_SHA1, EFI_CERT_SHA1_GUID, "sha1", 20}, + {PKEY_HASH_SHA512, EFI_CERT_SHA512_GUID, "sha512", 64}, + {PKEY_HASH_SHA224, EFI_CERT_SHA224_GUID, "sha224", 28}, + {PKEY_HASH_SHA384, EFI_CERT_SHA384_GUID, "sha384", 48}, + {PKEY_HASH__LAST} +}; + static __init int check_ignore_db(void) { efi_status_t status; @@ -55,6 +72,68 @@ out: return db; }
+static int __init signature_blacklist_func(efi_guid_t efi_cert_guid, + const efi_signature_data_t *elem, size_t esize, + struct key *keyring) +{ + struct module_hash *module_hash = NULL; + int i; + + for (i = 0; efi_hash_types[i].hash < PKEY_HASH__LAST; i++) { + struct efi_hash_type type = efi_hash_types[i]; + + if (efi_guidcmp(efi_cert_guid, type.efi_cert_guid) != 0) + continue; + + if (strcmp(type.hash_name, CONFIG_MODULE_SIG_HASH)) { + pr_err("Hash type is %s not %s: %*phN\n", + type.hash_name, CONFIG_MODULE_SIG_HASH, + (int)type.size, elem->signature_data); + return -ENOTSUPP; + } + + module_hash = kzalloc(sizeof(*module_hash) + type.size, GFP_KERNEL); + if (!module_hash) { + pr_err("module hash allocate fail\n"); + return -ENOMEM; + } + module_hash->hash = type.hash; + module_hash->hash_name = type.hash_name; + module_hash->size = type.size; + memcpy(module_hash->hash_data, elem->signature_data, type.size); + } + + if (!module_hash) { + pr_err("Problem loading hash of blacklisted module: %pUb\n", + &efi_cert_guid); + return -ENOTSUPP; + } else { + pr_notice("Loaded %s hash %*phN to blacklisted modules\n", + module_hash->hash_name, (int) module_hash->size, + module_hash->hash_data); + } + + list_add(&module_hash->list, &module_hash_blacklist); + + return 0; +} + +static int __init parse_efi_signature_list_hashs(const void *data, size_t size) +{ + int i, rc = 0; + + for (i = 0; !rc && efi_hash_types[i].hash < PKEY_HASH__LAST; i++) { + struct efi_hash_type type = efi_hash_types[i]; + rc = parse_efi_signature_db(data, size, NULL, + type.efi_cert_guid, signature_blacklist_func); + if (rc) + pr_err("Couldn't parse signatures of %s hash: %d\n", + type.hash_name, rc); + } + + return rc; +} + /* * * Load the certs contained in the UEFI databases * */ @@ -62,8 +141,8 @@ static int __init load_uefi_certs(void) { efi_guid_t secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; efi_guid_t mok_var = EFI_SHIM_LOCK_GUID; - void *db = NULL, *dbx = NULL, *mok = NULL; - unsigned long dbsize = 0, dbxsize = 0, moksize = 0; + void *db = NULL, *dbx = NULL, *mok = NULL, *mokx = NULL; + unsigned long dbsize = 0, dbxsize = 0, moksize = 0, mokxsize = 0; int ignore_db, rc = 0;
/* Check if SB is enabled and just return if not */ @@ -109,6 +188,20 @@ static int __init load_uefi_certs(void) kfree(dbx); }
+ mokx = get_cert_list(L"MokListXRT", &mok_var, &mokxsize); + if (!mokx) { + pr_info("MODSIGN: Couldn't get UEFI MokListXRT\n"); + } else { + rc = parse_efi_signature_list(mokx, mokxsize, + system_blacklist_keyring); + if (rc) + pr_err("Couldn't parse MokListXRT signatures: %d\n", rc); + rc = parse_efi_signature_list_hashs(mokx, mokxsize); + if (rc) + pr_err("Couldn't parse MokListXRT hashes: %d\n", rc); + kfree(mokx); + } + return rc; } late_initcall(load_uefi_certs); Index: linux-3.12-SLE12/crypto/asymmetric_keys/efi_parser.c =================================================================== --- linux-3.12-SLE12.orig/crypto/asymmetric_keys/efi_parser.c +++ linux-3.12-SLE12/crypto/asymmetric_keys/efi_parser.c @@ -18,13 +18,38 @@
static __initdata efi_guid_t efi_cert_x509_guid = EFI_CERT_X509_GUID;
-/** - * parse_efi_signature_list - Parse an EFI signature list for certificates - * @data: The data blob to parse - * @size: The size of the data blob - * @keyring: The keyring to add extracted keys to - */ -int __init parse_efi_signature_list(const void *data, size_t size, struct key *keyring) +static int __init signature_certificates_func(efi_guid_t efi_cert_guid, + const efi_signature_data_t *elem, size_t esize, + struct key *keyring) +{ + key_ref_t key; + + key = key_create_or_update( + make_key_ref(keyring, 1), + "asymmetric", + NULL, + &elem->signature_data, + esize - sizeof(*elem), + (KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW, + KEY_ALLOC_NOT_IN_QUOTA | + KEY_ALLOC_TRUSTED); + + if (IS_ERR(key)) + pr_err("Problem loading in-kernel X.509 certificate (%ld)\n", + PTR_ERR(key)); + else + pr_notice("Loaded cert '%s' linked to '%s'\n", + key_ref_to_ptr(key)->description, + keyring->description); + + return 0; +} + +int parse_efi_signature_db(const void *data, size_t size, struct key *keyring, + efi_guid_t efi_cert_guid, + int (*func)(efi_guid_t, const efi_signature_data_t *, + size_t esize, struct key *keyring)) { unsigned offs = 0; size_t lsize, esize, hsize, elsize; @@ -34,7 +59,6 @@ int __init parse_efi_signature_list(cons while (size > 0) { efi_signature_list_t list; const efi_signature_data_t *elem; - key_ref_t key;
if (size < sizeof(list)) return -EBADMSG; @@ -64,7 +88,7 @@ int __init parse_efi_signature_list(cons return -EBADMSG; }
- if (efi_guidcmp(list.signature_type, efi_cert_x509_guid) != 0) { + if (efi_guidcmp(list.signature_type, efi_cert_guid) != 0) { data += lsize; size -= lsize; offs += lsize; @@ -76,28 +100,13 @@ int __init parse_efi_signature_list(cons offs += sizeof(list) + hsize;
for (; elsize > 0; elsize -= esize) { + int ret = 0; elem = data;
pr_devel("ELEM[%04x]\n", offs); - - key = key_create_or_update( - make_key_ref(keyring, 1), - "asymmetric", - NULL, - &elem->signature_data, - esize - sizeof(*elem), - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW, - KEY_ALLOC_NOT_IN_QUOTA | - KEY_ALLOC_TRUSTED); - - if (IS_ERR(key)) - pr_err("Problem loading in-kernel X.509 certificate (%ld)\n", - PTR_ERR(key)); - else - pr_notice("Loaded cert '%s' linked to '%s'\n", - key_ref_to_ptr(key)->description, - keyring->description); + ret = func(efi_cert_guid, elem, esize, keyring); + if (ret && ret != -ENOTSUPP) + return ret;
data += esize; size -= esize; @@ -107,3 +116,18 @@ int __init parse_efi_signature_list(cons
return 0; } + +/** + * parse_efi_signature_list - Parse an EFI signature list for certificates or hashs + * @data: The data blob to parse + * @size: The size of the data blob + * @keyring: The keyring to add extracted keys to + */ +int __init parse_efi_signature_list(const void *data, size_t size, + struct key *keyring) +{ + + parse_efi_signature_db(data, size, keyring, efi_cert_x509_guid, + signature_certificates_func); + return 0; +}
kernel@lists.fedoraproject.org