diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-03 08:50:52 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-03 08:50:52 -0700 |
commit | 0302e28dee643932ee7b3c112ebccdbb9f8ec32c (patch) | |
tree | 405d4cb3f772ef069ed7f291adc4b74a4e73346e /drivers/char | |
parent | 89c9fea3c8034cdb2fd745f551cde0b507fd6893 (diff) | |
parent | 8979b02aaf1d6de8d52cc143aa4da961ed32e5a2 (diff) |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security
Pull security subsystem updates from James Morris:
"Highlights:
IMA:
- provide ">" and "<" operators for fowner/uid/euid rules
KEYS:
- add a system blacklist keyring
- add KEYCTL_RESTRICT_KEYRING, exposes keyring link restriction
functionality to userland via keyctl()
LSM:
- harden LSM API with __ro_after_init
- add prlmit security hook, implement for SELinux
- revive security_task_alloc hook
TPM:
- implement contextual TPM command 'spaces'"
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: (98 commits)
tpm: Fix reference count to main device
tpm_tis: convert to using locality callbacks
tpm: fix handling of the TPM 2.0 event logs
tpm_crb: remove a cruft constant
keys: select CONFIG_CRYPTO when selecting DH / KDF
apparmor: Make path_max parameter readonly
apparmor: fix parameters so that the permission test is bypassed at boot
apparmor: fix invalid reference to index variable of iterator line 836
apparmor: use SHASH_DESC_ON_STACK
security/apparmor/lsm.c: set debug messages
apparmor: fix boolreturn.cocci warnings
Smack: Use GFP_KERNEL for smk_netlbl_mls().
smack: fix double free in smack_parse_opts_str()
KEYS: add SP800-56A KDF support for DH
KEYS: Keyring asymmetric key restrict method with chaining
KEYS: Restrict asymmetric key linkage using a specific keychain
KEYS: Add a lookup_restriction function for the asymmetric key type
KEYS: Add KEYCTL_RESTRICT_KEYRING
KEYS: Consistent ordering for __key_link_begin and restrict check
KEYS: Add an optional lookup_restriction hook to key_type
...
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/tpm/Kconfig | 3 | ||||
-rw-r--r-- | drivers/char/tpm/Makefile | 3 | ||||
-rw-r--r-- | drivers/char/tpm/st33zp24/i2c.c | 23 | ||||
-rw-r--r-- | drivers/char/tpm/st33zp24/spi.c | 23 | ||||
-rw-r--r-- | drivers/char/tpm/st33zp24/st33zp24.c | 12 | ||||
-rw-r--r-- | drivers/char/tpm/tpm-chip.c | 71 | ||||
-rw-r--r-- | drivers/char/tpm/tpm-dev-common.c | 148 | ||||
-rw-r--r-- | drivers/char/tpm/tpm-dev.c | 143 | ||||
-rw-r--r-- | drivers/char/tpm/tpm-dev.h | 27 | ||||
-rw-r--r-- | drivers/char/tpm/tpm-interface.c | 152 | ||||
-rw-r--r-- | drivers/char/tpm/tpm-sysfs.c | 2 | ||||
-rw-r--r-- | drivers/char/tpm/tpm.h | 52 | ||||
-rw-r--r-- | drivers/char/tpm/tpm2-cmd.c | 173 | ||||
-rw-r--r-- | drivers/char/tpm/tpm2-space.c | 528 | ||||
-rw-r--r-- | drivers/char/tpm/tpm2_eventlog.c | 14 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_crb.c | 279 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_i2c_infineon.c | 12 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_i2c_nuvoton.c | 24 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_ibmvtpm.c | 8 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_tis_core.c | 60 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_tis_spi.c | 160 | ||||
-rw-r--r-- | drivers/char/tpm/tpmrm-dev.c | 65 |
22 files changed, 1549 insertions, 433 deletions
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig index af985cca413c..a30352202f1f 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -6,6 +6,7 @@ menuconfig TCG_TPM tristate "TPM Hardware Support" depends on HAS_IOMEM select SECURITYFS + select CRYPTO select CRYPTO_HASH_INFO ---help--- If you have a TPM security chip in your system, which @@ -135,7 +136,7 @@ config TCG_XEN config TCG_CRB tristate "TPM 2.0 CRB Interface" - depends on X86 && ACPI + depends on ACPI ---help--- If you have a TPM security chip that is compliant with the TCG CRB 2.0 TPM specification say Yes and it will be accessible diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile index 3d386a8c579f..23681f01f95a 100644 --- a/drivers/char/tpm/Makefile +++ b/drivers/char/tpm/Makefile @@ -3,7 +3,8 @@ # obj-$(CONFIG_TCG_TPM) += tpm.o tpm-y := tpm-interface.o tpm-dev.o tpm-sysfs.o tpm-chip.o tpm2-cmd.o \ - tpm1_eventlog.o tpm2_eventlog.o + tpm-dev-common.o tpmrm-dev.o tpm1_eventlog.o tpm2_eventlog.o \ + tpm2-space.o tpm-$(CONFIG_ACPI) += tpm_ppi.o tpm_acpi.o tpm-$(CONFIG_OF) += tpm_of.o obj-$(CONFIG_TCG_TIS_CORE) += tpm_tis_core.o diff --git a/drivers/char/tpm/st33zp24/i2c.c b/drivers/char/tpm/st33zp24/i2c.c index 028a9cd76b63..1b10e38f214e 100644 --- a/drivers/char/tpm/st33zp24/i2c.c +++ b/drivers/char/tpm/st33zp24/i2c.c @@ -111,6 +111,13 @@ static const struct st33zp24_phy_ops i2c_phy_ops = { .recv = st33zp24_i2c_recv, }; +static const struct acpi_gpio_params lpcpd_gpios = { 1, 0, false }; + +static const struct acpi_gpio_mapping acpi_st33zp24_gpios[] = { + { "lpcpd-gpios", &lpcpd_gpios, 1 }, + {}, +}; + static int st33zp24_i2c_acpi_request_resources(struct i2c_client *client) { struct tpm_chip *chip = i2c_get_clientdata(client); @@ -118,10 +125,14 @@ static int st33zp24_i2c_acpi_request_resources(struct i2c_client *client) struct st33zp24_i2c_phy *phy = tpm_dev->phy_id; struct gpio_desc *gpiod_lpcpd; struct device *dev = &client->dev; + int ret; + + ret = acpi_dev_add_driver_gpios(ACPI_COMPANION(dev), acpi_st33zp24_gpios); + if (ret) + return ret; /* Get LPCPD GPIO from ACPI */ - gpiod_lpcpd = devm_gpiod_get_index(dev, "TPM IO LPCPD", 1, - GPIOD_OUT_HIGH); + gpiod_lpcpd = devm_gpiod_get(dev, "lpcpd", GPIOD_OUT_HIGH); if (IS_ERR(gpiod_lpcpd)) { dev_err(&client->dev, "Failed to retrieve lpcpd-gpios from acpi.\n"); @@ -268,8 +279,14 @@ static int st33zp24_i2c_probe(struct i2c_client *client, static int st33zp24_i2c_remove(struct i2c_client *client) { struct tpm_chip *chip = i2c_get_clientdata(client); + int ret; - return st33zp24_remove(chip); + ret = st33zp24_remove(chip); + if (ret) + return ret; + + acpi_dev_remove_driver_gpios(ACPI_COMPANION(&client->dev)); + return 0; } static const struct i2c_device_id st33zp24_i2c_id[] = { diff --git a/drivers/char/tpm/st33zp24/spi.c b/drivers/char/tpm/st33zp24/spi.c index 9f5a0117098c..c69d15198f84 100644 --- a/drivers/char/tpm/st33zp24/spi.c +++ b/drivers/char/tpm/st33zp24/spi.c @@ -230,6 +230,13 @@ static const struct st33zp24_phy_ops spi_phy_ops = { .recv = st33zp24_spi_recv, }; +static const struct acpi_gpio_params lpcpd_gpios = { 1, 0, false }; + +static const struct acpi_gpio_mapping acpi_st33zp24_gpios[] = { + { "lpcpd-gpios", &lpcpd_gpios, 1 }, + {}, +}; + static int st33zp24_spi_acpi_request_resources(struct spi_device *spi_dev) { struct tpm_chip *chip = spi_get_drvdata(spi_dev); @@ -237,10 +244,14 @@ static int st33zp24_spi_acpi_request_resources(struct spi_device *spi_dev) struct st33zp24_spi_phy *phy = tpm_dev->phy_id; struct gpio_desc *gpiod_lpcpd; struct device *dev = &spi_dev->dev; + int ret; + + ret = acpi_dev_add_driver_gpios(ACPI_COMPANION(dev), acpi_st33zp24_gpios); + if (ret) + return ret; /* Get LPCPD GPIO from ACPI */ - gpiod_lpcpd = devm_gpiod_get_index(dev, "TPM IO LPCPD", 1, - GPIOD_OUT_HIGH); + gpiod_lpcpd = devm_gpiod_get(dev, "lpcpd", GPIOD_OUT_HIGH); if (IS_ERR(gpiod_lpcpd)) { dev_err(dev, "Failed to retrieve lpcpd-gpios from acpi.\n"); phy->io_lpcpd = -1; @@ -385,8 +396,14 @@ static int st33zp24_spi_probe(struct spi_device *dev) static int st33zp24_spi_remove(struct spi_device *dev) { struct tpm_chip *chip = spi_get_drvdata(dev); + int ret; - return st33zp24_remove(chip); + ret = st33zp24_remove(chip); + if (ret) + return ret; + + acpi_dev_remove_driver_gpios(ACPI_COMPANION(&dev->dev)); + return 0; } static const struct spi_device_id st33zp24_spi_id[] = { diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index e8e0f7c02686..4d1dc8b46877 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -117,9 +117,9 @@ static u8 st33zp24_status(struct tpm_chip *chip) /* * check_locality if the locality is active * @param: chip, the tpm chip description - * @return: the active locality or -EACCESS. + * @return: true if LOCALITY0 is active, otherwise false */ -static int check_locality(struct tpm_chip *chip) +static bool check_locality(struct tpm_chip *chip) { struct st33zp24_dev *tpm_dev = dev_get_drvdata(&chip->dev); u8 data; @@ -129,9 +129,9 @@ static int check_locality(struct tpm_chip *chip) if (status && (data & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) - return tpm_dev->locality; + return true; - return -EACCES; + return false; } /* check_locality() */ /* @@ -146,7 +146,7 @@ static int request_locality(struct tpm_chip *chip) long ret; u8 data; - if (check_locality(chip) == tpm_dev->locality) + if (check_locality(chip)) return tpm_dev->locality; data = TPM_ACCESS_REQUEST_USE; @@ -158,7 +158,7 @@ static int request_locality(struct tpm_chip *chip) /* Request locality is usually effective after the request */ do { - if (check_locality(chip) >= 0) + if (check_locality(chip)) return tpm_dev->locality; msleep(TPM_TIMEOUT); } while (time_before(jiffies, stop)); diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c index c406343848da..9dec9f551b83 100644 --- a/drivers/char/tpm/tpm-chip.c +++ b/drivers/char/tpm/tpm-chip.c @@ -33,6 +33,7 @@ DEFINE_IDR(dev_nums_idr); static DEFINE_MUTEX(idr_lock); struct class *tpm_class; +struct class *tpmrm_class; dev_t tpm_devt; /** @@ -128,9 +129,19 @@ static void tpm_dev_release(struct device *dev) mutex_unlock(&idr_lock); kfree(chip->log.bios_event_log); + kfree(chip->work_space.context_buf); + kfree(chip->work_space.session_buf); kfree(chip); } +static void tpm_devs_release(struct device *dev) +{ + struct tpm_chip *chip = container_of(dev, struct tpm_chip, devs); + + /* release the master device reference */ + put_device(&chip->dev); +} + /** * tpm_chip_alloc() - allocate a new struct tpm_chip instance * @pdev: device to which the chip is associated @@ -167,31 +178,65 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev, chip->dev_num = rc; device_initialize(&chip->dev); + device_initialize(&chip->devs); chip->dev.class = tpm_class; chip->dev.release = tpm_dev_release; chip->dev.parent = pdev; chip->dev.groups = chip->groups; + chip->devs.parent = pdev; + chip->devs.class = tpmrm_class; + chip->devs.release = tpm_devs_release; + /* get extra reference on main device to hold on + * behalf of devs. This holds the chip structure + * while cdevs is in use. The corresponding put + * is in the tpm_devs_release (TPM2 only) + */ + if (chip->flags & TPM_CHIP_FLAG_TPM2) + get_device(&chip->dev); + if (chip->dev_num == 0) chip->dev.devt = MKDEV(MISC_MAJOR, TPM_MINOR); else chip->dev.devt = MKDEV(MAJOR(tpm_devt), chip->dev_num); + chip->devs.devt = + MKDEV(MAJOR(tpm_devt), chip->dev_num + TPM_NUM_DEVICES); + rc = dev_set_name(&chip->dev, "tpm%d", chip->dev_num); if (rc) goto out; + rc = dev_set_name(&chip->devs, "tpmrm%d", chip->dev_num); + if (rc) + goto out; if (!pdev) chip->flags |= TPM_CHIP_FLAG_VIRTUAL; cdev_init(&chip->cdev, &tpm_fops); + cdev_init(&chip->cdevs, &tpmrm_fops); chip->cdev.owner = THIS_MODULE; + chip->cdevs.owner = THIS_MODULE; chip->cdev.kobj.parent = &chip->dev.kobj; + chip->cdevs.kobj.parent = &chip->devs.kobj; + + chip->work_space.context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!chip->work_space.context_buf) { + rc = -ENOMEM; + goto out; + } + chip->work_space.session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!chip->work_space.session_buf) { + rc = -ENOMEM; + goto out; + } + chip->locality = -1; return chip; out: + put_device(&chip->devs); put_device(&chip->dev); return ERR_PTR(rc); } @@ -236,7 +281,6 @@ static int tpm_add_char_device(struct tpm_chip *chip) "unable to cdev_add() %s, major %d, minor %d, err=%d\n", dev_name(&chip->dev), MAJOR(chip->dev.devt), MINOR(chip->dev.devt), rc); - return rc; } @@ -251,6 +295,27 @@ static int tpm_add_char_device(struct tpm_chip *chip) return rc; } + if (chip->flags & TPM_CHIP_FLAG_TPM2) + rc = cdev_add(&chip->cdevs, chip->devs.devt, 1); + if (rc) { + dev_err(&chip->dev, + "unable to cdev_add() %s, major %d, minor %d, err=%d\n", + dev_name(&chip->devs), MAJOR(chip->devs.devt), + MINOR(chip->devs.devt), rc); + return rc; + } + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + rc = device_add(&chip->devs); + if (rc) { + dev_err(&chip->dev, + "unable to device_register() %s, major %d, minor %d, err=%d\n", + dev_name(&chip->devs), MAJOR(chip->devs.devt), + MINOR(chip->devs.devt), rc); + cdev_del(&chip->cdevs); + return rc; + } + /* Make the chip available. */ mutex_lock(&idr_lock); idr_replace(&dev_nums_idr, chip, chip->dev_num); @@ -384,6 +449,10 @@ void tpm_chip_unregister(struct tpm_chip *chip) { tpm_del_legacy_sysfs(chip); tpm_bios_log_teardown(chip); + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + cdev_del(&chip->cdevs); + device_del(&chip->devs); + } tpm_del_char_device(chip); } EXPORT_SYMBOL_GPL(tpm_chip_unregister); diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c new file mode 100644 index 000000000000..610638a80383 --- /dev/null +++ b/drivers/char/tpm/tpm-dev-common.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2004 IBM Corporation + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Copyright (C) 2013 Obsidian Research Corp + * Jason Gunthorpe <jgunthorpe@obsidianresearch.com> + * + * Device file system interface to the TPM + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + */ +#include <linux/slab.h> +#include <linux/uaccess.h> +#include "tpm.h" +#include "tpm-dev.h" + +static void user_reader_timeout(unsigned long ptr) +{ + struct file_priv *priv = (struct file_priv *)ptr; + + pr_warn("TPM user space timeout is deprecated (pid=%d)\n", + task_tgid_nr(current)); + + schedule_work(&priv->work); +} + +static void timeout_work(struct work_struct *work) +{ + struct file_priv *priv = container_of(work, struct file_priv, work); + + mutex_lock(&priv->buffer_mutex); + atomic_set(&priv->data_pending, 0); + memset(priv->data_buffer, 0, sizeof(priv->data_buffer)); + mutex_unlock(&priv->buffer_mutex); +} + +void tpm_common_open(struct file *file, struct tpm_chip *chip, + struct file_priv *priv) +{ + priv->chip = chip; + atomic_set(&priv->data_pending, 0); + mutex_init(&priv->buffer_mutex); + setup_timer(&priv->user_read_timer, user_reader_timeout, + (unsigned long)priv); + INIT_WORK(&priv->work, timeout_work); + + file->private_data = priv; +} + +ssize_t tpm_common_read(struct file *file, char __user *buf, + size_t size, loff_t *off) +{ + struct file_priv *priv = file->private_data; + ssize_t ret_size; + ssize_t orig_ret_size; + int rc; + + del_singleshot_timer_sync(&priv->user_read_timer); + flush_work(&priv->work); + ret_size = atomic_read(&priv->data_pending); + if (ret_size > 0) { /* relay data */ + orig_ret_size = ret_size; + if (size < ret_size) + ret_size = size; + + mutex_lock(&priv->buffer_mutex); + rc = copy_to_user(buf, priv->data_buffer, ret_size); + memset(priv->data_buffer, 0, orig_ret_size); + if (rc) + ret_size = -EFAULT; + + mutex_unlock(&priv->buffer_mutex); + } + + atomic_set(&priv->data_pending, 0); + + return ret_size; +} + +ssize_t tpm_common_write(struct file *file, const char __user *buf, + size_t size, loff_t *off, struct tpm_space *space) +{ + struct file_priv *priv = file->private_data; + size_t in_size = size; + ssize_t out_size; + + /* Cannot perform a write until the read has cleared either via + * tpm_read or a user_read_timer timeout. This also prevents split + * buffered writes from blocking here. + */ + if (atomic_read(&priv->data_pending) != 0) + return -EBUSY; + + if (in_size > TPM_BUFSIZE) + return -E2BIG; + + mutex_lock(&priv->buffer_mutex); + + if (copy_from_user + (priv->data_buffer, (void __user *) buf, in_size)) { + mutex_unlock(&priv->buffer_mutex); + return -EFAULT; + } + + /* atomic tpm command send and result receive. We only hold the ops + * lock during this period so that the tpm can be unregistered even if + * the char dev is held open. + */ + if (tpm_try_get_ops(priv->chip)) { + mutex_unlock(&priv->buffer_mutex); + return -EPIPE; + } + out_size = tpm_transmit(priv->chip, space, priv->data_buffer, + sizeof(priv->data_buffer), 0); + + tpm_put_ops(priv->chip); + if (out_size < 0) { + mutex_unlock(&priv->buffer_mutex); + return out_size; + } + + atomic_set(&priv->data_pending, out_size); + mutex_unlock(&priv->buffer_mutex); + + /* Set a timeout by which the reader must come claim the result */ + mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); + + return in_size; +} + +/* + * Called on file close + */ +void tpm_common_release(struct file *file, struct file_priv *priv) +{ + del_singleshot_timer_sync(&priv->user_read_timer); + flush_work(&priv->work); + file->private_data = NULL; + atomic_set(&priv->data_pending, 0); +} diff --git a/drivers/char/tpm/tpm-dev.c b/drivers/char/tpm/tpm-dev.c index 02a8850d3a69..ebd74ab5abef 100644 --- a/drivers/char/tpm/tpm-dev.c +++ b/drivers/char/tpm/tpm-dev.c @@ -18,48 +18,15 @@ * */ #include <linux/slab.h> -#include <linux/uaccess.h> -#include "tpm.h" - -struct file_priv { - struct tpm_chip *chip; - - /* Data passed to and from the tpm via the read/write calls */ - atomic_t data_pending; - struct mutex buffer_mutex; - - struct timer_list user_read_timer; /* user needs to claim result */ - struct work_struct work; - - u8 data_buffer[TPM_BUFSIZE]; -}; - -static void user_reader_timeout(unsigned long ptr) -{ - struct file_priv *priv = (struct file_priv *)ptr; - - pr_warn("TPM user space timeout is deprecated (pid=%d)\n", - task_tgid_nr(current)); - - schedule_work(&priv->work); -} - -static void timeout_work(struct work_struct *work) -{ - struct file_priv *priv = container_of(work, struct file_priv, work); - - mutex_lock(&priv->buffer_mutex); - atomic_set(&priv->data_pending, 0); - memset(priv->data_buffer, 0, sizeof(priv->data_buffer)); - mutex_unlock(&priv->buffer_mutex); -} +#include "tpm-dev.h" static int tpm_open(struct inode *inode, struct file *file) { - struct tpm_chip *chip = - container_of(inode->i_cdev, struct tpm_chip, cdev); + struct tpm_chip *chip; struct file_priv *priv; + chip = container_of(inode->i_cdev, struct tpm_chip, cdev); + /* It's assured that the chip will be opened just once, * by the check of is_open variable, which is protected * by driver_lock. */ @@ -69,100 +36,22 @@ static int tpm_open(struct inode *inode, struct file *file) } priv = kzalloc(sizeof(*priv), GFP_KERNEL); - if (priv == NULL) { - clear_bit(0, &chip->is_open); - return -ENOMEM; - } + if (priv == NULL) + goto out; - priv->chip = chip; - atomic_set(&priv->data_pending, 0); - mutex_init(&priv->buffer_mutex); - setup_timer(&priv->user_read_timer, user_reader_timeout, - (unsigned long)priv); - INIT_WORK(&priv->work, timeout_work); + tpm_common_open(file, chip, priv); - file->private_data = priv; return 0; -} - -static ssize_t tpm_read(struct file *file, char __user *buf, - size_t size, loff_t *off) -{ - struct file_priv *priv = file->private_data; - ssize_t ret_size; - int rc; - del_singleshot_timer_sync(&priv->user_read_timer); - flush_work(&priv->work); - ret_size = atomic_read(&priv->data_pending); - if (ret_size > 0) { /* relay data */ - ssize_t orig_ret_size = ret_size; - if (size < ret_size) - ret_size = size; - - mutex_lock(&priv->buffer_mutex); - rc = copy_to_user(buf, priv->data_buffer, ret_size); - memset(priv->data_buffer, 0, orig_ret_size); - if (rc) - ret_size = -EFAULT; - - mutex_unlock(&priv->buffer_mutex); - } - - atomic_set(&priv->data_pending, 0); - - return ret_size; + out: + clear_bit(0, &chip->is_open); + return -ENOMEM; } static ssize_t tpm_write(struct file *file, const char __user *buf, size_t size, loff_t *off) { - struct file_priv *priv = file->private_data; - size_t in_size = size; - ssize_t out_size; - - /* cannot perform a write until the read has cleared - either via tpm_read or a user_read_timer timeout. - This also prevents splitted buffered writes from blocking here. - */ - if (atomic_read(&priv->data_pending) != 0) - return -EBUSY; - - if (in_size > TPM_BUFSIZE) - return -E2BIG; - - mutex_lock(&priv->buffer_mutex); - - if (copy_from_user - (priv->data_buffer, (void __user *) buf, in_size)) { - mutex_unlock(&priv->buffer_mutex); - return -EFAULT; - } - - /* atomic tpm command send and result receive. We only hold the ops - * lock during this period so that the tpm can be unregistered even if - * the char dev is held open. - */ - if (tpm_try_get_ops(priv->chip)) { - mutex_unlock(&priv->buffer_mutex); - return -EPIPE; - } - out_size = tpm_transmit(priv->chip, priv->data_buffer, - sizeof(priv->data_buffer), 0); - - tpm_put_ops(priv->chip); - if (out_size < 0) { - mutex_unlock(&priv->buffer_mutex); - return out_size; - } - - atomic_set(&priv->data_pending, out_size); - mutex_unlock(&priv->buffer_mutex); - - /* Set a timeout by which the reader must come claim the result */ - mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); - - return in_size; + return tpm_common_write(file, buf, size, off, NULL); } /* @@ -172,12 +61,10 @@ static int tpm_release(struct inode *inode, struct file *file) { struct file_priv *priv = file->private_data; - del_singleshot_timer_sync(&priv->user_read_timer); - flush_work(&priv->work); - file->private_data = NULL; - atomic_set(&priv->data_pending, 0); + tpm_common_release(file, priv); clear_bit(0, &priv->chip->is_open); kfree(priv); + return 0; } @@ -185,9 +72,7 @@ const struct file_operations tpm_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .open = tpm_open, - .read = tpm_read, + .read = tpm_common_read, .write = tpm_write, .release = tpm_release, }; - - diff --git a/drivers/char/tpm/tpm-dev.h b/drivers/char/tpm/tpm-dev.h new file mode 100644 index 000000000000..ff15cf719bad --- /dev/null +++ b/drivers/char/tpm/tpm-dev.h @@ -0,0 +1,27 @@ +#ifndef _TPM_DEV_H +#define _TPM_DEV_H + +#include "tpm.h" + +struct file_priv { + struct tpm_chip *chip; + + /* Data passed to and from the tpm via the read/write calls */ + atomic_t data_pending; + struct mutex buffer_mutex; + + struct timer_list user_read_timer; /* user needs to claim result */ + struct work_struct work; + + u8 data_buffer[TPM_BUFSIZE]; +}; + +void tpm_common_open(struct file *file, struct tpm_chip *chip, + struct file_priv *priv); +ssize_t tpm_common_read(struct file *file, char __user *buf, + size_t size, loff_t *off); +ssize_t tpm_common_write(struct file *file, const char __user *buf, + size_t size, loff_t *off, struct tpm_space *space); +void tpm_common_release(struct file *file, struct file_priv *priv); + +#endif diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index bd2128e0b56c..158c1db83f05 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -328,6 +328,47 @@ unsigned long tpm_calc_ordinal_duration(struct tpm_chip *chip, } EXPORT_SYMBOL_GPL(tpm_calc_ordinal_duration); +static bool tpm_validate_command(struct tpm_chip *chip, + struct tpm_space *space, + const u8 *cmd, + size_t len) +{ + const struct tpm_input_header *header = (const void *)cmd; + int i; + u32 cc; + u32 attrs; + unsigned int nr_handles; + + if (len < TPM_HEADER_SIZE) + return false; + + if (!space) + return true; + + if (chip->flags & TPM_CHIP_FLAG_TPM2 && chip->nr_commands) { + cc = be32_to_cpu(header->ordinal); + + i = tpm2_find_cc(chip, cc); + if (i < 0) { + dev_dbg(&chip->dev, "0x%04X is an invalid command\n", + cc); + return false; + } + + attrs = chip->cc_attrs_tbl[i]; + nr_handles = + 4 * ((attrs >> TPM2_CC_ATTR_CHANDLES) & GENMASK(2, 0)); + if (len < TPM_HEADER_SIZE + 4 * nr_handles) + goto err_len; + } + + return true; +err_len: + dev_dbg(&chip->dev, + "%s: insufficient command length %zu", __func__, len); + return false; +} + /** * tmp_transmit - Internal kernel interface to transmit TPM commands. * @@ -340,14 +381,17 @@ EXPORT_SYMBOL_GPL(tpm_calc_ordinal_duration); * 0 when the operation is successful. * A negative number for system errors (errno). */ -ssize_t tpm_transmit(struct tpm_chip *chip, const u8 *buf, size_t bufsiz, - unsigned int flags) +ssize_t tpm_transmit(struct tpm_chip *chip, struct tpm_space *space, + u8 *buf, size_t bufsiz, unsigned int flags) { - ssize_t rc; + struct tpm_output_header *header = (void *)buf; + int rc; + ssize_t len = 0; u32 count, ordinal; unsigned long stop; + bool need_locality; - if (bufsiz < TPM_HEADER_SIZE) + if (!tpm_validate_command(chip, space, buf, bufsiz)) return -EINVAL; if (bufsiz > TPM_BUFSIZE) @@ -369,10 +413,24 @@ ssize_t tpm_transmit(struct tpm_chip *chip, const u8 *buf, size_t bufsiz, if (chip->dev.parent) pm_runtime_get_sync(chip->dev.parent); + /* Store the decision as chip->locality will be changed. */ + need_locality = chip->locality == -1; + + if (need_locality && chip->ops->request_locality) { + rc = chip->ops->request_locality(chip, 0); + if (rc < 0) + goto out_no_locality; + chip->locality = rc; + } + + rc = tpm2_prepare_space(chip, space, ordinal, buf); + if (rc) + goto out; + rc = chip->ops->send(chip, (u8 *) buf, count); if (rc < 0) { dev_err(&chip->dev, - "tpm_transmit: tpm_send: error %zd\n", rc); + "tpm_transmit: tpm_send: error %d\n", rc); goto out; } @@ -405,17 +463,36 @@ ssize_t tpm_transmit(struct tpm_chip *chip, const u8 *buf, size_t bufsiz, goto out; out_recv: - rc = chip->ops->recv(chip, (u8 *) buf, bufsiz); - if (rc < 0) + len = chip->ops->recv(chip, (u8 *) buf, bufsiz); + if (len < 0) { + rc = len; dev_err(&chip->dev, - "tpm_transmit: tpm_recv: error %zd\n", rc); + "tpm_transmit: tpm_recv: error %d\n", rc); + goto out; + } else if (len < TPM_HEADER_SIZE) { + rc = -EFAULT; + goto out; + } + + if (len != be32_to_cpu(header->length)) { + rc = -EFAULT; + goto out; + } + + rc = tpm2_commit_space(chip, space, ordinal, buf, &len); + out: + if (need_locality && chip->ops->relinquish_locality) { + chip->ops->relinquish_locality(chip, chip->locality); + chip->locality = -1; + } +out_no_locality: if (chip->dev.parent) pm_runtime_put_sync(chip->dev.parent); if (!(flags & TPM_TRANSMIT_UNLOCKED)) mutex_unlock(&chip->tpm_mutex); - return rc; + return rc ? rc : len; } /** @@ -434,23 +511,18 @@ out: * A negative number for system errors (errno). * A positive number for a TPM error. */ -ssize_t tpm_transmit_cmd(struct tpm_chip *chip, const void *buf, - size_t bufsiz, size_t min_rsp_body_length, - unsigned int flags, const char *desc) +ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_space *space, + const void *buf, size_t bufsiz, + size_t min_rsp_body_length, unsigned int flags, + const char *desc) { - const struct tpm_output_header *header; + const struct tpm_output_header *header = buf; int err; ssize_t len; - len = tpm_transmit(chip, (const u8 *)buf, bufsiz, flags); + len = tpm_transmit(chip, space, (u8 *)buf, bufsiz, flags); if (len < 0) return len; - else if (len < TPM_HEADER_SIZE) - return -EFAULT; - - header = buf; - if (len != be32_to_cpu(header->length)) - return -EFAULT; err = be32_to_cpu(header->return_code); if (err != 0 && desc) @@ -501,7 +573,7 @@ ssize_t tpm_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); tpm_cmd.params.getcap_in.subcap = cpu_to_be32(subcap_id); } - rc = tpm_transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, + rc = tpm_transmit_cmd(chip, NULL, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, min_cap_length, 0, desc); if (!rc) *cap = tpm_cmd.params.getcap_out.cap; @@ -525,7 +597,8 @@ static int tpm_startup(struct tpm_chip *chip, __be16 startup_type) start_cmd.header.in = tpm_startup_header; start_cmd.params.startup_in.startup_type = startup_type; - return tpm_transmit_cmd(chip, &start_cmd, TPM_INTERNAL_RESULT_SIZE, 0, + return tpm_transmit_cmd(chip, NULL, &start_cmd, + TPM_INTERNAL_RESULT_SIZE, 0, 0, "attempting to start the TPM"); } @@ -682,8 +755,8 @@ static int tpm_continue_selftest(struct tpm_chip *chip) struct tpm_cmd_t cmd; cmd.header.in = continue_selftest_header; - rc = tpm_transmit_cmd(chip, &cmd, CONTINUE_SELFTEST_RESULT_SIZE, 0, 0, - "continue selftest"); + rc = tpm_transmit_cmd(chip, NULL, &cmd, CONTINUE_SELFTEST_RESULT_SIZE, + 0, 0, "continue selftest"); return rc; } |