]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/char/tpm/tpm.c
drivers/char/tpm/tpm.c: fix error-path memory leak
[linux-2.6-omap-h63xx.git] / drivers / char / tpm / tpm.c
index e1fc193d9396fc69c41ecd868fd2bd2d60ff73b7..e70d13defde4bcc30724187ff6443f553730eeb7 100644 (file)
@@ -525,19 +525,19 @@ void tpm_get_timeouts(struct tpm_chip *chip)
        timeout =
            be32_to_cpu(*((__be32 *) (data + TPM_GET_CAP_RET_UINT32_1_IDX)));
        if (timeout)
-               chip->vendor.timeout_a = msecs_to_jiffies(timeout);
+               chip->vendor.timeout_a = usecs_to_jiffies(timeout);
        timeout =
            be32_to_cpu(*((__be32 *) (data + TPM_GET_CAP_RET_UINT32_2_IDX)));
        if (timeout)
-               chip->vendor.timeout_b = msecs_to_jiffies(timeout);
+               chip->vendor.timeout_b = usecs_to_jiffies(timeout);
        timeout =
            be32_to_cpu(*((__be32 *) (data + TPM_GET_CAP_RET_UINT32_3_IDX)));
        if (timeout)
-               chip->vendor.timeout_c = msecs_to_jiffies(timeout);
+               chip->vendor.timeout_c = usecs_to_jiffies(timeout);
        timeout =
            be32_to_cpu(*((__be32 *) (data + TPM_GET_CAP_RET_UINT32_4_IDX)));
        if (timeout)
-               chip->vendor.timeout_d = msecs_to_jiffies(timeout);
+               chip->vendor.timeout_d = usecs_to_jiffies(timeout);
 
 duration:
        memcpy(data, tpm_cap, sizeof(tpm_cap));
@@ -554,15 +554,22 @@ duration:
                return;
 
        chip->vendor.duration[TPM_SHORT] =
