* [PATCH v4 0/4] gpio: shared: replace platform lookup with dummy software nodes
@ 2026-10-14 9:38 Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 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-14 9:38 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 v4:
- Fix arguments passed to ida_free() in error path
- Fix memory leak in error path
- Link to v3: https://patch.msgid.link/20261013-gpio-shared-fwnode-lookup-v3-0-4553c1a7d80f@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 | 159 ++++++++++++++++++++++--------------------
drivers/gpio/gpiolib-shared.h | 15 ++--
drivers/gpio/gpiolib.c | 20 +-----
3 files changed, 94 insertions(+), 100 deletions(-)
---
base-commit: 1fffefb00cc0b0da1f2ae24e3d33364530ea6515
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 v4 1/4] gpio: shared: fix use-after-free bug when adding shared proxies
2026-10-14 9:38 [PATCH v4 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
@ 2026-10-14 9:38 ` Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 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-14 9:38 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 v4 2/4] gpio: shared: gracefully handle no-device lookup of shared GPIOs
2026-10-14 9:38 [PATCH v4 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 1/4] gpio: shared: fix use-after-free bug when adding shared proxies Bartosz Golaszewski
@ 2026-10-14 9:38 ` Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 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-14 9:38 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 v4 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref
2026-10-14 9:38 [PATCH v4 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 1/4] gpio: shared: fix use-after-free bug when adding shared proxies Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 2/4] gpio: shared: gracefully handle no-device lookup of shared GPIOs Bartosz Golaszewski
@ 2026-10-14 9:38 ` Bartosz Golaszewski
2026-10-14 10:01 ` sashiko-bot
2026-10-14 9:38 ` [PATCH v4 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-14 9:38 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 v4 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup
2026-10-14 9:38 [PATCH v4 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
` (2 preceding siblings ...)
2026-10-14 9:38 ` [PATCH v4 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
@ 2026-10-14 9:38 ` Bartosz Golaszewski
2026-10-14 9:51 ` sashiko-bot
3 siblings, 1 reply; 7+ messages in thread
From: Bartosz Golaszewski @ 2026-10-14 9:38 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 | 121 +++++++++++++++++++-----------------------
drivers/gpio/gpiolib-shared.h | 15 +++---
drivers/gpio/gpiolib.c | 20 +------
3 files changed, 65 insertions(+), 91 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index a57a2dd5ca00eead40dc79198c94862b5a3d1ee9..16c0a9d491167c08dd011c7eb05ebfc68a6fc9fb 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,37 +314,55 @@ 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;
}
+ ret = software_node_register(swnode);
+ if (ret) {
+ ida_free(&gpio_shared_ida, id);
+ kfree(adev);
+ return ret;
+ }
+
+ ref->proxy_fwnode = software_node_fwnode(swnode);
+ if (!ref->proxy_fwnode) {
+ ida_free(&gpio_shared_ida, id);
+ kfree(adev);
+ goto err_swnode_unreg;
+ }
+
+ dev = &adev->dev;
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;
+ 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) {
ida_free(&gpio_shared_ida, id);
kfree(adev);
- return ret;
+ goto err_swnode_unreg;
}
ret = auxiliary_device_add(adev);
- if (ret) {
- auxiliary_device_uninit(adev);
- return ret;
- }
+ 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 +372,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 +483,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 +506,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 +623,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 v4 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup
2026-10-14 9:38 ` [PATCH v4 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
@ 2026-10-14 9:51 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-14 9:51 UTC (permalink / raw)
To: Bartosz Golaszewski; +Cc: linux-gpio
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] gpio: use-after-free of dangling proxy_fwnode on proxy creation failure
- [High] gpio: missing NULL check in gpio_device_teardown_shared causes panic
- [High] gpio: lockless proxy_fwnode teardown causes data race and incorrect chip lookup
- [Medium] gpio: missing proxy device cleanup on error causes permanent -EEXIST probe failure
- [Medium] gpio: missing error code in gpio_shared_make_proxy_dev causes silent failure
- [Medium] gpio: lockless proxy_fwnode initialization causes data race in proxy lookup
--
commit 5c7699dca403f7ae958b2532cf7a05f31e80a27e
Author: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
gpio: shared: convert to using software nodes for GPIO shared proxy lookup
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.
This commit converts the GPIO shared proxy lookup mechanism to use
software nodes instead of platform lookup tables. It assigns dummy
software nodes to proxy devices and stores them in reference structures.
> diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
> index a57a2dd5ca00e..16c0a9d491167 100644
> --- a/drivers/gpio/gpiolib-shared.c
> +++ b/drivers/gpio/gpiolib-shared.c
> @@ -313,37 +314,55 @@ 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;
> }
>
> + ret = software_node_register(swnode);
> + if (ret) {
> + ida_free(&gpio_shared_ida, id);
> + kfree(adev);
> + return ret;
> + }
> +
> + ref->proxy_fwnode = software_node_fwnode(swnode);
[Severity: Medium]
Is ref->proxy_fwnode protected by a lock here?
If a concurrent consumer calls gpiod_get() and invokes
gpio_shared_get_proxy(), it will acquire ref->lock to safely read
ref->proxy_fwnode. Because the writer ignores the lock, the reader may
observe a partially initialized or stale pointer, leading to a race condition
during fwnode lookup.
> + if (!ref->proxy_fwnode) {
> + ida_free(&gpio_shared_ida, id);
> + kfree(adev);
> + goto err_swnode_unreg;
[Severity: Medium]
Will this silently return 0 (success) because ret is not set to a negative
error code before jumping to err_swnode_unreg?
Because ret is 0 from the previous successful call to
software_node_register(), this masks the initialization failure, leaving
the proxy device uninitialized but tricking the caller into proceeding as
if it succeeded.
[ ... ]
> ret = auxiliary_device_init(adev);
> if (ret) {
> ida_free(&gpio_shared_ida, id);
> kfree(adev);
> - return ret;
> + goto err_swnode_unreg;
> }
>
> ret = auxiliary_device_add(adev);
> - if (ret) {
> - auxiliary_device_uninit(adev);
> - return ret;
> - }
> + 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 +372,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 error path leave ref->proxy_fwnode dangling?
If auxiliary_device_init() or auxiliary_device_add() fails, the code jumps
to err_swnode_unreg, which unregisters and frees the swnode. However,
ref->proxy_fwnode is not reset to NULL.
Since ref is part of the global gpio_shared_list, a concurrent or
subsequent consumer calling gpiod_get() will trigger
gpio_shared_get_proxy(), which will dereference the freed proxy_fwnode
pointer, leading to a Use-After-Free.
[Severity: Medium]
Can returning an error here cause a permanent probe failure for the controller?
In gpiochip_setup_shared(), if gpio_shared_make_proxy_dev() fails for one
entry, it immediately returns the error without cleaning up the proxy
devices and software nodes successfully created in previous iterations:
ret = gpio_shared_make_proxy_dev(gdev, entry, ref);
if (ret) {
gpiod_free_commit(desc);
return ret;
}
The caller (gpiochip_add_data_with_key()) will then bypass
gpio_device_teardown_shared(). The software_node remains registered, and
when the GPIO controller is reprobed (e.g. after -EPROBE_DEFER),
software_node_register() will be called again with the exact same pointer,
returning -EEXIST.
> }
>
> #if IS_ENABLED(CONFIG_RESET_GPIO)
[ ... ]
> @@ -628,15 +623,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);
[Severity: High]
Might ref->proxy_dev be NULL here?
In gpiochip_setup_shared(), if an entry has only 1 consumer
(list_count_nodes <= 1), proxy creation is skipped, leaving ref->proxy_dev
as NULL. Additionally, silent initialization failures also leave it NULL.
Passing NULL triggers an immediate kernel panic due to
device_del(&NULL->dev) and mutex_destroy(&NULL->sysfs.lock).
> + software_node_unregister(&ref->proxy_swnode);
> + ref->proxy_dev = NULL;
> + ref->proxy_fwnode = NULL;
[Severity: High]
Could modifying ref->proxy_fwnode outside of ref->lock cause a data race?
Concurrently, a consumer calling gpio_shared_get_proxy() acquires
ref->lock to safely read ref->proxy_fwnode. Because this teardown path
modifies it locklessly, a data race occurs.
If gpio_shared_get_proxy() reads the pointer as NULL during the race, it
calls gpio_device_find_by_fwnode(NULL):
struct gpio_device *gdev __free(gpio_device_put) =
gpio_device_find_by_fwnode(ref->proxy_fwnode);
The matching function gpio_chip_match_by_fwnode() incorrectly returns true
for NULL if a device has no secondary fwnode. As a result, the consumer
acquires a descriptor from a completely unrelated GPIO chip instead of
failing safely.
> }
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-gpio-shared-fwnode-lookup-v4-0-2253ad645fd6@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref
2026-10-14 9:38 ` [PATCH v4 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
@ 2026-10-14 10:01 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-14 10:01 UTC (permalink / raw)
To: Bartosz Golaszewski; +Cc: linux-gpio
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] gpiolib: logic bug and UB falsely matching parentless chips as shared proxies
- [High] gpio: NULL pointer dereference in teardown of early-probed shared GPIOs
- [High] gpio: proxy devices and descriptors leak on setup error path
- [Medium] gpiolib: memory and IDA leak on shared proxy setup failure
--
commit f0939e1241bcddbe2c589891db8c55a92fef6474
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 allocates its memory separately on proxy setup to prevent
memory corruption if references outlive the parent's teardown.
> 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);
> + auxiliary_device_uninit(ref->proxy_dev);
> + ref->proxy_dev = NULL;
> }
[Severity: High]
If a GPIO controller containing shared GPIOs is probed early, its proxy
devices might never be created, leaving ref->proxy_dev as NULL.
If the controller is later removed, gpio_device_teardown_shared() will call
gpio_shared_remove_proxy_dev(), which unconditionally passes this NULL
pointer to auxiliary_device_delete() and auxiliary_device_uninit().
Will this result in a kernel panic when the driver core attempts to
dereference the NULL proxy_dev?
>
> 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]
Before a proxy is instantiated, ref->proxy_dev is NULL, meaning
&ref->proxy_dev->dev will evaluate to NULL.
If a newly registered GPIO controller has no parent device (such as virtual
controllers like gpio-mockup or gpio-sim), gdev->dev.parent is also NULL.
Does this cause the condition to falsely evaluate to true (NULL == NULL)
and erroneously identify generic controllers as shared proxies, breaking
their 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: High]
If gpio_shared_make_proxy_dev() fails and returns an error, the function
returns immediately without unwinding any proxy devices that were
successfully created during previous iterations of the loop.
Additionally, in the caller gpiochip_add_data_with_key(), the error path
jumps to err_remove_irqchip, bypassing err_teardown_shared.
Are the successfully registered auxiliary devices, their kzalloc'd memory,
allocated IDA slots, and committed GPIO descriptors permanently leaked on
this error path?
> }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-gpio-shared-fwnode-lookup-v4-0-2253ad645fd6@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-14 10:01 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 9:38 [PATCH v4 0/4] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 1/4] gpio: shared: fix use-after-free bug when adding shared proxies Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 2/4] gpio: shared: gracefully handle no-device lookup of shared GPIOs Bartosz Golaszewski
2026-10-14 9:38 ` [PATCH v4 3/4] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
2026-10-14 10:01 ` sashiko-bot
2026-10-14 9:38 ` [PATCH v4 4/4] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
2026-10-14 9:51 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.