/** * Copyright (c) 2011 Trusted Logic S.A. * All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "tf_crypto.h" #include "tf_defs.h" #include "tf_util.h" /*** Test vectors ***/ struct digest_test_vector { unsigned char *text; unsigned length; unsigned char *digest; unsigned char *key; /*used for HMAC, NULL for plain digests*/ unsigned key_length; }; struct blkcipher_test_vector { unsigned char *key; unsigned key_length; unsigned char *iv; unsigned char *plaintext; unsigned char *ciphertext; unsigned length; }; /* From FIPS-180 */ struct digest_test_vector sha1_test_vector = { "abc", 3, "\xa9\x99\x3e\x36\x47\x06\x81\x6a\xba\x3e\x25\x71\x78\x50\xc2\x6c" "\x9c\xd0\xd8\x9d" }; struct digest_test_vector sha224_test_vector = { "abc", 3, "\x23\x09\x7D\x22\x34\x05\xD8\x22\x86\x42\xA4\x77\xBD\xA2\x55\xB3" "\x2A\xAD\xBC\xE4\xBD\xA0\xB3\xF7\xE3\x6C\x9D\xA7" }; struct digest_test_vector sha256_test_vector = { "abc", 3, "\xba\x78\x16\xbf\x8f\x01\xcf\xea\x41\x41\x40\xde\x5d\xae\x22\x23" "\xb0\x03\x61\xa3\x96\x17\x7a\x9c\xb4\x10\xff\x61\xf2\x00\x15\xad" }; /* From FIPS-198 */ struct digest_test_vector hmac_sha1_test_vector = { "Sample message for keylen sizeof(digest)) { ERROR("digest length too large (%zu > %zu)", digest_length, sizeof(digest)); error = -ENOMEM; goto abort; } if (tv->key != NULL) { error = crypto_hash_setkey(desc.tfm, tv->key, tv->key_length); if (error) { ERROR("crypto_hash_setkey(%s) failed: %d", alg_name, error); goto abort; } TF_TRACE_ARRAY(tv->key, tv->key_length); } error = crypto_hash_init(&desc); if (error) { ERROR("crypto_hash_init(%s) failed: %d", alg_name, error); goto abort; } /* The test vector data is in vmalloc'ed memory since it's a module global. Copy it to the stack, since the crypto API doesn't support vmalloc'ed memory. */ if (tv->length > sizeof(input)) { ERROR("data too large (%zu > %zu)", tv->length, sizeof(input)); error = -ENOMEM; goto abort; } memcpy(input, tv->text, tv->length); INFO("sg_init_one(%p, %p, %u)", &sg, input, tv->length); sg_init_one(&sg, input, tv->length); TF_TRACE_ARRAY(input, tv->length); error = crypto_hash_update(&desc, &sg, tv->length); if (error) { ERROR("crypto_hash_update(%s) failed: %d", alg_name, error); goto abort; } error = crypto_hash_final(&desc, digest); if (error) { ERROR("crypto_hash_final(%s) failed: %d", alg_name, error); goto abort; } crypto_free_hash(desc.tfm); desc.tfm = NULL; if (memcmp(digest, tv->digest, digest_length)) { TF_TRACE_ARRAY(digest, digest_length); ERROR("wrong %s digest value", alg_name); pr_err("[SMC Driver] error: SMC Driver POST FAILURE (%s)\n", alg_name); error = -EINVAL; } else { INFO("%s: digest successful", alg_name); error = 0; } return error; abort: if (!IS_ERR_OR_NULL(desc.tfm)) crypto_free_hash(desc.tfm); pr_err("[SMC Driver] error: SMC Driver POST FAILURE (%s)\n", alg_name); return error; } static int tf_self_test_perform_blkcipher( const char *alg_name, const struct blkcipher_test_vector *tv, bool decrypt) { struct blkcipher_desc desc = {0}; struct scatterlist sg_in, sg_out; unsigned char *in = NULL; unsigned char *out = NULL; unsigned in_size, out_size; int error; desc.tfm = crypto_alloc_blkcipher(alg_name, 0, 0); if (IS_ERR_OR_NULL(desc.tfm)) { ERROR("crypto_alloc_blkcipher(%s) failed", alg_name); error = (desc.tfm == NULL ? -ENOMEM : (int)desc.tfm); goto abort; } INFO("%s alg_name=%s driver_name=%s key_size=%u block_size=%u", decrypt ? "decrypt" : "encrypt", alg_name, crypto_tfm_alg_driver_name(crypto_blkcipher_tfm(desc.tfm)), tv->key_length * 8, crypto_blkcipher_blocksize(desc.tfm)); in_size = tv->length; in = kmalloc(in_size, GFP_KERNEL); if (IS_ERR_OR_NULL(in)) { ERROR("kmalloc(%u) failed: %d", in_size, (int)in); error = (in == NULL ? -ENOMEM : (int)in); goto abort; } memcpy(in, decrypt ? tv->ciphertext : tv->plaintext, tv->length); out_size = tv->length + crypto_blkcipher_blocksize(desc.tfm); out = kmalloc(out_size, GFP_KERNEL); if (IS_ERR_OR_NULL(out)) { ERROR("kmalloc(%u) failed: %d", out_size, (int)out); error = (out == NULL ? -ENOMEM : (int)out); goto abort; } error = crypto_blkcipher_setkey(desc.tfm, tv->key, tv->key_length); if (error) { ERROR("crypto_alloc_setkey(%s) failed", alg_name); goto abort; } TF_TRACE_ARRAY(tv->key, tv->key_length); if (tv->iv != NULL) { unsigned iv_length = crypto_blkcipher_ivsize(desc.tfm); crypto_blkcipher_set_iv(desc.tfm, tv->iv, iv_length); TF_TRACE_ARRAY(tv->iv, iv_length); } sg_init_one(&sg_in, in, tv->length); sg_init_one(&sg_out, out, tv->length); TF_TRACE_ARRAY(in, tv->length); (decrypt ? crypto_blkcipher_decrypt : crypto_blkcipher_encrypt) (&desc, &sg_out, &sg_in, tv->length); if (error) { ERROR("crypto_blkcipher_%s(%s) failed", decrypt ? "decrypt" : "encrypt", alg_name); goto abort; } TF_TRACE_ARRAY(out, tv->length); crypto_free_blkcipher(desc.tfm); if (memcmp((decrypt ? tv->plaintext : tv->ciphertext), out, tv->length)) { ERROR("Wrong %s/%u %s result", alg_name, tv->key_length * 8, decrypt ? "decryption" : "encryption"); error = -EINVAL; } else { INFO("%s/%u: %s successful", alg_name, tv->key_length * 8, decrypt ? "decryption" : "encryption"); error = 0; } kfree(in); kfree(out); return error; abort: if (!IS_ERR_OR_NULL(desc.tfm)) crypto_free_blkcipher(desc.tfm); if (!IS_ERR_OR_NULL(out)) kfree(out); if (!IS_ERR_OR_NULL(in)) kfree(in); return error; } static int tf_self_test_blkcipher(const char *alg_name, const struct blkcipher_test_vector *tv) { int encryption_outcome = tf_self_test_perform_blkcipher(alg_name, tv, false); int decryption_outcome = tf_self_test_perform_blkcipher(alg_name, tv, true); if (encryption_outcome == 0 && decryption_outcome == 0) { return 0; } else { pr_err("[SMC Driver] error: SMC Driver POST FAILURE (%s/%u)", alg_name, tv->key_length * 8); return -EINVAL; } } /*** Integrity check ***/ #if defined(CONFIG_MODULE_EXTRA_COPY) && defined(MODULE) static ssize_t scan_hex(unsigned char *const buf, size_t buf_size, const char *const hex) { size_t bi = 0; size_t hi; unsigned prev = -1, cur; for (hi = 0; hex[hi] != 0; hi++) { if (hex[hi] >= '0' && hex[hi] <= '9') cur = hex[hi] - '0'; else if (hex[hi] >= 'a' && hex[hi] <= 'f') cur = hex[hi] - 'a' + 10; else if (hex[hi] >= 'A' && hex[hi] <= 'f') cur = hex[hi] - 'F' + 10; else if (hex[hi] == '-' || hex[hi] == ' ') continue; else { ERROR("invalid character at %zu (%u)", hi, hex[hi]); return -EINVAL; } if (prev == -1) prev = cur; else { if (bi >= buf_size) { ERROR("buffer too large at %zu", hi); return -ENOSPC; } buf[bi++] = prev << 4 | cur; prev = -1; } } return bi; } /* Allocate a scatterlist for a vmalloc block. The scatterlist is allocated with kmalloc. Buffers of arbitrary alignment are supported. This function is derived from other vmalloc_to_sg functions in the kernel tree, but note that its second argument is a size in bytes, not in pages. */ static struct scatterlist *vmalloc_to_sg(unsigned char *const buf, size_t const bytes) { struct scatterlist *sg_array = NULL; struct page *pg; /* Allow non-page-aligned pointers, so the first and last page may both be partial. */ unsigned const page_count = bytes / PAGE_SIZE + 2; unsigned char *ptr; unsigned i; sg_array = kcalloc(page_count, sizeof(*sg_array), GFP_KERNEL); if (sg_array == NULL) goto abort; sg_init_table(sg_array, page_count); for (i = 0, ptr = (void *)((unsigned long)buf & PAGE_MASK); ptr < buf + bytes; i++, ptr += PAGE_SIZE) { pg = vmalloc_to_page(ptr); if (pg == NULL) goto abort; sg_set_page(&sg_array[i], pg, PAGE_SIZE, 0); } /* Rectify the first page which may be partial. The last page may also be partial but its offset is correct so it doesn't matter. */ sg_array[0].offset = offset_in_page(buf); sg_array[0].length = PAGE_SIZE - offset_in_page(buf); return sg_array; abort: if (sg_array != NULL) kfree(sg_array); return NULL; } static unsigned char tf_integrity_hmac_sha256_key[] = { 0x6c, 0x99, 0x2c, 0x8a, 0x26, 0x98, 0xd1, 0x09, 0x5c, 0x18, 0x20, 0x42, 0x51, 0xaf, 0xf7, 0xad, 0x6b, 0x42, 0xfb, 0x1d, 0x4b, 0x44, 0xfa, 0xcc, 0x37, 0x7b, 0x05, 0x6d, 0x57, 0x24, 0x5f, 0x46, }; static int tf_self_test_integrity(const char *alg_name, struct module *mod) { unsigned char expected[32]; unsigned char actual[32]; struct scatterlist *sg = NULL; struct hash_desc desc = {NULL, 0}; size_t digest_length; unsigned char *const key = tf_integrity_hmac_sha256_key; size_t const key_length = sizeof(tf_integrity_hmac_sha256_key); int error; if (mod->raw_binary_ptr == NULL) return -ENXIO; if (tf_integrity_hmac_sha256_expected_value == NULL) return -ENOENT; INFO("expected=%s", tf_integrity_hmac_sha256_expected_value); error = scan_hex(expected, sizeof(expected), tf_integrity_hmac_sha256_expected_value); if (error < 0) { pr_err("tf_driver: Badly formatted hmac_sha256 parameter " "(should be a hex string)\n"); return -EIO; }; desc.tfm = crypto_alloc_hash(alg_name, 0, 0); if (IS_ERR_OR_NULL(desc.tfm)) { ERROR("crypto_alloc_hash(%s) failed", alg_name); error = (desc.tfm == NULL ? -ENOMEM : (int)desc.tfm); goto abort; } digest_length = crypto_hash_digestsize(desc.tfm); INFO("alg_name=%s driver_name=%s digest_length=%u", alg_name, crypto_tfm_alg_driver_name(crypto_hash_tfm(desc.tfm)), digest_length); error = crypto_hash_setkey(desc.tfm, key, key_length); if (error) { ERROR("crypto_hash_setkey(%s) failed: %d", alg_name, error); goto abort; } sg = vmalloc_to_sg(mod->raw_binary_ptr, mod->raw_binary_size); if (IS_ERR_OR_NULL(sg)) { ERROR("vmalloc_to_sg(%lu) failed: %d", mod->raw_binary_size, (int)sg); error = (sg == NULL ? -ENOMEM : (int)sg); goto abort; } error = crypto_hash_digest(&desc, sg, mod->raw_binary_size, actual); if (error) { ERROR("crypto_hash_digest(%s) failed: %d", alg_name, error); goto abort; } kfree(sg); crypto_free_hash(desc.tfm); #ifdef CONFIG_TF_DRIVER_FAULT_INJECTION if (tf_fault_injection_mask & TF_CRYPTO_ALG_INTEGRITY) { pr_warning("TF: injecting fault in integrity check!\n"); actual[0] = 0xff; actual[1] ^= 0xff; } #endif TF_TRACE_ARRAY(expected, digest_length); TF_TRACE_ARRAY(actual, digest_length); if (memcmp(expected, actual, digest_length)) { ERROR("wrong %s digest value", alg_name); error = -EINVAL; } else { INFO("%s: digest successful", alg_name); error = 0; } return error; abort: if (!IS_ERR_OR_NULL(sg)) kfree(sg); if (!IS_ERR_OR_NULL(desc.tfm)) crypto_free_hash(desc.tfm); return error == -ENOMEM ? error : -EIO; } #endif /*defined(CONFIG_MODULE_EXTRA_COPY) && defined(MODULE)*/ /*** Sysfs entries ***/ struct tf_post_data { unsigned failures; struct kobject kobj; }; static const struct attribute tf_post_failures_attr = { .name = "failures", .mode = 0444, }; static ssize_t tf_post_kobject_show(struct kobject *kobj, struct attribute *attribute, char *buf) { struct tf_post_data *data = container_of(kobj, struct tf_post_data, kobj); return (data->failures == 0 ? snprintf(buf, PAGE_SIZE, "0\n") : snprintf(buf, PAGE_SIZE, "0x%08x\n", data->failures)); } static const struct sysfs_ops tf_post_sysfs_ops = { .show = tf_post_kobject_show, }; static struct kobj_type tf_post_data_ktype = { .sysfs_ops = &tf_post_sysfs_ops, }; static struct tf_post_data tf_post_data; /*** POST entry point ***/ unsigned tf_self_test_post_vectors(void) { unsigned failures = 0; dpr_info("[SMC Driver] Starting POST\n"); #ifdef CONFIG_TF_DRIVER_FAULT_INJECTION dpr_info("%s: fault=0x%08x\n", __func__, tf_fault_injection_mask); #endif if (tf_self_test_blkcipher("aes-ecb-smc", &aes_ecb_128_test_vector)) failures |= TF_CRYPTO_ALG_AES_ECB_128; if (tf_self_test_blkcipher("aes-ecb-smc", &aes_ecb_192_test_vector)) failures |= TF_CRYPTO_ALG_AES_ECB_192; if (tf_self_test_blkcipher("aes-ecb-smc", &aes_ecb_256_test_vector)) failures |= TF_CRYPTO_ALG_AES_ECB_256; if (tf_self_test_blkcipher("aes-cbc-smc", &aes_cbc_128_test_vector)) failures |= TF_CRYPTO_ALG_AES_CBC_128; if (tf_self_test_blkcipher("aes-cbc-smc", &aes_cbc_192_test_vector)) failures |= TF_CRYPTO_ALG_AES_CBC_192; if (tf_self_test_blkcipher("aes-cbc-smc", &aes_cbc_256_test_vector)) failures |= TF_CRYPTO_ALG_AES_CBC_256; if (tf_self_test_digest("sha1-smc", &sha1_test_vector)) failures |= TF_CRYPTO_ALG_SHA1; if (tf_self_test_digest("sha224-smc", &sha224_test_vector)) failures |= TF_CRYPTO_ALG_SHA224; if (tf_self_test_digest("sha256-smc", &sha256_test_vector)) failures |= TF_CRYPTO_ALG_SHA256; if (tf_self_test_digest("tf_hmac(sha1-smc)", &hmac_sha1_test_vector)) failures |= TF_CRYPTO_ALG_HMAC_SHA1; if (tf_self_test_digest("tf_hmac(sha224-smc)", &hmac_sha224_test_vector)) failures |= TF_CRYPTO_ALG_HMAC_SHA224; if (tf_self_test_digest("tf_hmac(sha256-smc)", &hmac_sha256_test_vector)) failures |= TF_CRYPTO_ALG_HMAC_SHA256; #if defined(CONFIG_MODULE_EXTRA_COPY) && defined(MODULE) switch (tf_self_test_integrity("tf_hmac(sha256-smc)", &__this_module)) { case 0: pr_notice("[SMC Driver] integrity check passed\n"); break; case -ENXIO: pr_warning("[SMC Driver] integrity check can only be run " "when the module is loaded"); if (tf_post_data.failures & TF_CRYPTO_ALG_INTEGRITY) { pr_notice("[SMC Driver] " "integrity check initially failed\n"); failures |= TF_CRYPTO_ALG_INTEGRITY; } else pr_notice("[SMC Driver] " "integrity check initially passed\n"); break; case -ENOENT: pr_warning("[SCM Driver] " "integrity check cannot be made\n"); pr_notice("[SCM Driver] " "you must pass the hmac_sha256 parameter\n"); /* FALLTHROUGH */ default: pr_err("[SCM Driver] error: " "SMC Driver POST FAILURE " "(Integrity check HMAC-SHA-256)\n"); failures |= TF_CRYPTO_ALG_INTEGRITY; break; } #endif if (failures) { pr_notice("[SMC Driver] failures in POST (0x%08x)\n", failures); } else { pr_notice("[SMC Driver] init successful\n"); } tf_post_data.failures = failures; return failures; } void tf_self_test_post_exit(void) { tf_post_data.failures = 0; kobject_put(&tf_post_data.kobj); } int __init tf_self_test_post_init(struct kobject *parent) { int error = 0; if (parent != NULL) { error = kobject_init_and_add(&tf_post_data.kobj, &tf_post_data_ktype, parent, "post"); if (error) goto abort; error = sysfs_create_file(&tf_post_data.kobj, &tf_post_failures_attr); if (error) goto abort; } return tf_self_test_post_vectors(); abort: tf_self_test_post_exit(); if (error == 0) error = -ENOMEM; return error; }