-           msecs_to_jiffies(be32_to_cpu
+           usecs_to_jiffies(be32_to_cpu
                             (*((__be32 *) (data +
                                            TPM_GET_CAP_RET_UINT32_1_IDX))));
+       /* The Broadcom BCM0102 chipset in a Dell Latitude D820 gets the above
+        * value wrong and apparently reports msecs rather than usecs. So we
+        * fix up the resulting too-small TPM_SHORT value to make things work.
+        */
+       if (chip->vendor.duration[TPM_SHORT] < (HZ/100))
+               chip->vendor.duration[TPM_SHORT] = HZ;
+
        chip->vendor.duration[TPM_MEDIUM] =
-           msecs_to_jiffies(be32_to_cpu
+           usecs_to_jiffies(be32_to_cpu
                             (*((__be32 *) (data +
                                            TPM_GET_CAP_RET_UINT32_2_IDX))));
        chip->vendor.duration[TPM_LONG] =
-           msecs_to_jiffies(be32_to_cpu
+           usecs_to_jiffies(be32_to_cpu
                             (*((__be32 *) (data +
                                            TPM_GET_CAP_RET_UINT32_3_IDX))));
 }
@@ -580,91 +587,133 @@ void tpm_continue_selftest(struct tpm_chip *chip)
 }
 EXPORT_SYMBOL_GPL(tpm_continue_selftest);
 
+#define  TPM_INTERNAL_RESULT_SIZE 200
+
 ssize_t tpm_show_enabled(struct device * dev, struct device_attribute * attr,
                        char *buf)
 {
-       u8 data[max_t(int, ARRAY_SIZE(tpm_cap), 35)];
+       u8 *data;
        ssize_t rc;
 
        struct tpm_chip *chip = dev_get_drvdata(dev);
        if (chip == NULL)
                return -ENODEV;
 
+       data = kzalloc(TPM_INTERNAL_RESULT_SIZE, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
        memcpy(data, tpm_cap, sizeof(tpm_cap));
        data[TPM_CAP_IDX] = TPM_CAP_FLAG;
        data[TPM_CAP_SUBCAP_IDX] = TPM_CAP_FLAG_PERM;
 
-       rc = transmit_cmd(chip, data, sizeof(data),
-                       "attemtping to determine the permanent state");
-       if (rc)
+       rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
+                       "attemtping to determine the permanent enabled state");
+       if (rc) {
+               kfree(data);
                return 0;
-       return sprintf(buf, "%d\n", !data[TPM_GET_CAP_PERM_DISABLE_IDX]);
+       }
+
+       rc = sprintf(buf, "%d\n", !data[TPM_GET_CAP_PERM_DISABLE_IDX]);
+
+       kfree(data);
+       return rc;
 }
 EXPORT_SYMBOL_GPL(tpm_show_enabled);
 
 ssize_t tpm_show_active(struct device * dev, struct device_attribute * attr,
                        char *buf)
 {
-       u8 data[max_t(int, ARRAY_SIZE(tpm_cap), 35)];
+       u8 *data;
        ssize_t rc;
 
        struct tpm_chip *chip = dev_get_drvdata(dev);
        if (chip == NULL)
                return -ENODEV;
 
+       data = kzalloc(TPM_INTERNAL_RESULT_SIZE, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
        memcpy(data, tpm_cap, sizeof(tpm_cap));
        data[TPM_CAP_IDX] = TPM_CAP_FLAG;
        data[TPM_CAP_SUBCAP_IDX] = TPM_CAP_FLAG_PERM;
 
-       rc = transmit_cmd(chip, data, sizeof(data),
-                       "attemtping to determine the permanent state");
-       if (rc)
+       rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
+                       "attemtping to determine the permanent active state");
+       if (rc) {
+               kfree(data);
                return 0;
-       return sprintf(buf, "%d\n", !data[TPM_GET_CAP_PERM_INACTIVE_IDX]);
+       }
+
+       rc = sprintf(buf, "%d\n", !data[TPM_GET_CAP_PERM_INACTIVE_IDX]);
+
+       kfree(data);
+       return rc;
 }
 EXPORT_SYMBOL_GPL(tpm_show_active);
 
 ssize_t tpm_show_owned(struct device * dev, struct device_attribute * attr,
                        char *buf)
 {
-       u8 data[sizeof(tpm_cap)];
+       u8 *data;
        ssize_t rc;
 
        struct tpm_chip *chip = dev_get_drvdata(dev);
        if (chip == NULL)
                return -ENODEV;
 
+       data = kzalloc(TPM_INTERNAL_RESULT_SIZE, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
        memcpy(data, tpm_cap, sizeof(tpm_cap));
        data[TPM_CAP_IDX] = TPM_CAP_PROP;
        data[TPM_CAP_SUBCAP_IDX] = TPM_CAP_PROP_OWNER;
 
-       rc = transmit_cmd(chip, data, sizeof(data),
+       rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
                        "attempting to determine the owner state");
-       if (rc)
+       if (rc) {
+               kfree(data);
                return 0;
-       return sprintf(buf, "%d\n", data[TPM_GET_CAP_RET_BOOL_1_IDX]);
+       }
+
+       rc = sprintf(buf, "%d\n", data[TPM_GET_CAP_RET_BOOL_1_IDX]);
+
+       kfree(data);
+       return rc;
 }
 EXPORT_SYMBOL_GPL(tpm_show_owned);
 
 ssize_t tpm_show_temp_deactivated(struct device * dev,
                                struct device_attribute * attr, char *buf)
 {
-       u8 data[sizeof(tpm_cap)];
+       u8 *data;
        ssize_t rc;
 
        struct tpm_chip *chip = dev_get_drvdata(dev);
        if (chip == NULL)
                return -ENODEV;
 
+       data = kzalloc(TPM_INTERNAL_RESULT_SIZE, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
        memcpy(data, tpm_cap, sizeof(tpm_cap));
        data[TPM_CAP_IDX] = TPM_CAP_FLAG;
        data[TPM_CAP_SUBCAP_IDX] = TPM_CAP_FLAG_VOL;
 
-       rc = transmit_cmd(chip, data, sizeof(data),
+       rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
                        "attempting to determine the temporary state");
-       if (rc)
+       if (rc) {
+               kfree(data);
                return 0;
-       return sprintf(buf, "%d\n", data[TPM_GET_CAP_TEMP_INACTIVE_IDX]);
+       }
+
+       rc = sprintf(buf, "%d\n", data[TPM_GET_CAP_TEMP_INACTIVE_IDX]);
+
+       kfree(data);
+       return rc;
 }
 EXPORT_SYMBOL_GPL(tpm_show_temp_deactivated);
 
@@ -678,7 +727,7 @@ static const u8 pcrread[] = {
 ssize_t tpm_show_pcrs(struct device *dev, struct device_attribute *attr,
                      char *buf)
 {
-       u8 data[max_t(int, max(ARRAY_SIZE(tpm_cap), ARRAY_SIZE(pcrread)), 30)];
+       u8 *data;
        ssize_t rc;
        int i, j, num_pcrs;
        __be32 index;
@@ -688,21 +737,27 @@ ssize_t tpm_show_pcrs(struct device *dev, struct device_attribute *attr,
        if (chip == NULL)
                return -ENODEV;
 
+       data = kzalloc(TPM_INTERNAL_RESULT_SIZE, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
        memcpy(data, tpm_cap, sizeof(tpm_cap));
        data[TPM_CAP_IDX] = TPM_CAP_PROP;
        data[TPM_CAP_SUBCAP_IDX] = TPM_CAP_PROP_PCR;
 
-       rc = transmit_cmd(chip, data, sizeof(data),
+       rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
                        "attempting to determine the number of PCRS");
-       if (rc)
+       if (rc) {
+               kfree(data);
                return 0;
+       }
 
        num_pcrs = be32_to_cpu(*((__be32 *) (data + 14)));
        for (i = 0; i < num_pcrs; i++) {
                memcpy(data, pcrread, sizeof(pcrread));
                index = cpu_to_be32(i);
                memcpy(data + 10, &index, 4);
-               rc = transmit_cmd(chip, data, sizeof(data),
+               rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
                                "attempting to read a PCR");
                if (rc)
                        goto out;
@@ -712,6 +767,7 @@ ssize_t tpm_show_pcrs(struct device *dev, struct device_attribute *attr,
                str += sprintf(str, "\n");
        }
 out:
+       kfree(data);
        return str - buf;
 }
 EXPORT_SYMBOL_GPL(tpm_show_pcrs);
@@ -795,7 +851,7 @@ static const u8 cap_version[] = {
 ssize_t tpm_show_caps(struct device *dev, struct device_attribute *attr,
                      char *buf)
 {
-       u8 data[max_t(int, max(ARRAY_SIZE(tpm_cap), ARRAY_SIZE(cap_version)), 30)];
+       u8 *data;
        ssize_t rc;
        char *str = buf;
 
@@ -803,21 +859,27 @@ ssize_t tpm_show_caps(struct device *dev, struct device_attribute *attr,
        if (chip == NULL)
                return -ENODEV;
 
+       data = kzalloc(TPM_INTERNAL_RESULT_SIZE, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
        memcpy(data, tpm_cap, sizeof(tpm_cap));
        data[TPM_CAP_IDX] = TPM_CAP_PROP;
        data[TPM_CAP_SUBCAP_IDX] = TPM_CAP_PROP_MANUFACTURER;
 
-       rc = transmit_cmd(chip, data, sizeof(data),
+       rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
                        "attempting to determine the manufacturer");
-       if (rc)
+       if (rc) {
+               kfree(data);
                return 0;
+       }
 
        str += sprintf(str, "Manufacturer: 0x%x\n",
                       be32_to_cpu(*((__be32 *) (data + TPM_GET_CAP_RET_UINT32_1_IDX))));
 
        memcpy(data, cap_version, sizeof(cap_version));
        data[CAP_VERSION_IDX] = CAP_VERSION_1_1;
-       rc = transmit_cmd(chip, data, sizeof(data),
+       rc = transmit_cmd(chip, data, TPM_INTERNAL_RESULT_SIZE,
                        "attempting to determine the 1.1 version");
        if (rc)
                goto out;
@@ -828,6 +890,7 @@ ssize_t tpm_show_caps(struct device *dev, struct device_attribute *attr,
                       (int) data[17]);
 
 out:
+       kfree(data);
        return str - buf;
 }
 EXPORT_SYMBOL_GPL(tpm_show_caps);
@@ -835,7 +898,7 @@ EXPORT_SYMBOL_GPL(tpm_show_caps);
 ssize_t tpm_show_caps_1_2(struct device * dev,
                          struct device_attribute * attr, char *buf)
 {
-       u8 data[max_t(int, max(ARRAY_SIZE(tpm_cap), ARRAY_SIZE(cap_version)), 30)];
+       u8 *data;
        ssize_t len;
        char *str = buf;
 
@@ -843,15 +906,20 @@ ssize_t tpm_show_caps_1_2(struct device * dev,
        if (chip == NULL)
                return -ENODEV;
 
+       data = kzalloc(TPM_INTERNAL_RESULT_SIZE, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
        memcpy(data, tpm_cap, sizeof(tpm_cap));
        data[TPM_CAP_IDX] = TPM_CAP_PROP;
        data[TPM_CAP_SUBCAP_IDX] = TPM_CAP_PROP_MANUFACTURER;
 
-       if ((len = tpm_transmit(chip, data, sizeof(data))) <=
-           TPM_ERROR_SIZE) {
+       len = tpm_transmit(chip, data, TPM_INTERNAL_RESULT_SIZE);
+       if (len <= TPM_ERROR_SIZE) {
                dev_dbg(chip->dev, "A TPM error (%d) occurred "
                        "attempting to determine the manufacturer\n",
                        be32_to_cpu(*((__be32 *) (data + TPM_RET_CODE_IDX))));
+               kfree(data);
                return 0;
        }
 
@@ -861,8 +929,8 @@ ssize_t tpm_show_caps_1_2(struct device * dev,
        memcpy(data, cap_version, sizeof(cap_version));
        data[CAP_VERSION_IDX] = CAP_VERSION_1_2;
 
-       if ((len = tpm_transmit(chip, data, sizeof(data))) <=
-           TPM_ERROR_SIZE) {
+       len = tpm_transmit(chip, data, TPM_INTERNAL_RESULT_SIZE);
+       if (len <= TPM_ERROR_SIZE) {
                dev_err(chip->dev, "A TPM error (%d) occurred "
                        "attempting to determine the 1.2 version\n",
                        be32_to_cpu(*((__be32 *) (data + TPM_RET_CODE_IDX))));
@@ -874,6 +942,7 @@ ssize_t tpm_show_caps_1_2(struct device * dev,
                       (int) data[19]);
 
 out:
+       kfree(data);
        return str - buf;
 }
 EXPORT_SYMBOL_GPL(tpm_show_caps_1_2);
@@ -892,72 +961,63 @@ EXPORT_SYMBOL_GPL(tpm_store_cancel);
 
 /*
  * Device file system interface to the TPM
+ *
+ * It's assured that the chip will be opened just once,
+ * by the check of is_open variable, which is protected
+ * by driver_lock.
  */
 int tpm_open(struct inode *inode, struct file *file)
 {
-       int rc = 0, minor = iminor(inode);
+       int minor = iminor(inode);
        struct tpm_chip *chip = NULL, *pos;
 
-       lock_kernel();
-       spin_lock(&driver_lock);
-
-       list_for_each_entry(pos, &tpm_chip_list, list) {
+       rcu_read_lock();
+       list_for_each_entry_rcu(pos, &tpm_chip_list, list) {
                if (pos->vendor.miscdev.minor == minor) {
                        chip = pos;
+                       get_device(chip->dev);
                        break;
                }
        }
+       rcu_read_unlock();
 
-       if (chip == NULL) {
-               rc = -ENODEV;
-               goto err_out;
-       }
+       if (!chip)
+               return -ENODEV;
 
-       if (chip->num_opens) {
+       if (test_and_set_bit(0, &chip->is_open)) {
                dev_dbg(chip->dev, "Another process owns this TPM\n");
-               rc = -EBUSY;
-               goto err_out;
+               put_device(chip->dev);
+               return -EBUSY;
        }
 
-       chip->num_opens++;
-       get_device(chip->dev);
-
-       spin_unlock(&driver_lock);
-
        chip->data_buffer = kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL);
        if (chip->data_buffer == NULL) {
-               chip->num_opens--;
+               clear_bit(0, &chip->is_open);
                put_device(chip->dev);
-               unlock_kernel();
                return -ENOMEM;
        }
 
        atomic_set(&chip->data_pending, 0);
 
        file->private_data = chip;
-       unlock_kernel();
        return 0;
-
-err_out:
-       spin_unlock(&driver_lock);
-       unlock_kernel();
-       return rc;
 }
 EXPORT_SYMBOL_GPL(tpm_open);
 
+/*
+ * Called on file close
+ */
 int tpm_release(struct inode *inode, struct file *file)
 {
        struct tpm_chip *chip = file->private_data;
 
+       del_singleshot_timer_sync(&chip->user_read_timer);
        flush_scheduled_work();
-       spin_lock(&driver_lock);
        file->private_data = NULL;
-       del_singleshot_timer_sync(&chip->user_read_timer);
        atomic_set(&chip->data_pending, 0);
-       chip->num_opens--;
-       put_device(chip->dev);
        kfree(chip->data_buffer);
-       spin_unlock(&driver_lock);
+       clear_bit(0, &chip->is_open);
+       put_device(chip->dev);
        return 0;
 }
 EXPORT_SYMBOL_GPL(tpm_release);
@@ -966,7 +1026,7 @@ ssize_t tpm_write(struct file *file, const char __user *buf,
                  size_t size, loff_t *off)
 {
        struct tpm_chip *chip = file->private_data;
-       int in_size = size, out_size;
+       size_t in_size = size, out_size;
 
        /* cannot perform a write until the read has cleared
           either via tpm_read or a user_read_timer timeout */
@@ -1001,7 +1061,7 @@ ssize_t tpm_read(struct file *file, char __user *buf,
                 size_t size, loff_t *off)
 {
        struct tpm_chip *chip = file->private_data;
-       int ret_size;
+       ssize_t ret_size;
 
        del_singleshot_timer_sync(&chip->user_read_timer);
        flush_scheduled_work();
@@ -1031,13 +1091,11 @@ void tpm_remove_hardware(struct device *dev)
        }
 
        spin_lock(&driver_lock);
-
-       list_del(&chip->list);
-
+       list_del_rcu(&chip->list);
        spin_unlock(&driver_lock);
+       synchronize_rcu();
 
        misc_deregister(&chip->vendor.miscdev);
-
        sysfs_remove_group(&dev->kobj, chip->vendor.attr_group);
        tpm_bios_log_teardown(chip->bios_dir);
 
@@ -1082,25 +1140,33 @@ int tpm_pm_resume(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(tpm_pm_resume);
 
+/* In case vendor provided release function, call it too.*/
+
+void tpm_dev_vendor_release(struct tpm_chip *chip)
+{
+       if (chip->vendor.release)
+               chip->vendor.release(chip->dev);
+
+       clear_bit(chip->dev_num, dev_mask);
+       kfree(chip->vendor.miscdev.name);
+}
+EXPORT_SYMBOL_GPL(tpm_dev_vendor_release);
+
+
 /*
  * Once all references to platform device are down to 0,
  * release all allocated structures.
- * In case vendor provided release function,
- * call it too.
  */
 static void tpm_dev_release(struct device *dev)
 {
        struct tpm_chip *chip = dev_get_drvdata(dev);
 
-       if (chip->vendor.release)
-               chip->vendor.release(dev);
+       tpm_dev_vendor_release(chip);
 
        chip->release(dev);
-
-       clear_bit(chip->dev_num, dev_mask);
-       kfree(chip->vendor.miscdev.name);
        kfree(chip);
 }
+EXPORT_SYMBOL_GPL(tpm_dev_release);
 
 /*
  * Called from tpm_<specific>.c probe function only for devices 
@@ -1109,8 +1175,8 @@ static void tpm_dev_release(struct device *dev)
  * upon errant exit from this function specific probe function should call
  * pci_disable_device
  */
-struct tpm_chip *tpm_register_hardware(struct device *dev, const struct tpm_vendor_specific
-                                      *entry)
+struct tpm_chip *tpm_register_hardware(struct device *dev,
+                                       const struct tpm_vendor_specific *entry)
 {
 #define DEVNAME_SIZE 7
 
@@ -1121,11 +1187,8 @@ struct tpm_chip *tpm_register_hardware(struct device *dev, const struct tpm_vend
        chip = kzalloc(sizeof(*chip), GFP_KERNEL);
        devname = kmalloc(DEVNAME_SIZE, GFP_KERNEL);
 
-       if (chip == NULL || devname == NULL) {
-               kfree(chip);
-               kfree(devname);
-               return NULL;
-       }
+       if (chip == NULL || devname == NULL)
+               goto out_free;
 
        mutex_init(&chip->buffer_mutex);
        mutex_init(&chip->tpm_mutex);
@@ -1142,8 +1205,7 @@ struct tpm_chip *tpm_register_hardware(struct device *dev, const struct tpm_vend
 
        if (chip->dev_num >= TPM_NUM_DEVICES) {
                dev_err(dev, "No available tpm device numbers\n");
-               kfree(chip);
-               return NULL;
+               goto out_free;
        } else if (chip->dev_num == 0)
                chip->vendor.miscdev.minor = TPM_MINOR;
        else
@@ -1169,22 +1231,26 @@ struct tpm_chip *tpm_register_hardware(struct device *dev, const struct tpm_vend
                return NULL;
        }
 
-       spin_lock(&driver_lock);
-
-       list_add(&chip->list, &tpm_chip_list);
-
-       spin_unlock(&driver_lock);
-
        if (sysfs_create_group(&dev->kobj, chip->vendor.attr_group)) {
-               list_del(&chip->list);
                misc_deregister(&chip->vendor.miscdev);
                put_device(chip->dev);
+
                return NULL;
        }
 
        chip->bios_dir = tpm_bios_log_setup(devname);
 
+       /* Make chip available */
+       spin_lock(&driver_lock);
+       list_add_rcu(&chip->list, &tpm_chip_list);
+       spin_unlock(&driver_lock);
+
        return chip;
+
+out_free:
+       kfree(chip);
+       kfree(devname);
+       return NULL;
 }
 EXPORT_SYMBOL_GPL(tpm_register_hardware);