* [PATCH v3 0/4] gpio: shared: replace platform lookup with dummy software nodes
@ 2026-10-13 13:11 Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 1/4] gpio: shared: fix use-after-free bug when adding shared proxies Bartosz Golaszewski
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Bartosz Golaszewski @ 2026-10-13 13:11 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski,
stable
Abel reported to me a crash on Eliza linked to fwnode GPIO lookup for
shared GPIOs. Turns out we don't support no-device, fwnode-based lookup
for shared GPIOs. In fact: we trigger a NULL-pointer deference.
Making it work requires a more serious rework. This is what patch 4/4
does with 3/4 introducing a preparatory improvement.
The second patch fixes the NULL-pointer dereference in favor of
gracefully returning an error. The first patch fixes another problem the
clanker found during review.
Tested that the issue no longer happens and that fwnode-based sharing of
GPIOs works on Qualcomm Eliza.
Patches 1-2 should go into v7.3 and stable. Patches 3-4 are v7.4
material.
Sashiko pointed out two more issues under v2 but they're false positives.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
Changes in v3:
- Move struct auxiliary_device out of struct gpio_shared_ref
- Link to v2: https://patch.msgid.link/20261001-gpio-shared-fwnode-lookup-v2-0-9a40681713cc@oss.qualcomm.com
Changes in v2:
- Rework the shared GPIO management to replace platform lookup with
software nodes associated with proxy GPIO controllers
- Link to v1: https://patch.msgid.link/20260929-gpio-shared-fwnode-lookup-v1-0-57d54811de1a@oss.qualcomm.com
---
Bartosz Golaszewski (4):
gpio: shared: fix use-after-free bug when adding shared proxies
gpio: shared: gracefully handle no-device lookup of shared GPIOs
gpio: shared: detach the proxy device from struct gpio_shared_ref
gpio: shared: convert to using software nodes for GPIO shared proxy lookup
drivers/gpio/gpiolib-shared.c | 154 ++++++++++++++++++++++--------------------
drivers/gpio/gpiolib-shared.h | 15 ++--
drivers/gpio/gpiolib.c | 20 +-----
3 files changed, 90 insertions(+), 99 deletions(-)
---
base-commit: 4eb2586bd40db27d77cc5b724ca9386ed0c71d53
change-id: 20260929-gpio-shared-fwnode-lookup-e866faeb5bde
Best regards,
--
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 1/4] gpio: shared: fix use-after-free bug when adding shared proxies
2026-10-13 13:11 [PATCH v3 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
@ 2026-10-13 13:11 ` Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 2/4] gpio: shared: gracefully handle no-device lookup of shared GPIOs Bartosz Golaszewski
` (2 subsequent siblings)
3 siblings, 0 replies; 7+ messages in thread
From: Bartosz Golaszewski @ 2026-10-13 13:11 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski,
stable
The GPIO lookup table can stay alive for longer than the kobject whose
name string's address we're assigning to lookup->dev_id. Fix a potential
UAF by duplicating the string.
Cc: stable@vger.kernel.org
Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 5f9623e40b0fbebcf318fcfb2e75768cf6e3f313..6acfec73b73477245c7e4c593f2925efbc8980c6 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -450,7 +450,6 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
const char *con_id, unsigned long lflags)
{
const char *dev_id = dev_name(consumer);
- struct gpiod_lookup_table *lookup;
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
@@ -480,18 +479,22 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
if (!key)
return -ENOMEM;
- lookup = kzalloc_flex(*lookup, table, 2);
+ struct gpiod_lookup_table *lookup __free(kfree) =
+ kzalloc_flex(*lookup, table, 2);
if (!lookup)
return -ENOMEM;
pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n",
dev_id, key, ref->con_id ?: "none");
- lookup->dev_id = dev_id;
+ lookup->dev_id = kstrdup(dev_id, GFP_KERNEL);
+ if (!lookup->dev_id)
+ return -ENOMEM;
+
lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0,
ref->con_id, lflags);
- ref->lookup = lookup;
+ ref->lookup = no_free_ptr(lookup);
gpiod_add_lookup_table(ref->lookup);
return 0;
@@ -611,6 +614,7 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
if (ref->lookup) {
gpiod_remove_lookup_table(ref->lookup);
kfree(ref->lookup->table[0].key);
+ kfree(ref->lookup->dev_id);
kfree(ref->lookup);
ref->lookup = NULL;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 2/4] gpio: shared: gracefully handle no-device lookup of shared GPIOs
2026-10-13 13:11 [PATCH v3 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 1/4] gpio: shared: fix use-after-free bug when adding shared proxies Bartosz Golaszewski
@ 2026-10-13 13:11 ` Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
3 siblings, 0 replies; 7+ messages in thread
From: Bartosz Golaszewski @ 2026-10-13 13:11 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski,
stable
We use platform lookup tables for shared GPIOs. This mechanism needs a
struct device to work so fwnode-based lookup with no device associated
currently causes a NULL-pointer deference.
Adding this functionality will require a larger rework and it has never
worked anyway so add a check that allows the kernel to gracefully handle
this situation with an error ahead of implementing that functionality as
a new feature.
Cc: stable@vger.kernel.org
Reported-by: Abel Vesa <abelvesa@kernel.org>
Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 6acfec73b73477245c7e4c593f2925efbc8980c6..8c9ac9d9330490b17fb3936b8758beb97a308a15 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -449,9 +449,14 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *fwnode,
const char *con_id, unsigned long lflags)
{
- const char *dev_id = dev_name(consumer);
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
+ const char *dev_id;
+
+ if (!consumer)
+ return -EOPNOTSUPP;
+
+ dev_id = dev_name(consumer);
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref
2026-10-13 13:11 [PATCH v3 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 1/4] gpio: shared: fix use-after-free bug when adding shared proxies Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 2/4] gpio: shared: gracefully handle no-device lookup of shared GPIOs Bartosz Golaszewski
@ 2026-10-13 13:11 ` Bartosz Golaszewski
2026-10-13 13:31 ` sashiko-bot
2026-10-13 13:11 ` [PATCH v3 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
3 siblings, 1 reply; 7+ messages in thread
From: Bartosz Golaszewski @ 2026-10-13 13:11 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
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@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 61 +++++++++++++++++++++++++------------------
1 file changed, 36 insertions(+), 25 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 8c9ac9d9330490b17fb3936b8758beb97a308a15..a57a2dd5ca00eead40dc79198c94862b5a3d1ee9 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;
}
int gpiochip_setup_shared(struct gpio_chip *gc)
@@ -527,7 +539,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 (gdev->dev.parent == &ref->proxy_dev->dev) {
/*
* This is a shared GPIO proxy. Mark its
* descriptor as such and return here.
@@ -592,7 +604,7 @@ int gpiochip_setup_shared(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) {
gpiod_free_commit(desc);
return ret;
@@ -624,7 +636,7 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
ref->lookup = NULL;
}
- gpio_shared_remove_adev(&ref->adev);
+ gpio_shared_remove_proxy_dev(ref);
}
}
}
@@ -718,7 +730,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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup
2026-10-13 13:11 [PATCH v3 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
` (2 preceding siblings ...)
2026-10-13 13:11 ` [PATCH v3 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
@ 2026-10-13 13:11 ` Bartosz Golaszewski
2026-10-13 13:27 ` sashiko-bot
3 siblings, 1 reply; 7+ messages in thread
From: Bartosz Golaszewski @ 2026-10-13 13:11 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
Automated management of shared GPIOs only allows to request shared pins
when they're assigned to a real struct device. Firmware node, no-device
lookup currently isn't supported and can't be because we're using
platform lookup tables to point the callers to the shared proxy GPIO
controllers.
Rework the GPIO sharing by using dummy software nodes assigned to the
proxy devices and stored in the reference structures which allow us -
once the correct struct gpio_shared_ref is found - to find the correct
target GPIO device by its firmware node.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 126 +++++++++++++++++++-----------------------
drivers/gpio/gpiolib-shared.h | 15 +++--
drivers/gpio/gpiolib.c | 20 +------
3 files changed, 66 insertions(+), 95 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index a57a2dd5ca00eead40dc79198c94862b5a3d1ee9..c0b1713cdab171dfc1ac16393dde3dd8db12d8a8 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -35,11 +35,12 @@ struct gpio_shared_ref {
/* GPIO flags this consumer uses for the request. */
enum gpiod_flags flags;
char *con_id;
- /* Protects the auxiliary device struct and the lookup table. */
+ /* Protects the auxiliary device struct and fwnode. */
struct mutex lock;
struct lock_class_key lock_key;
struct auxiliary_device *proxy_dev;
- struct gpiod_lookup_table *lookup;
+ struct software_node proxy_swnode;
+ struct fwnode_handle *proxy_fwnode;
bool is_reset_gpio;
};
@@ -313,38 +314,53 @@ static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
struct gpio_shared_entry *entry,
struct gpio_shared_ref *ref)
{
+ struct software_node *swnode = &ref->proxy_swnode;
struct auxiliary_device *adev;
+ struct device *dev;
int ret, id;
adev = kzalloc_obj(*adev);
if (!adev)
return -ENOMEM;
+
id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
if (id < 0) {
kfree(adev);
return id;
}
- adev->id = id;
- adev->name = "proxy";
- adev->dev.parent = gdev->dev.parent;
- adev->dev.platform_data = entry;
- adev->dev.release = gpio_shared_proxy_dev_release;
-
- ret = auxiliary_device_init(adev);
+ ret = software_node_register(swnode);
if (ret) {
- ida_free(&gpio_shared_ida, id);
+ ida_free(&gpio_shared_ida, GFP_KERNEL);
kfree(adev);
return ret;
}
- ret = auxiliary_device_add(adev);
- if (ret) {
- auxiliary_device_uninit(adev);
- return ret;
+ ref->proxy_fwnode = software_node_fwnode(swnode);
+ if (!ref->proxy_fwnode) {
+ ida_free(&gpio_shared_ida, GFP_KERNEL);
+ kfree(adev);
+ goto err_swnode_unreg;
}
+ dev = &adev->dev;
+ adev->id = id;
+ adev->name = "proxy";
+ dev->parent = gdev->dev.parent;
+ dev->platform_data = entry;
+ dev->release = gpio_shared_proxy_dev_release;
+
+ device_set_node(dev, software_node_fwnode(swnode));
+
+ ret = auxiliary_device_init(adev);
+ if (ret)
+ goto err_swnode_unreg;
+
+ ret = auxiliary_device_add(adev);
+ if (ret)
+ goto err_dev_uninit;
+
pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n",
dev_name(&adev->dev), gpio_device_get_label(gdev));
@@ -353,6 +369,13 @@ static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
}
return 0;
+
+err_dev_uninit:
+ auxiliary_device_uninit(adev);
+err_swnode_unreg:
+ software_node_unregister(swnode);
+
+ return ret;
}
#if IS_ENABLED(CONFIG_RESET_GPIO)
@@ -457,23 +480,19 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
}
#endif /* CONFIG_RESET_GPIO */
-int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *fwnode,
- const char *con_id, unsigned long lflags)
+struct gpio_desc *
+gpio_shared_get_proxy(struct device *consumer, struct fwnode_handle *fwnode,
+ const char *con_id)
{
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
- const char *dev_id;
-
- if (!consumer)
- return -EOPNOTSUPP;
-
- dev_id = dev_name(consumer);
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
guard(mutex)(&ref->lock);
- if (!ref->fwnode && device_is_compatible(consumer, "reset-gpio")) {
+ if (!ref->fwnode && consumer &&
+ device_is_compatible(consumer, "reset-gpio")) {
if (!gpio_shared_dev_is_reset_gpio(consumer, entry, ref))
continue;
} else if (fwnode != ref->fwnode) {
@@ -484,49 +503,22 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
(con_id && ref->con_id && strcmp(con_id, ref->con_id) != 0))
continue;
- /* We've already done that on a previous request. */
- if (ref->lookup)
- return 0;
-
- char *key __free(kfree) =
- kasprintf(GFP_KERNEL,
- KBUILD_MODNAME ".proxy.%u",
- ref->proxy_dev->id);
- if (!key)
- return -ENOMEM;
-
- struct gpiod_lookup_table *lookup __free(kfree) =
- kzalloc_flex(*lookup, table, 2);
- if (!lookup)
- return -ENOMEM;
-
- pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n",
- dev_id, key, ref->con_id ?: "none");
-
- lookup->dev_id = kstrdup(dev_id, GFP_KERNEL);
- if (!lookup->dev_id)
- return -ENOMEM;
-
- lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0,
- ref->con_id, lflags);
-
- ref->lookup = no_free_ptr(lookup);
- gpiod_add_lookup_table(ref->lookup);
+ struct gpio_device *gdev __free(gpio_device_put) =
+ gpio_device_find_by_fwnode(ref->proxy_fwnode);
+ if (!gdev)
+ /*
+ * A matching entry is there but the associated
+ * controller has not been registered yet.
+ */
+ return ERR_PTR(-EPROBE_DEFER);
- return 0;
+ return gpio_device_get_desc(gdev, 0);
}
}
/* We warn here because this can only happen if the programmer borked. */
WARN_ON(1);
- return -ENOENT;
-}
-
-static void gpio_shared_remove_proxy_dev(struct gpio_shared_ref *ref)
-{
- auxiliary_device_delete(ref->proxy_dev);
- auxiliary_device_uninit(ref->proxy_dev);
- ref->proxy_dev = NULL;
+ return ERR_PTR(-ENOENT);
}
int gpiochip_setup_shared(struct gpio_chip *gc)
@@ -628,15 +620,11 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
gpiod_free_commit(&gdev->descs[entry->offset]);
list_for_each_entry(ref, &entry->refs, list) {
- if (ref->lookup) {
- gpiod_remove_lookup_table(ref->lookup);
- kfree(ref->lookup->table[0].key);
- kfree(ref->lookup->dev_id);
- kfree(ref->lookup);
- ref->lookup = NULL;
- }
-
- gpio_shared_remove_proxy_dev(ref);
+ auxiliary_device_delete(ref->proxy_dev);
+ auxiliary_device_uninit(ref->proxy_dev);
+ software_node_unregister(&ref->proxy_swnode);
+ ref->proxy_dev = NULL;
+ ref->proxy_fwnode = NULL;
}
}
}
diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
index 618756f6c6aafd087df267a4a32732ccc03af38e..27fd2a40c5cb77a19c258e3fbf429d3f6e7d4b32 100644
--- a/drivers/gpio/gpiolib-shared.h
+++ b/drivers/gpio/gpiolib-shared.h
@@ -14,9 +14,9 @@ struct fwnode_handle;
int gpiochip_setup_shared(struct gpio_chip *gc);
void gpio_device_teardown_shared(struct gpio_device *gdev);
-int gpio_shared_add_proxy_lookup(struct device *consumer,
- struct fwnode_handle *fwnode,
- const char *con_id, unsigned long lflags);
+struct gpio_desc *
+gpio_shared_get_proxy(struct device *consumer, struct fwnode_handle *fwnode,
+ const char *con_id);
#else
@@ -27,12 +27,11 @@ static inline int gpiochip_setup_shared(struct gpio_chip *gc)
static inline void gpio_device_teardown_shared(struct gpio_device *gdev) { }
-static inline int gpio_shared_add_proxy_lookup(struct device *consumer,
- struct fwnode_handle *fwnode,
- const char *con_id,
- unsigned long lflags)
+static inline struct gpio_desc *
+gpio_shared_get_proxy(struct device *consumer, struct fwnode_handle *fwnode,
+ const char *con_id)
{
- return 0;
+ return NULL;
}
#endif /* CONFIG_GPIO_SHARED */
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index f9173560812d00020f41a745f8a67e0a3a6bbf76..253da538aa98674aeaa488b3ed0317faab5e5a4b 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -4824,24 +4824,8 @@ struct gpio_desc *gpiod_find_and_request(struct device *consumer,
scoped_guard(srcu, &gpio_devices_srcu) {
desc = gpiod_fwnode_lookup(fwnode, consumer, con_id, idx,
&flags, &lookupflags);
- if (!IS_ERR_OR_NULL(desc) &&
- test_bit(GPIOD_FLAG_SHARED, &desc->flags)) {
- /*
- * We're dealing with a GPIO shared by multiple
- * consumers. This is the moment to add the machine
- * lookup table for the proxy device as previously
- * we only knew the consumer's fwnode.
- */
- ret = gpio_shared_add_proxy_lookup(consumer, fwnode,
- con_id, lookupflags);
- if (ret)
- return ERR_PTR(ret);
-
- /* Trigger platform lookup for shared GPIO proxy. */
- desc = ERR_PTR(-ENOENT);
- /* Trigger it even for fwnode-only gpiod_get(). */
- platform_lookup_allowed = true;
- }
+ if (!IS_ERR_OR_NULL(desc) && test_bit(GPIOD_FLAG_SHARED, &desc->flags))
+ desc = gpio_shared_get_proxy(consumer, fwnode, con_id);
if (gpiod_not_found(desc) && platform_lookup_allowed) {
/*
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup
2026-10-13 13:11 ` [PATCH v3 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
@ 2026-10-13 13:27 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-13 13:27 UTC (permalink / raw)
To: Bartosz Golaszewski; +Cc: linux-gpio
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] gpio: gpiolib-shared: invalid argument passed to ida_free() causing ID leak
- [High] gpio: proxy device and swnode leak on gpiochip_setup_shared() error
- [High] gpio: gpiolib-shared: invalid ida_free and missing error rollback
- [High] gpio: NULL fwnode in proxy lookup returns bogus device
- [High] gpio: shared: unlocked proxy_fwnode modification causes wrong GPIO lookup
- [Medium] gpio: gpiolib-shared: memory and ID leak when auxiliary_device_init() fails
- [Medium] gpio: gpiolib-shared: software_node_fwnode() failure returns 0 (success)
--
commit bf7ac17bfe223ebd18a727bb416b2d9a07ea42a7
Author: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
gpio: shared: convert to using software nodes for GPIO shared proxy lookup
This commit reworks the GPIO sharing mechanism to use dummy software nodes
assigned to proxy devices instead of platform lookup tables. This allows finding
the target GPIO device by its firmware node once the correct reference is found.
> diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
> --- a/drivers/gpio/gpiolib-shared.c
> +++ b/drivers/gpio/gpiolib-shared.c
[ ... ]
> @@ -313,38 +314,53 @@ static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
> struct gpio_shared_entry *entry,
> struct gpio_shared_ref *ref)
> {
> + struct software_node *swnode = &ref->proxy_swnode;
> struct auxiliary_device *adev;
> + struct device *dev;
> int ret, id;
>
> adev = kzalloc_obj(*adev);
> if (!adev)
> return -ENOMEM;
>
> +
> id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
> if (id < 0) {
> kfree(adev);
> return id;
> }
>
> - adev->id = id;
> - adev->name = "proxy";
> - adev->dev.parent = gdev->dev.parent;
> - adev->dev.platform_data = entry;
> - adev->dev.release = gpio_shared_proxy_dev_release;
> -
> - ret = auxiliary_device_init(adev);
> + ret = software_node_register(swnode);
> if (ret) {
> - ida_free(&gpio_shared_ida, id);
> + ida_free(&gpio_shared_ida, GFP_KERNEL);
[Severity: High]
Does this code leak the allocated IDA?
It appears ida_free() is being called with the GFP_KERNEL constant instead of
the allocated id variable.
> kfree(adev);
> return ret;
> }
>
> - ret = auxiliary_device_add(adev);
> - if (ret) {
> - auxiliary_device_uninit(adev);
> - return ret;
> + ref->proxy_fwnode = software_node_fwnode(swnode);
> + if (!ref->proxy_fwnode) {
> + ida_free(&gpio_shared_ida, GFP_KERNEL);
[Severity: High]
Does this code also leak the IDA by passing GFP_KERNEL instead of id?
> + kfree(adev);
> + goto err_swnode_unreg;
[Severity: Medium]
Since ret is 0 from the preceding successful software_node_register() call,
does this jump cause the function to return 0 (success) instead of an error
code while leaving ref->proxy_dev as NULL?
> }
>
> + dev = &adev->dev;
> + adev->id = id;
> + adev->name = "proxy";
> + dev->parent = gdev->dev.parent;
> + dev->platform_data = entry;
> + dev->release = gpio_shared_proxy_dev_release;
> +
> + device_set_node(dev, software_node_fwnode(swnode));
> +
> + ret = auxiliary_device_init(adev);
> + if (ret)
> + goto err_swnode_unreg;
[Severity: Medium]
If auxiliary_device_init() fails, does this code leak the allocated adev
memory and the IDA? The err_swnode_unreg label unregisters the software node
but does not free adev or call ida_free().
> +
> + ret = auxiliary_device_add(adev);
> + if (ret)
> + goto err_dev_uninit;
> +
> pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n",
> dev_name(&adev->dev), gpio_device_get_label(gdev));
>
> @@ -353,6 +369,13 @@ static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
> }
>
> return 0;
> +
> +err_dev_uninit:
> + auxiliary_device_uninit(adev);
> +err_swnode_unreg:
> + software_node_unregister(swnode);
> +
> + return ret;
> }
[Severity: High]
Does this new software node registration cause a permanent leak if
gpio_shared_make_proxy_dev() fails for a subsequent reference?
In gpiochip_setup_shared():
list_for_each_entry(ref, &entry->refs, list) {
ret = gpio_shared_make_proxy_dev(gdev, entry, ref);
if (ret) {
gpiod_free_commit(desc);
return ret;
}
}
If an error occurs midway, the function returns immediately without rolling
back the proxy devices and software nodes registered for previous references.
The caller then jumps to err_remove_irqchip, completely bypassing the teardown
path. This permanently leaks the successfully registered software nodes and
ensures that future attempts to probe this controller will fail with -EEXIST.
[ ... ]
> @@ -484,49 +503,22 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
> (con_id && ref->con_id && strcmp(con_id, ref->con_id) != 0))
> continue;
>
> - /* We've already done that on a previous request. */
> - if (ref->lookup)
> - return 0;
> -
> - char *key __free(kfree) =
> - kasprintf(GFP_KERNEL,
> - KBUILD_MODNAME ".proxy.%u",
> - ref->proxy_dev->id);
> - if (!key)
> - return -ENOMEM;
> -
> - struct gpiod_lookup_table *lookup __free(kfree) =
> - kzalloc_flex(*lookup, table, 2);
> - if (!lookup)
> - return -ENOMEM;
> -
> - pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n",
> - dev_id, key, ref->con_id ?: "none");
> -
> - lookup->dev_id = kstrdup(dev_id, GFP_KERNEL);
> - if (!lookup->dev_id)
> - return -ENOMEM;
> -
> - lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0,
> - ref->con_id, lflags);
> -
> - ref->lookup = no_free_ptr(lookup);
> - gpiod_add_lookup_table(ref->lookup);
> + struct gpio_device *gdev __free(gpio_device_put) =
> + gpio_device_find_by_fwnode(ref->proxy_fwnode);
[Severity: High]
Is it possible for a concurrent consumer to enter this lookup while
gpiochip_setup_shared() is still initializing the proxy devices?
In gpiochip_setup_shared(), the GPIOD_FLAG_SHARED bit is set before calling
gpio_shared_make_proxy_dev(). A concurrent consumer requesting a shared GPIO
might see the shared flag and call gpio_shared_get_proxy() while
ref->proxy_fwnode is still NULL.
If gpio_device_find_by_fwnode() is called with a NULL fwnode, it seems to
fallback to evaluating node && node->secondary == NULL in
gpio_chip_match_by_fwnode(). This could return the first GPIO device in the
system lacking a secondary fwnode, providing an unrelated GPIO descriptor to
the consumer.
> + if (!gdev)
> + /*
> + * A matching entry is there but the associated
> + * controller has not been registered yet.
> + */
> + return ERR_PTR(-EPROBE_DEFER);
>
> - return 0;
> + return gpio_device_get_desc(gdev, 0);
> }
> }
[ ... ]
> @@ -628,15 +620,11 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
> gpiod_free_commit(&gdev->descs[entry->offset]);
>
> list_for_each_entry(ref, &entry->refs, list) {
> - if (ref->lookup) {
> - gpiod_remove_lookup_table(ref->lookup);
> - kfree(ref->lookup->table[0].key);
> - kfree(ref->lookup->dev_id);
> - kfree(ref->lookup);
> - ref->lookup = NULL;
> - }
> -
> - gpio_shared_remove_proxy_dev(ref);
> + auxiliary_device_delete(ref->proxy_dev);
> + auxiliary_device_uninit(ref->proxy_dev);
> + software_node_unregister(&ref->proxy_swnode);
> + ref->proxy_dev = NULL;
> + ref->proxy_fwnode = NULL;
> }
> }
> }
[Severity: High]
Does modifying ref->proxy_fwnode here without holding ref->lock introduce a
data race?
A concurrent reader iterating the global gpio_shared_list in
gpio_shared_get_proxy() could read a NULL ref->proxy_fwnode if it encounters
a reference currently being torn down. As noted above, looking up a NULL
fwnode might return an entirely unrelated GPIO controller descriptor.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261013-gpio-shared-fwnode-lookup-v3-0-4553c1a7d80f@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref
2026-10-13 13:11 ` [PATCH v3 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
@ 2026-10-13 13:31 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-13 13:31 UTC (permalink / raw)
To: Bartosz Golaszewski; +Cc: linux-gpio
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] gpio: shared: race returns wrong GPIO desc on uninitialized proxy_fwnode
- [High] gpio: shared: teardown unconditionally dereferences NULL proxy_dev
- [High] gpio: shared: NULL pointer evaluation misidentifies virtual chips as proxies
- [Medium] gpio: shared: memory leak of dynamically allocated proxy devices on error path
--
commit 5669c21b32e82fcb8daf60473942426be156bbb3
Author: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
gpio: shared: detach the proxy device from struct gpio_shared_ref
This commit moves the auxiliary device struct out of the shared reference
structure and dynamically allocates its memory during proxy setup. It also
renames the field to proxy_dev to better reflect its purpose.
> diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
> index 8c9ac9d933049..a57a2dd5ca00e 100644
> --- a/drivers/gpio/gpiolib-shared.c
> +++ b/drivers/gpio/gpiolib-shared.c
[ ... ]
> @@ -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);
[Severity: High]
Does this code crash if ref->proxy_dev is NULL?
If a shared GPIO entry has 1 or fewer consumers, gpiochip_setup_shared() skips
creating the proxy device, leaving ref->proxy_dev as NULL. Alternatively, if a
GPIO controller probes before gpio_shared_init populates the list, proxy_dev
remains NULL. Later, when the controller is removed,
gpio_device_teardown_shared() unconditionally calls
gpio_shared_remove_proxy_dev().
Because dev is at offset 0 of struct auxiliary_device, &auxdev->dev evaluates
to NULL, and device_del(NULL) dereferences NULL->parent, causing a panic.
> + auxiliary_device_uninit(ref->proxy_dev);
> + ref->proxy_dev = NULL;
> }
>
> int gpiochip_setup_shared(struct gpio_chip *gc)
> @@ -527,7 +539,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 (gdev->dev.parent == &ref->proxy_dev->dev) {
[Severity: High]
Can this evaluate to NULL == NULL and incorrectly identify virtual controllers
as shared proxies?
Because ref->proxy_dev is initially NULL before setup, and dev is at offset 0,
&ref->proxy_dev->dev evaluates to NULL. If a virtual GPIO controller like
gpio-mockup or gpio-sim probes with no parent device, gdev->dev.parent is also
NULL. This early return would abort the actual shared setup.
> /*
> * This is a shared GPIO proxy. Mark its
> * descriptor as such and return here.
[ ... ]
> @@ -592,7 +604,7 @@ int gpiochip_setup_shared(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) {
> gpiod_free_commit(desc);
> return ret;
[Severity: Medium]
Does this leak memory and IDA identifiers if a subsequent consumer allocation
fails?
If gpio_shared_make_proxy_dev() fails, the function immediately returns
without rolling back the dynamically allocated proxy devices from earlier
iterations in the loop. Also, in the caller gpiochip_add_data_with_key(),
a failure here jumps directly to err_remove_irqchip, bypassing
err_teardown_shared and permanently leaking the auxiliary devices.
[Severity: Critical]
Does setting the shared flag earlier in this function expose an uninitialized
proxy to concurrent consumers?
Since the provider is already on the gpio_devices list, setting
GPIOD_FLAG_SHARED before creating the proxy device can allow a concurrent
gpiod_get() to enter gpio_shared_get_proxy().
Because the proxy and its fwnode are not yet initialized, the consumer will
read NULL. It then calls gpio_device_find_by_fwnode(NULL). In
gpio_chip_match_by_fwnode(), the condition:
return node && node->secondary == fwnode;
evaluates to node->secondary == NULL, incorrectly matching the first GPIO
controller in the system that has a primary fwnode but no secondary. This
could return an unrelated GPIO descriptor and cause cross-device hardware
corruption.
> }
> @@ -624,7 +636,7 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
> ref->lookup = NULL;
> }
>
> - gpio_shared_remove_adev(&ref->adev);
> + gpio_shared_remove_proxy_dev(ref);
> }
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261013-gpio-shared-fwnode-lookup-v3-0-4553c1a7d80f@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-13 13:31 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13 13:11 [PATCH v3 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 1/4] gpio: shared: fix use-after-free bug when adding shared proxies Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 2/4] gpio: shared: gracefully handle no-device lookup of shared GPIOs Bartosz Golaszewski
2026-10-13 13:11 ` [PATCH v3 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
2026-10-13 13:31 ` sashiko-bot
2026-10-13 13:11 ` [PATCH v3 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
2026-10-13 13:27 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox