hwmon: (core) Do not use device managed functions for memory allocations
authorGuenter Roeck <linux@roeck-us.net>
Thu, 16 Jan 2020 18:44:17 +0000 (10:44 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 29 Jan 2020 09:24:38 +0000 (10:24 +0100)
commit 3bf8bdcf3bada771eb12b57f2a30caee69e8ab8d upstream.

The hwmon core uses device managed functions, tied to the hwmon parent
device, for various internal memory allocations. This is problematic
since hwmon device lifetime does not necessarily match its parent's
device lifetime. If there is a mismatch, memory leaks will accumulate
until the parent device is released.

Fix the problem by managing all memory allocations internally. The only
exception is memory allocation for thermal device registration, which
can be tied to the hwmon device, along with thermal device registration
itself.

Fixes: d560168b5d0f ("hwmon: (core) New hwmon registration API")
Cc: stable@vger.kernel.org # v4.14.x: 47c332deb8e8: hwmon: Deal with errors from the thermal subsystem
Cc: stable@vger.kernel.org # v4.14.x: 74e3512731bd: hwmon: (core) Fix double-free in __hwmon_device_register()
Cc: stable@vger.kernel.org # v4.9.x: 3a412d5e4a1c: hwmon: (core) Simplify sysfs attribute name allocation
Cc: stable@vger.kernel.org # v4.9.x: 47c332deb8e8: hwmon: Deal with errors from the thermal subsystem
Cc: stable@vger.kernel.org # v4.9.x: 74e3512731bd: hwmon: (core) Fix double-free in __hwmon_device_register()
Cc: stable@vger.kernel.org # v4.9+
Cc: Martin K. Petersen <martin.petersen@oracle.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/hwmon/hwmon.c

index ddb8e70..e0a1a11 100644 (file)
@@ -51,6 +51,7 @@ struct hwmon_device_attribute {
 
 #define to_hwmon_attr(d) \
        container_of(d, struct hwmon_device_attribute, dev_attr)
+#define to_dev_attr(a) container_of(a, struct device_attribute, attr)
 
 /*
  * Thermal zone information
@@ -58,7 +59,7 @@ struct hwmon_device_attribute {
  * also provides the sensor index.
  */
 struct hwmon_thermal_data {
-       struct hwmon_device *hwdev;     /* Reference to hwmon device */
+       struct device *dev;             /* Reference to hwmon device */
        int index;                      /* sensor index */
 };
 
@@ -95,9 +96,27 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = {
        NULL
 };
 
+static void hwmon_free_attrs(struct attribute **attrs)
+{
+       int i;
+
+       for (i = 0; attrs[i]; i++) {
+               struct device_attribute *dattr = to_dev_attr(attrs[i]);
+               struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr);
+
+               kfree(hattr);
+       }
+       kfree(attrs);
+}
+
 static void hwmon_dev_release(struct device *dev)
 {
-       kfree(to_hwmon_device(dev));
+       struct hwmon_device *hwdev = to_hwmon_device(dev);
+
+       if (hwdev->group.attrs)
+               hwmon_free_attrs(hwdev->group.attrs);
+       kfree(hwdev->groups);
+       kfree(hwdev);
 }
 
 static struct class hwmon_class = {
@@ -121,11 +140,11 @@ static DEFINE_IDA(hwmon_ida);
 static int hwmon_thermal_get_temp(void *data, int *temp)
 {
        struct hwmon_thermal_data *tdata = data;
-       struct hwmon_device *hwdev = tdata->hwdev;
+       struct hwmon_device *hwdev = to_hwmon_device(tdata->dev);
        int ret;
        long t;
 
-       ret = hwdev->chip->ops->read(&hwdev->dev, hwmon_temp, hwmon_temp_input,
+       ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input,
                                     tdata->index, &t);
        if (ret < 0)
                return ret;
@@ -139,8 +158,7 @@ static struct thermal_zone_of_device_ops hwmon_thermal_ops = {
        .get_temp = hwmon_thermal_get_temp,
 };
 
-static int hwmon_thermal_add_sensor(struct device *dev,
-                                   struct hwmon_device *hwdev, int index)
+static int hwmon_thermal_add_sensor(struct device *dev, int index)
 {
        struct hwmon_thermal_data *tdata;
        struct thermal_zone_device *tzd;
@@ -149,10 +167,10 @@ static int hwmon_thermal_add_sensor(struct device *dev,
        if (!tdata)
                return -ENOMEM;
 
-       tdata->hwdev = hwdev;
+       tdata->dev = dev;
        tdata->index = index;
 
-       tzd = devm_thermal_zone_of_sensor_register(&hwdev->dev, index, tdata,
+       tzd = devm_thermal_zone_of_sensor_register(dev, index, tdata,
                                                   &hwmon_thermal_ops);
        /*
         * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV,
@@ -164,8 +182,7 @@ static int hwmon_thermal_add_sensor(struct device *dev,
        return 0;
 }
 #else
-static int hwmon_thermal_add_sensor(struct device *dev,
-                                   struct hwmon_device *hwdev, int index)
+static int hwmon_thermal_add_sensor(struct device *dev, int index)
 {
        return 0;
 }
@@ -215,8 +232,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type)
        return 1;
 }
 
-static struct attribute *hwmon_genattr(struct device *dev,
-                                      const void *drvdata,
+static struct attribute *hwmon_genattr(const void *drvdata,
                                       enum hwmon_sensor_types type,
                                       u32 attr,
                                       int index,
@@ -242,7 +258,7 @@ static struct attribute *hwmon_genattr(struct device *dev,
        if ((mode & S_IWUGO) && !ops->write)
                return ERR_PTR(-EINVAL);
 
-       hattr = devm_kzalloc(dev, sizeof(*hattr), GFP_KERNEL);
+       hattr = kzalloc(sizeof(*hattr), GFP_KERNEL);
        if (!hattr)
                return ERR_PTR(-ENOMEM);
 
@@ -441,8 +457,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
        return n;
 }
 
-static int hwmon_genattrs(struct device *dev,
-                         const void *drvdata,
+static int hwmon_genattrs(const void *drvdata,
                          struct attribute **attrs,
                          const struct hwmon_ops *ops,
                          const struct hwmon_channel_info *info)
@@ -468,7 +483,7 @@ static int hwmon_genattrs(struct device *dev,
                        attr_mask &= ~BIT(attr);
                        if (attr >= template_size)
                                return -EINVAL;
-                       a = hwmon_genattr(dev, drvdata, info->type, attr, i,
+                       a = hwmon_genattr(drvdata, info->type, attr, i,
                                          templates[attr], ops);
                        if (IS_ERR(a)) {
                                if (PTR_ERR(a) != -ENOENT)
@@ -482,8 +497,7 @@ static int hwmon_genattrs(struct device *dev,
 }
 
 static struct attribute **
-__hwmon_create_attrs(struct device *dev, const void *drvdata,
-                    const struct hwmon_chip_info *chip)
+__hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip)
 {
        int ret, i, aindex = 0, nattrs = 0;
        struct attribute **attrs;
@@ -494,15 +508,17 @@ __hwmon_create_attrs(struct device *dev, const void *drvdata,
        if (nattrs == 0)
                return ERR_PTR(-EINVAL);
 
-       attrs = devm_kcalloc(dev, nattrs + 1, sizeof(*attrs), GFP_KERNEL);
+       attrs = kcalloc(nattrs + 1, sizeof(*attrs), GFP_KERNEL);
        if (!attrs)
                return ERR_PTR(-ENOMEM);
 
        for (i = 0; chip->info[i]; i++) {
-               ret = hwmon_genattrs(dev, drvdata, &attrs[aindex], chip->ops,
+               ret = hwmon_genattrs(drvdata, &attrs[aindex], chip->ops,
                                     chip->info[i]);
-               if (ret < 0)
+               if (ret < 0) {
+                       hwmon_free_attrs(attrs);
                        return ERR_PTR(ret);
+               }
                aindex += ret;
        }
 
@@ -542,14 +558,13 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
                        for (i = 0; groups[i]; i++)
                                ngroups++;
 
-               hwdev->groups = devm_kcalloc(dev, ngroups, sizeof(*groups),
-                                            GFP_KERNEL);
+               hwdev->groups = kcalloc(ngroups, sizeof(*groups), GFP_KERNEL);
                if (!hwdev->groups) {
                        err = -ENOMEM;
                        goto free_hwmon;
                }
 
-               attrs = __hwmon_create_attrs(dev, drvdata, chip);
+               attrs = __hwmon_create_attrs(drvdata, chip);
                if (IS_ERR(attrs)) {
                        err = PTR_ERR(attrs);
                        goto free_hwmon;
@@ -594,8 +609,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
                                                           hwmon_temp_input, j))
                                        continue;
                                if (info[i]->config[j] & HWMON_T_INPUT) {
-                                       err = hwmon_thermal_add_sensor(dev,
-                                                               hwdev, j);
+                                       err = hwmon_thermal_add_sensor(hdev, j);
                                        if (err) {
                                                device_unregister(hdev);
                                                goto ida_remove;
@@ -608,7 +622,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
        return hdev;
 
 free_hwmon:
-       kfree(hwdev);
+       hwmon_dev_release(hdev);
 ida_remove:
        ida_simple_remove(&hwmon_ida, id);
        return ERR_PTR(err);