[PATCH v6 4/5] gpio: shared: detach the proxy device from struct gpio_shared_ref
From: Bartosz Golaszewski
Date: Wed Oct 21 2026 - 10:33:36 EST
Sashiko reported a pre-existing issue with the reference-counted
auxiliary device being embedded in struct gpio_shared_ref. We
unconditionally memset() the struct when setting up the GPIO shared
proxy parent while there may still be references to it held by users.
While in practice this would never happen and sashiko seems to have
confused the parent auxiliary device and the separately allocated struct
device embedded in struct gpio_device, it's still a bad pattern and
could lead to memory corruption if a user deliberately took reference to
the auxiliary device via one of the lookup functions and kept it over
the GPIO parent's teardown.
Move the auxiliary device struct out of the shared reference structure
and allocate its memory separately on proxy setup.
Since we're already touching that field: change the name to something
more meaningful: it's more important to point out this is the proxy
device instead of indicating its type in the name.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
---
drivers/gpio/gpiolib-shared.c | 63 +++++++++++++++++++++++++------------------
1 file changed, 37 insertions(+), 26 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 043d5daba7a73062151a089b3ee8ccdac726f221..3ed25609cae10081a0ab5276ebcd47aa3c49f76a 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -35,11 +35,10 @@ struct gpio_shared_ref {
/* GPIO flags this consumer uses for the request. */
enum gpiod_flags flags;
char *con_id;
- int dev_id;
/* Protects the auxiliary device struct and the lookup table. */
struct mutex lock;
struct lock_class_key lock_key;
- struct auxiliary_device adev;
+ struct auxiliary_device *proxy_dev;
struct gpiod_lookup_table *lookup;
bool is_reset_gpio;
};
@@ -94,10 +93,6 @@ static struct gpio_shared_ref *gpio_shared_make_ref(struct fwnode_handle *fwnode
return NULL;
}
- ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
- if (ref->dev_id < 0)
- return NULL;
-
ref->flags = flags;
ref->con_id = no_free_ptr(con_id_cpy);
ref->fwnode = fwnode;
@@ -306,31 +301,43 @@ static int gpio_shared_of_scan(void)
}
#endif /* CONFIG_OF */
-static void gpio_shared_adev_release(struct device *dev)
+static void gpio_shared_proxy_dev_release(struct device *dev)
{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+ ida_free(&gpio_shared_ida, adev->id);
+ kfree(adev);
}
-static int gpio_shared_make_adev(struct gpio_device *gdev,
- struct gpio_shared_entry *entry,
- struct gpio_shared_ref *ref)
+static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
+ struct gpio_shared_entry *entry,
+ struct gpio_shared_ref *ref)
{
- struct auxiliary_device *adev = &ref->adev;
- int ret;
+ struct auxiliary_device *adev;
+ int ret, id;
- guard(mutex)(&ref->lock);
+ adev = kzalloc_obj(*adev);
+ if (!adev)
+ return -ENOMEM;
- memset(adev, 0, sizeof(*adev));
+ id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
+ if (id < 0) {
+ kfree(adev);
+ return id;
+ }
- adev->id = ref->dev_id;
+ adev->id = id;
adev->name = "proxy";
adev->dev.parent = gdev->dev.parent;
adev->dev.platform_data = entry;
- adev->dev.release = gpio_shared_adev_release;
+ adev->dev.release = gpio_shared_proxy_dev_release;
ret = auxiliary_device_init(adev);
- if (ret)
+ if (ret) {
+ ida_free(&gpio_shared_ida, id);
+ kfree(adev);
return ret;
+ }
ret = auxiliary_device_add(adev);
if (ret) {
@@ -341,6 +348,10 @@ static int gpio_shared_make_adev(struct gpio_device *gdev,
pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n",
dev_name(&adev->dev), gpio_device_get_label(gdev));
+ scoped_guard(mutex, &ref->lock) {
+ ref->proxy_dev = adev;
+ }
+
return 0;
}
@@ -480,7 +491,7 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
char *key __free(kfree) =
kasprintf(GFP_KERNEL,
KBUILD_MODNAME ".proxy.%u",
- ref->adev.id);
+ ref->proxy_dev->id);
if (!key)
return -ENOMEM;
@@ -511,10 +522,11 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
return -ENOENT;
}
-static void gpio_shared_remove_adev(struct auxiliary_device *adev)
+static void gpio_shared_remove_proxy_dev(struct gpio_shared_ref *ref)
{
- auxiliary_device_delete(adev);
- auxiliary_device_uninit(adev);
+ auxiliary_device_delete(ref->proxy_dev);
+ auxiliary_device_uninit(ref->proxy_dev);
+ ref->proxy_dev = NULL;
}
static void gpio_shared_teardown_entry(struct gpio_device *gdev,
@@ -534,7 +546,7 @@ static void gpio_shared_teardown_entry(struct gpio_device *gdev,
ref->lookup = NULL;
}
- gpio_shared_remove_adev(&ref->adev);
+ gpio_shared_remove_proxy_dev(ref);
}
}
@@ -588,7 +600,7 @@ static int gpio_shared_setup_entry(struct gpio_chip *gc,
fwnode_get_name(ref->fwnode) ?: "(no fwnode)",
ref->con_id ?: "(none)");
- ret = gpio_shared_make_adev(gdev, entry, ref);
+ ret = gpio_shared_make_proxy_dev(gdev, entry, ref);
if (ret)
goto err_out;
}
@@ -598,7 +610,7 @@ static int gpio_shared_setup_entry(struct gpio_chip *gc,
err_out:
gpiod_free_commit(desc);
list_for_each_entry_continue_reverse(ref, &entry->refs, list)
- gpio_shared_remove_adev(&ref->adev);
+ gpio_shared_remove_proxy_dev(ref);
return ret;
}
@@ -619,7 +631,7 @@ int gpiochip_setup_shared(struct gpio_chip *gc)
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
- if (gdev->dev.parent == &ref->adev.dev) {
+ if (ref->proxy_dev && gdev->dev.parent == &ref->proxy_dev->dev) {
/*
* This is a shared GPIO proxy. Mark its
* descriptor as such and return here.
@@ -756,7 +768,6 @@ static void gpio_shared_drop_ref(struct gpio_shared_ref *ref)
mutex_destroy(&ref->lock);
lockdep_unregister_key(&ref->lock_key);
kfree(ref->con_id);
- ida_free(&gpio_shared_ida, ref->dev_id);
fwnode_handle_put(ref->fwnode);
kfree(ref);
}
--
2.47.3