[PATCH 5/6] gpiolib: cdev: don't leak the IRQ name when the chip is removed

From: Adrian Ambrozewicz

Date: Mon Oct 12 2026 - 09:59:27 EST


The character device requests line IRQs with a copy of the consumer
label as the name and frees that copy with the name free_irq() returns.
Since commit ec8b6f55b981 ("gpio: free irqs that are still requested
when the chip is being removed"), gpiochip_remove() may free the IRQ
first and drops the name it gets back. The character device then does
not get it from its own free_irq() call, which since the previous patch
it does not even make, so the copy leaks:

unreferenced object 0xff1100000435b080 (size 8):
comm "gpio-hold", pid 108, jiffies 4294695819
hex dump (first 8 bytes):
72 65 70 72 6f 00 00 00 repro...
backtrace (crc dc8eac94):
__kmalloc_node_track_caller_noprof+0x50f/0x6a0
kstrdup+0x35/0x90
kstrdup_and_replace+0x19/0xe0
edge_detector_setup+0x5d8/0x940
linereq_create+0x9ec/0xce0
gpio_ioctl+0x49c/0xc80
__x64_sys_ioctl+0x134/0x1c0
do_syscall_64+0x109/0x5d0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Keep the name with the line, or with the v1 line event, and free it
there whoever freed the IRQ.

Reproduced with gpio-sim and a line requested with edge detection and
a consumer label, the chip being removed while the request is open:
kmemleak reports between 17 and 23 such objects after 30 cycles; with
this change, none after 100.

Fixes: ec8b6f55b981 ("gpio: free irqs that are still requested when the chip is being removed")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Adrian Ambrozewicz <aambrozewicz@xxxxxxxxxx>
---
drivers/gpio/gpiolib-cdev.c | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index 11d5b112886c..ae19b4c3e9d8 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -399,6 +399,7 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
* @desc: the GPIO descriptor for this line.
* @req: the corresponding line request
* @irq: the interrupt triggered in response to events on this GPIO
+ * @irq_label: the name @irq was requested with
* @edflags: the edge flags, GPIO_V2_LINE_FLAG_EDGE_RISING and/or
* GPIO_V2_LINE_FLAG_EDGE_FALLING, indicating the edge detection applied
* @timestamp_ns: cache for the timestamp storing it between hardirq and
@@ -422,6 +423,7 @@ struct line {
*/
struct linereq *req;
unsigned int irq;
+ char *irq_label;
/*
* The flags for the active edge detector configuration.
*
@@ -601,12 +603,15 @@ static inline void free_irq_label(const char *label)
/*
* gpiochip_remove() frees the interrupts still requested on the lines of the
* chip being removed, so once the chip is going away the IRQ may no longer be
- * ours to free.
+ * ours to free. The label is kept by the caller as free_irq() is then not the
+ * one returning it.
*/
-static void free_line_irq(unsigned int irq, void *dev_id)
+static void free_line_irq(unsigned int irq, void *dev_id, char **label)
{
if (irq_has_action(irq))
- free_irq_label(free_irq(irq, dev_id));
+ free_irq(irq, dev_id);
+ free_irq_label(*label);
+ *label = NULL;
}

#ifdef CONFIG_HTE
@@ -932,6 +937,7 @@ static int debounce_setup(struct line *line, unsigned int debounce_period_us)
return ret;
}
line->irq = irq;
+ line->irq_label = label;
} else {
ret = hte_edge_setup(line, GPIO_V2_LINE_FLAG_EDGE_BOTH);
if (ret)
@@ -975,7 +981,7 @@ static u32 gpio_v2_line_config_debounce_period(struct gpio_v2_line_config *lc,
static void edge_detector_stop(struct line *line)
{
if (line->irq) {
- free_line_irq(line->irq, line);
+ free_line_irq(line->irq, line, &line->irq_label);
line->irq = 0;
}

@@ -1078,6 +1084,7 @@ static int edge_detector_setup(struct line *line,
}

line->irq = irq;
+ line->irq_label = label;
return 0;
}

@@ -1772,6 +1779,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
* @desc: the GPIO descriptor held by this event
* @eflags: the event flags this line was requested with
* @irq: the interrupt that trigger in response to events on this GPIO
+ * @irq_label: the name @irq was requested with
* @wait: wait queue that handles blocking reads of events
* @device_unregistered_nb: notifier block for receiving gdev unregister events
* @events: KFIFO for the GPIO events
@@ -1785,6 +1793,7 @@ struct lineevent_state {
struct gpio_desc *desc;
u32 eflags;
int irq;
+ char *irq_label;
wait_queue_head_t wait;
struct notifier_block device_unregistered_nb;
DECLARE_KFIFO(events, struct gpioevent_data, 16);
@@ -1821,7 +1830,7 @@ static int lineevent_unregistered_notify(struct notifier_block *nb,
device_unregistered_nb);

if (le->irq) {
- free_line_irq(le->irq, le);
+ free_line_irq(le->irq, le, &le->irq_label);
le->irq = 0;
}

@@ -1908,7 +1917,7 @@ static void lineevent_free(struct lineevent_state *le)
blocking_notifier_chain_unregister(&le->gdev->device_notifier,
&le->device_unregistered_nb);
if (le->irq)
- free_line_irq(le->irq, le);
+ free_line_irq(le->irq, le, &le->irq_label);
if (le->desc)
gpiod_free(le->desc);
kfree(le->label);
@@ -2147,6 +2156,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
}

le->irq = irq;
+ le->irq_label = label;

FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC,
anon_inode_getfile("gpio-event", &lineevent_fileops, le,
--
2.43.0