of/irq: Fix device node refcount leakage in API of_irq_parse_one()
authorZijun Hu <quic_zijuhu@quicinc.com>
Sun, 9 Feb 2025 12:58:55 +0000 (20:58 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 25 Apr 2025 08:45:35 +0000 (10:45 +0200)
commit 0cb58d6c7b558a69957fabe159bfb184196e1e8d upstream.

of_irq_parse_one(@int_gen_dev, i, ...) will leak refcount of @i_th_phandle

int_gen_dev {
    ...
    interrupts-extended = ..., <&i_th_phandle ...>, ...;
    ...
};

Refcount of @i_th_phandle is increased by of_parse_phandle_with_args()
but is not decreased by API of_irq_parse_one() before return, so causes
refcount leakage.

Rework the refcounting to use __free() cleanup and simplify the code to
have a single call to of_irq_parse_raw().

Also add comments about refcount of node @out_irq->np got by the API.

Fixes: 79d9701559a9 ("of/irq: create interrupts-extended property")
Cc: stable@vger.kernel.org
Signed-off-by: Zijun Hu <quic_zijuhu@quicinc.com>
Link: https://lore.kernel.org/r/20250209-of_irq_fix-v2-2-93e3a2659aa7@quicinc.com
[robh: Use __free() to do puts]
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/of/irq.c

index e54f003c1aaa9163d3ec1fa83f515a2276cd82d6..1fd049d6a437711fc516aa55781533daa4dabe1f 100644 (file)
@@ -16,6 +16,7 @@
 
 #define pr_fmt(fmt)    "OF: " fmt
 
+#include <linux/cleanup.h>
 #include <linux/device.h>
 #include <linux/errno.h>
 #include <linux/list.h>
@@ -339,10 +340,12 @@ EXPORT_SYMBOL_GPL(of_irq_parse_raw);
  * This function resolves an interrupt for a node by walking the interrupt tree,
  * finding which interrupt controller node it is attached to, and returning the
  * interrupt specifier that can be used to retrieve a Linux IRQ number.
+ *
+ * Note: refcount of node @out_irq->np is increased by 1 on success.
  */
 int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
 {
-       struct device_node *p;
+       struct device_node __free(device_node) *p = NULL;
        const __be32 *addr;
        u32 intsize;
        int i, res, addr_len;
@@ -367,41 +370,33 @@ int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_ar
        /* Try the new-style interrupts-extended first */
        res = of_parse_phandle_with_args(device, "interrupts-extended",
                                        "#interrupt-cells", index, out_irq);
-       if (!res)
-               return of_irq_parse_raw(addr_buf, out_irq);
-
-       /* Look for the interrupt parent. */
-       p = of_irq_find_parent(device);
-       if (p == NULL)
-               return -EINVAL;
-
-       /* Get size of interrupt specifier */
-       if (of_property_read_u32(p, "#interrupt-cells", &intsize)) {
-               res = -EINVAL;
-               goto out;
-       }
-
-       pr_debug(" parent=%pOF, intsize=%d\n", p, intsize);
+       if (!res) {
+               p = out_irq->np;
+       } else {
+               /* Look for the interrupt parent. */
+               p = of_irq_find_parent(device);
+               /* Get size of interrupt specifier */
+               if (!p || of_property_read_u32(p, "#interrupt-cells", &intsize))
+                       return -EINVAL;
+
+               pr_debug(" parent=%pOF, intsize=%d\n", p, intsize);
+
+               /* Copy intspec into irq structure */
+               out_irq->np = p;
+               out_irq->args_count = intsize;
+               for (i = 0; i < intsize; i++) {
+                       res = of_property_read_u32_index(device, "interrupts",
+                                                       (index * intsize) + i,
+                                                       out_irq->args + i);
+                       if (res)
+                               return res;
+               }
 
-       /* Copy intspec into irq structure */
-       out_irq->np = p;
-       out_irq->args_count = intsize;
-       for (i = 0; i < intsize; i++) {
-               res = of_property_read_u32_index(device, "interrupts",
-                                                (index * intsize) + i,
-                                                out_irq->args + i);
-               if (res)
-                       goto out;
+               pr_debug(" intspec=%d\n", *out_irq->args);
        }
 
-       pr_debug(" intspec=%d\n", *out_irq->args);
-
-
        /* Check if there are any interrupt-map translations to process */
-       res = of_irq_parse_raw(addr_buf, out_irq);
- out:
-       of_node_put(p);
-       return res;
+       return of_irq_parse_raw(addr_buf, out_irq);
 }
 EXPORT_SYMBOL_GPL(of_irq_parse_one);