Skip to content

Commit

Permalink
gpio: sysfs: fix memory leaks and device hotplug
Browse files Browse the repository at this point in the history
Unregister GPIOs requested through sysfs at chip remove to avoid leaking
the associated memory and sysfs entries.

The stale sysfs entries prevented the gpio numbers from being exported
when the gpio range was later reused (e.g. at device reconnect).

This also fixes the related module-reference leak.

Note that kernfs makes sure that any on-going sysfs operations finish
before the class devices are unregistered and that further accesses
fail.

The chip exported flag is used to prevent gpiod exports during removal.
This also makes it harder to trigger, but does not fix, the related race
between gpiochip_remove and export_store, which is really a race with
gpiod_request that needs to be addressed separately.

Also note that this would prevent the crashes (e.g. NULL-dereferences)
at reconnect that affects pre-3.18 kernels, as well as use-after-free on
operations on open attribute files on pre-3.14 kernels (prior to
kernfs).

Fixes: d8f388d ("gpio: sysfs interface")
Cc: stable <stable@vger.kernel.org>	# v2.6.27: 01cca93
Signed-off-by: Johan Hovold <johan@kernel.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
  • Loading branch information
Johan Hovold authored and Linus Walleij committed Apr 29, 2015
1 parent 24a6661 commit 483d821
Showing 1 changed file with 19 additions and 0 deletions.
19 changes: 19 additions & 0 deletions drivers/gpio/gpiolib-sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ static struct class gpio_class = {
*/
int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
{
struct gpio_chip *chip;
unsigned long flags;
int status;
const char *ioname = NULL;
Expand All @@ -568,8 +569,16 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
return -EINVAL;
}

chip = desc->chip;

mutex_lock(&sysfs_lock);

/* check if chip is being removed */
if (!chip || !chip->exported) {
status = -ENODEV;
goto fail_unlock;
}

spin_lock_irqsave(&gpio_lock, flags);
if (!test_bit(FLAG_REQUESTED, &desc->flags) ||
test_bit(FLAG_EXPORT, &desc->flags)) {
Expand Down Expand Up @@ -783,12 +792,15 @@ void gpiochip_unexport(struct gpio_chip *chip)
{
int status;
struct device *dev;
struct gpio_desc *desc;
unsigned int i;

mutex_lock(&sysfs_lock);
dev = class_find_device(&gpio_class, NULL, chip, match_export);
if (dev) {
put_device(dev);
device_unregister(dev);
/* prevent further gpiod exports */
chip->exported = false;
status = 0;
} else
Expand All @@ -797,6 +809,13 @@ void gpiochip_unexport(struct gpio_chip *chip)

if (status)
chip_dbg(chip, "%s: status %d\n", __func__, status);

/* unregister gpiod class devices owned by sysfs */
for (i = 0; i < chip->ngpio; i++) {
desc = &chip->desc[i];
if (test_and_clear_bit(FLAG_SYSFS, &desc->flags))
gpiod_free(desc);
}
}

static int __init gpiolib_sysfs_init(void)
Expand Down

0 comments on commit 483d821

Please sign in to comment.