[PATCH RFC] irqchip/gic-v3-its: Make LPI free path infallible
"syzbot" <[email protected]> Wed, 29 Jul 2026 09:50:24 +0000 (UTC)
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
The ITS driver manages available LPI ranges using a linked list. When LPIs
are freed via free_lpi_range(), the driver allocates a new lpi_range node
to add the freed range back to this list. If this allocation fails (e.g.,
under memory pressure), free_lpi_range() returns -ENOMEM.
The caller, its_lpi_free(), has a void return type and cannot propagate the
error. It wraps the call in a WARN_ON(). When the allocation fails, the
warning is triggered, and the LPI range is permanently leaked because it is
never added back to the free list.
WARNING: drivers/irqchip/irq-gic-v3-its.c:2251 at its_lpi_free
drivers/irqchip/irq-gic-v3-its.c:2251 [inline]
WARNING: drivers/irqchip/irq-gic-v3-its.c:2251 at
its_msi_teardown+0x3a4/0x424 drivers/irqchip/irq-gic-v3-its.c:3644
Call trace:
its_lpi_free drivers/irqchip/irq-gic-v3-its.c:2251 [inline]
its_msi_teardown+0x3a4/0x424 drivers/irqchip/irq-gic-v3-its.c:3644
its_msi_teardown+0xa0/0xb8 drivers/irqchip/irq-gic-its-msi-parent.c:249
msi_remove_device_irq_domain+0x16c/0x27c kernel/irq/msi.c:1127
msi_device_data_release+0x38/0x9c kernel/irq/msi.c:293
Free paths must generally be infallible because the caller usually cannot
handle a failure. WARN_ON must not be used for conditions that can
legitimately happen, and pr_err should be used instead if necessary.
Fix this by decoupling the memory allocation from the free path and
shifting it to the allocation path (alloc_lpi_range()), which is allowed to
fail. Introduce a global cache (lpi_free_cache) for lpi_range nodes.
Pre-allocate a node in alloc_lpi_range() and add it to the cache. In
free_lpi_range(), simply pop a node from the cache. Since every successful
allocation adds a node to the cache, free_lpi_range() is guaranteed to find
at least one node.
Additionally, fix two existing memory leaks in the allocation error paths
to keep the cache perfectly balanced. First, in its_lpi_alloc(), if
bitmap_zalloc() fails, free the successfully allocated LPI range before
returning. Second, in its_create_device(), if col_map allocation fails but
lpi_map was successfully allocated, call its_lpi_free() instead of
bitmap_free() to correctly free both the bitmap and the underlying LPI
range.
Fixes: 880cb3cddd16 ("irqchip/gic-v3-its: Refactor LPI allocator")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=229d761b8a110e6de517
Link: https://syzkaller.appspot.com/ai_job?id=09d24d0b-29ec-4b31-b433-6367b7670bfc
To: <[email protected]>
To: "Marc Zyngier" <[email protected]>
To: "Thomas Gleixner" <[email protected]>
To: "Marc Zyngier" <[email protected]>
Cc: <[email protected]>
---
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index 6f5811aae..634b0e154 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -2097,6 +2097,7 @@ static struct irq_chip its_irq_chip = {
static DEFINE_MUTEX(lpi_range_lock);
static LIST_HEAD(lpi_range_list);
+static LIST_HEAD(lpi_free_cache);
struct lpi_range {
struct list_head entry;
@@ -2120,8 +2121,13 @@ static struct lpi_range *mk_lpi_range(u32 base, u32 span)
static int alloc_lpi_range(u32 nr_lpis, u32 *base)
{
struct lpi_range *range, *tmp;
+ struct lpi_range *new_node;
int err = -ENOSPC;
+ new_node = mk_lpi_range(0, 0);
+ if (!new_node)
+ return -ENOMEM;
+
mutex_lock(&lpi_range_lock);
list_for_each_entry_safe(range, tmp, &lpi_range_list, entry) {
@@ -2140,6 +2146,11 @@ static int alloc_lpi_range(u32 nr_lpis, u32 *base)
}
}
+ if (!err)
+ list_add(&new_node->entry, &lpi_free_cache);
+ else
+ kfree(new_node);
+
mutex_unlock(&lpi_range_lock);
pr_debug("ITS: alloc %u:%u\n", *base, nr_lpis);
@@ -2162,11 +2173,18 @@ static int free_lpi_range(u32 base, u32 nr_lpis)
{
struct lpi_range *new, *old;
- new = mk_lpi_range(base, nr_lpis);
- if (!new)
+ mutex_lock(&lpi_range_lock);
+
+ if (WARN_ON(list_empty(&lpi_free_cache))) {
+ mutex_unlock(&lpi_range_lock);
return -ENOMEM;
+ }
- mutex_lock(&lpi_range_lock);
+ new = list_first_entry(&lpi_free_cache, struct lpi_range, entry);
+ list_del(&new->entry);
+
+ new->base_id = base;
+ new->span = nr_lpis;
list_for_each_entry_reverse(old, &lpi_range_list, entry) {
if (old->base_id < base)
@@ -2195,6 +2213,7 @@ static int __init its_lpi_init(u32 id_bits)
{
u32 lpis = (1UL << id_bits) - 8192;
u32 numlpis;
+ struct lpi_range *range;
int err;
numlpis = 1UL << GICD_TYPER_NUM_LPIS(gic_rdists->gicd_typer);
@@ -2205,6 +2224,12 @@ static int __init its_lpi_init(u32 id_bits)
lpis);
}
+ range = mk_lpi_range(0, 0);
+ if (!range)
+ return -ENOMEM;
+
+ list_add(&range->entry, &lpi_free_cache);
+
/*
* Initializing the allocator is just the same as freeing the
* full range of LPIs.
@@ -2234,8 +2259,10 @@ static unsigned long *its_lpi_alloc(int nr_irqs, u32 *base, int *nr_ids)
goto out;
bitmap = bitmap_zalloc(nr_irqs, GFP_ATOMIC);
- if (!bitmap)
+ if (!bitmap) {
+ free_lpi_range(*base, nr_irqs);
goto out;
+ }
*nr_ids = nr_irqs;
@@ -2248,7 +2275,7 @@ static unsigned long *its_lpi_alloc(int nr_irqs, u32 *base, int *nr_ids)
static void its_lpi_free(unsigned long *bitmap, u32 base, u32 nr_ids)
{
- WARN_ON(free_lpi_range(base, nr_ids));
+ free_lpi_range(base, nr_ids);
bitmap_free(bitmap);
}
@@ -3510,7 +3537,8 @@ static struct its_device *its_create_device(struct its_node *its, u32 dev_id,
if (!dev || !itt || !col_map || (!lpi_map && alloc_lpis)) {
kfree(dev);
itt_free_pool(itt, sz);
- bitmap_free(lpi_map);
+ if (lpi_map)
+ its_lpi_free(lpi_map, lpi_base, nr_lpis);
kfree(col_map);
return NULL;
}
base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].