aboutsummaryrefslogtreecommitdiffstats
path: root/security/smc/tf_self_test_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/smc/tf_self_test_device.c')
-rw-r--r--security/smc/tf_self_test_device.c508
1 files changed, 508 insertions, 0 deletions
diff --git a/security/smc/tf_self_test_device.c b/security/smc/tf_self_test_device.c
new file mode 100644
index 0000000..c9ed463
--- /dev/null
+++ b/security/smc/tf_self_test_device.c
@@ -0,0 +1,508 @@
+/**
+ * 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 <linux/cdev.h>
+#include <linux/crypto.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/scatterlist.h>
+
+#include "tf_crypto.h"
+#include "tf_defs.h"
+#include "tf_util.h"
+#include "tf_self_test_io.h"
+
+struct tf_self_test_hash_data {
+ struct hash_desc desc;
+ void *buf;
+ unsigned buffer_size;
+ unsigned digestsize;
+ void *key;
+};
+struct tf_self_test_blkcipher_data {
+ struct blkcipher_desc desc;
+ void *key;
+ bool decrypt; /*false => encrypt, true => decrypt*/
+};
+
+struct tf_self_test_file_data {
+ unsigned type;
+ struct scatterlist sg[2];
+ union {
+ struct tf_self_test_hash_data hash;
+ struct tf_self_test_blkcipher_data cipher;
+ };
+};
+
+static void tf_self_test_finalize(struct tf_self_test_file_data *data)
+{
+ switch (data->type & CRYPTO_ALG_TYPE_MASK) {
+ case CRYPTO_ALG_TYPE_HASH:
+ case CRYPTO_ALG_TYPE_SHASH:
+ case CRYPTO_ALG_TYPE_AHASH:
+ if (!IS_ERR_OR_NULL(data->hash.buf))
+ kfree(data->hash.buf);
+ if (!IS_ERR_OR_NULL(data->hash.desc.tfm))
+ crypto_free_hash(data->hash.desc.tfm);
+ if (!IS_ERR_OR_NULL(data->hash.key))
+ kfree(data->hash.key);
+ break;
+ case CRYPTO_ALG_TYPE_BLKCIPHER:
+ case CRYPTO_ALG_TYPE_ABLKCIPHER:
+ if (!IS_ERR_OR_NULL(data->cipher.desc.tfm))
+ crypto_free_blkcipher(data->cipher.desc.tfm);
+ if (!IS_ERR_OR_NULL(data->cipher.key))
+ kfree(data->cipher.key);
+ break;
+ }
+ memset(data, 0, sizeof(*data));
+}
+
+static int tf_self_test_device_open(struct inode *inode, struct file *filp)
+{
+ struct tf_self_test_file_data *data;
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+ data->type = 0;
+ filp->private_data = data;
+ return 0;
+}
+
+static int tf_self_test_device_release(struct inode *inode, struct file *filp)
+{
+ tf_self_test_finalize(filp->private_data);
+ kfree(filp->private_data);
+ return 0;
+}
+
+static long tf_self_test_ioctl_hash_init(struct file *filp,
+ void __user *user_params)
+{
+ struct tf_self_test_ioctl_param_hash_init params;
+ struct tf_self_test_file_data *data = filp->private_data;
+ void *ptr;
+ size_t size;
+ int ret;
+ char alg_name[CRYPTO_MAX_ALG_NAME] = "?";
+ INFO("invoked");
+
+ /* Reset the connection data. */
+ if (data->type != 0)
+ tf_self_test_finalize(data);
+
+ /* Copy parameters from user space */
+ if (copy_from_user(&params, user_params, sizeof(params)))
+ return -EFAULT;
+ ret = strncpy_from_user(alg_name, params.alg_name, sizeof(alg_name));
+ if (ret < 0)
+ return -EFAULT;
+ else if (ret >= sizeof(alg_name))
+ return -ENAMETOOLONG;
+ INFO("alg_name=%s", alg_name);
+
+ /* Prepare for hashing */
+ data->hash.desc.tfm = crypto_alloc_hash(alg_name, 0, 0);
+ if (IS_ERR_OR_NULL(data->hash.desc.tfm)) {
+ ret = (int)data->hash.desc.tfm;
+ goto abort;
+ }
+ data->type = crypto_tfm_alg_type(&data->hash.desc.tfm->base);
+ data->hash.digestsize = crypto_hash_digestsize(data->hash.desc.tfm);
+
+ /* Set the key if provided */
+ if (params.key != NULL) {
+ u8 key[128];
+ if (params.key_size > sizeof(key)) {
+ ret = -EINVAL;
+ goto abort;
+ }
+ if (copy_from_user(key, params.key, params.key_size)) {
+ ret = -EFAULT;
+ goto abort;
+ }
+ TF_TRACE_ARRAY(key, params.key_size);
+ if (crypto_hash_setkey(data->hash.desc.tfm,
+ key, params.key_size)) {
+ ret = -EIO;
+ goto abort;
+ }
+ INFO("crypto_hash_setkey ok");
+ }
+ if (crypto_hash_init(&data->hash.desc)) {
+ ret = -EIO;
+ goto abort;
+ }
+ INFO("crypto_hash_init(%s) ok", alg_name);
+
+ /* Allocate a staging buffer for the data or the result */
+ size = PAGE_SIZE;
+ BUG_ON(size < data->hash.digestsize);
+ ptr = kzalloc(size, GFP_KERNEL);
+ if (IS_ERR_OR_NULL(ptr)) {
+ ret = -ENOMEM;
+ goto abort;
+ }
+ data->hash.buf = ptr;
+ data->hash.buffer_size = size;
+ INFO("allocated a buffer of %zu bytes", size);
+
+ /* Success */
+ return 0;
+
+abort:
+ ERROR("alg_name=%s", alg_name);
+ tf_self_test_finalize(data);
+ return ret;
+}
+
+static long tf_self_test_ioctl_blkcipher_init(struct file *filp,
+ void __user *user_params)
+{
+ struct tf_self_test_ioctl_param_blkcipher_init params;
+ struct tf_self_test_file_data *data = filp->private_data;
+ int ret;
+ char alg_name[CRYPTO_MAX_ALG_NAME] = "?";
+
+ /* Reset the connection data. */
+ if (data->type != 0)
+ tf_self_test_finalize(data);
+
+ /* Copy parameters from user space */
+ if (copy_from_user(&params, user_params, sizeof(params)))
+ return -EFAULT;
+ ret = strncpy_from_user(alg_name, params.alg_name, sizeof(alg_name));
+ if (ret < 0)
+ return -EFAULT;
+ else if (ret >= sizeof(alg_name))
+ return -ENAMETOOLONG;
+ data->cipher.decrypt = params.decrypt;
+
+ /* Prepare for encryption/decryption */
+ data->cipher.desc.tfm = crypto_alloc_blkcipher(alg_name, 0, 0);
+ if (IS_ERR_OR_NULL(data->cipher.desc.tfm)) {
+ ret = (int)data->cipher.desc.tfm;
+ goto abort;
+ }
+ data->type = crypto_tfm_alg_type(&data->cipher.desc.tfm->base);
+ INFO("crypto_alloc_blkcipher(%s) ok", alg_name);
+
+ /* Set the key if provided */
+ if (params.key != NULL) {
+ u8 key[128];
+ if (params.key_size > sizeof(key)) {
+ ret = -EINVAL;
+ goto abort;
+ }
+ if (copy_from_user(key, params.key, params.key_size)) {
+ ret = -EFAULT;
+ goto abort;
+ }
+ TF_TRACE_ARRAY(key, params.key_size);
+ if (crypto_blkcipher_setkey(data->cipher.desc.tfm,
+ key, params.key_size)) {
+ ret = -EIO;
+ goto abort;
+ }
+ INFO("crypto_blkcipher_setkey ok");
+ } else {
+ /*A key is required for ciphers*/
+ ret = -EINVAL;
+ goto abort;
+ }
+
+ /* Set the IV if provided */
+ if (params.iv != NULL) {
+ unsigned char *iv =
+ crypto_blkcipher_crt(data->cipher.desc.tfm)->iv;
+ unsigned size = crypto_blkcipher_ivsize(data->cipher.desc.tfm);
+ if (size != params.iv_size)
+ WARNING("size=%u iv_size=%u", size, params.iv_size);
+ if (size > params.iv_size)
+ size = params.iv_size;
+ if (copy_from_user(iv, params.iv, size)) {
+ ret = -EFAULT;
+ goto abort;
+ }
+ TF_TRACE_ARRAY(iv, params.iv_size);
+ }
+
+ /* Success */
+ return 0;
+
+abort:
+ ERROR("alg_name=%s", alg_name);
+ tf_self_test_finalize(data);
+ return ret;
+}
+
+static long tf_self_test_ioctl_blkcipher_update(struct file *filp,
+ void __user *user_params)
+{
+ struct tf_self_test_ioctl_param_blkcipher_update params;
+ struct tf_self_test_file_data *data = filp->private_data;
+ struct scatterlist sg_in, sg_out;
+ unsigned char in[256] = {0};
+ unsigned char out[sizeof(in)] = "Uninitialized!";
+ struct blkcipher_tfm *crt;
+ int (*crypt)(struct blkcipher_desc *, struct scatterlist *,
+ struct scatterlist *, unsigned int);
+
+
+ switch (data->type & CRYPTO_ALG_TYPE_MASK) {
+ case CRYPTO_ALG_TYPE_BLKCIPHER:
+ case CRYPTO_ALG_TYPE_ABLKCIPHER:
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+
+ /* Copy parameters from user space */
+ if (copy_from_user(&params, user_params, sizeof(params)))
+ return -EFAULT;
+ if (params.in == NULL || params.out == NULL)
+ return -EINVAL;
+ if (params.size > sizeof(in) || params.size > sizeof(out))
+ return -ENOSPC;
+ if (copy_from_user(in, params.in, params.size))
+ return -EFAULT;
+
+ /* Perform the encryption or decryption */
+ sg_init_one(&sg_in, in, params.size);
+ sg_init_one(&sg_out, out, params.size);
+ crt = crypto_blkcipher_crt(data->cipher.desc.tfm);
+ data->cipher.desc.info = crt->iv;
+ crypt = (data->cipher.decrypt ? crt->decrypt : crt->encrypt);
+ if (crypt(&data->cipher.desc, &sg_out, &sg_in, params.size))
+ return -EIO;
+
+ /* Copy the result */
+ if (copy_to_user(params.out, out, params.size))
+ return -EFAULT;
+
+ /* Success */
+ return 0;
+}
+
+static long tf_self_test_device_ioctl(
+ struct file *filp, unsigned int ioctl_num,
+ unsigned long ioctl_param)
+{
+ switch (ioctl_num) {
+ case IOCTL_TF_SELF_TEST_POST:
+ return tf_self_test_post_vectors();
+ case IOCTL_TF_SELF_TEST_HASH_INIT:
+ return tf_self_test_ioctl_hash_init(filp, (void *)ioctl_param);
+ break;
+ case IOCTL_TF_SELF_TEST_BLKCIPHER_INIT:
+ return tf_self_test_ioctl_blkcipher_init(filp,
+ (void *)ioctl_param);
+ break;
+ case IOCTL_TF_SELF_TEST_BLKCIPHER_UPDATE:
+ return tf_self_test_ioctl_blkcipher_update(filp,
+ (void *)ioctl_param);
+ break;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static ssize_t tf_self_test_device_read(struct file *filp,
+ char __user *buf, size_t count,
+ loff_t *pos)
+{
+ struct tf_self_test_file_data *data = filp->private_data;
+ INFO("type=%03x tfm=%d pos=%lu",
+ data->type, data->hash.desc.tfm != NULL, (unsigned long)*pos);
+ switch (data->type & CRYPTO_ALG_TYPE_MASK) {
+ case CRYPTO_ALG_TYPE_HASH:
+ case CRYPTO_ALG_TYPE_SHASH:
+ case CRYPTO_ALG_TYPE_AHASH:
+ if (data->hash.desc.tfm != NULL) {
+ /*Stop accepting input and calculate the hash*/
+ if (crypto_hash_final(&data->hash.desc,
+ data->hash.buf)) {
+ ERROR("crypto_hash_final failed");
+ return -EIO;
+ }
+ crypto_free_hash(data->hash.desc.tfm);
+ data->hash.desc.tfm = NULL;
+ {
+ unsigned char *buf = data->hash.buf;
+ TF_TRACE_ARRAY(buf, data->hash.digestsize);
+ }
+ /*Reset the file position for the read part*/
+ *pos = 0;
+ }
+ if (*pos < 0 || *pos >= data->hash.digestsize)
+ return 0;
+ if (*pos + count > data->hash.digestsize)
+ count = data->hash.digestsize - *pos;
+ if (copy_to_user(buf, data->hash.buf, count))
+ return -EFAULT;
+ *pos += count;
+ break;
+ default:
+ return -ENXIO;
+ break;
+ }
+ return count;
+}
+
+static ssize_t tf_self_test_device_write(struct file *filp,
+ const char __user *buf, size_t count,
+ loff_t *pos)
+{
+ struct tf_self_test_file_data *data = filp->private_data;
+ INFO("type=%03x tfm=%d pos=%lu",
+ data->type, data->hash.desc.tfm != NULL, (unsigned long)*pos);
+ switch (data->type & CRYPTO_ALG_TYPE_MASK) {
+ case CRYPTO_ALG_TYPE_HASH:
+ case CRYPTO_ALG_TYPE_SHASH:
+ case CRYPTO_ALG_TYPE_AHASH:
+ if (IS_ERR_OR_NULL(data->hash.desc.tfm)) {
+ /*We are no longer accepting input*/
+ return -EROFS;
+ }
+ if (count > data->hash.buffer_size)
+ count = data->hash.buffer_size;
+ if (copy_from_user(data->hash.buf, buf, count))
+ return -EFAULT;
+ TF_TRACE_ARRAY(buf, count);
+ {
+ unsigned char *ptr = data->hash.buf;
+ TF_TRACE_ARRAY(ptr, count);
+ }
+ sg_init_one(&data->sg[0], data->hash.buf, count);
+ if (crypto_hash_update(&data->hash.desc,
+ &data->sg[0], count)) {
+ ERROR("crypto_hash_update failed");
+ tf_self_test_finalize(data);
+ return -EIO;
+ }
+ *pos += count;
+ break;
+ default:
+ return -ENXIO;
+ break;
+ }
+ return count;
+}
+
+
+
+static const struct file_operations tf_self_test_device_file_ops = {
+ .owner = THIS_MODULE,
+ .open = tf_self_test_device_open,
+ .release = tf_self_test_device_release,
+ .unlocked_ioctl = tf_self_test_device_ioctl,
+ .read = tf_self_test_device_read,
+ .write = tf_self_test_device_write,
+ .llseek = no_llseek,
+};
+
+struct tf_self_test_device {
+ dev_t devno;
+ struct cdev cdev;
+ struct class *class;
+ struct device *device;
+};
+static struct tf_self_test_device tf_self_test_device;
+
+int __init tf_self_test_register_device(void)
+{
+ struct tf_device *tf_device = tf_get_device();
+ int error;
+
+ tf_self_test_device.devno =
+ MKDEV(MAJOR(tf_device->dev_number),
+ TF_SELF_TEST_DEVICE_MINOR_NUMBER);
+ error = register_chrdev_region(tf_self_test_device.devno, 1,
+ TF_SELF_TEST_DEVICE_BASE_NAME);
+ if (error != 0) {
+ ERROR("register_chrdev_region failed (error %d)", error);
+ goto register_chrdev_region_failed;
+ }
+ cdev_init(&tf_self_test_device.cdev, &tf_self_test_device_file_ops);
+ tf_self_test_device.cdev.owner = THIS_MODULE;
+ error = cdev_add(&tf_self_test_device.cdev,
+ tf_self_test_device.devno, 1);
+ if (error != 0) {
+ ERROR("cdev_add failed (error %d)", error);
+ goto cdev_add_failed;
+ }
+ tf_self_test_device.class =
+ class_create(THIS_MODULE, TF_SELF_TEST_DEVICE_BASE_NAME);
+ if (IS_ERR_OR_NULL(tf_self_test_device.class)) {
+ ERROR("class_create failed (%d)",
+ (int)tf_self_test_device.class);
+ goto class_create_failed;
+ }
+ tf_self_test_device.device =
+ device_create(tf_self_test_device.class, NULL,
+ tf_self_test_device.devno,
+ NULL, TF_SELF_TEST_DEVICE_BASE_NAME);
+ INFO("created device %s = %u:%u",
+ TF_SELF_TEST_DEVICE_BASE_NAME,
+ MAJOR(tf_self_test_device.devno),
+ MINOR(tf_self_test_device.devno));
+ if (IS_ERR_OR_NULL(tf_self_test_device.device)) {
+ ERROR("device_create failed (%d)",
+ (int)tf_self_test_device.device);
+ goto device_create_failed;
+ }
+
+ return 0;
+
+ /*__builtin_unreachable();*/
+device_create_failed:
+ if (!IS_ERR_OR_NULL(tf_self_test_device.class)) {
+ device_destroy(tf_self_test_device.class,
+ tf_self_test_device.devno);
+ class_destroy(tf_self_test_device.class);
+ }
+class_create_failed:
+ tf_self_test_device.class = NULL;
+ cdev_del(&tf_self_test_device.cdev);
+cdev_add_failed:
+ cdev_init(&tf_self_test_device.cdev, NULL);
+ unregister_chrdev_region(tf_self_test_device.devno, 1);
+register_chrdev_region_failed:
+ return error;
+}
+
+void __exit tf_self_test_unregister_device(void)
+{
+ if (!IS_ERR_OR_NULL(tf_self_test_device.class)) {
+ device_destroy(tf_self_test_device.class,
+ tf_self_test_device.devno);
+ class_destroy(tf_self_test_device.class);
+ }
+ tf_self_test_device.class = NULL;
+ if (tf_self_test_device.cdev.owner == THIS_MODULE)
+ cdev_del(&tf_self_test_device.cdev);
+ unregister_chrdev_region(tf_self_test_device.devno, 1);
+}