CVE-2026-46141
Description
In the Linux kernel, the following vulnerability has been resolved:
powerpc/xive: fix kmemleak caused by incorrect chip_data lookup
The kmemleak reports the following memory leak:
Unreferenced object 0xc0000002a7fbc640 (size 64): comm "kworker/8:1", pid 540, jiffies 4294937872 hex dump (first 32 bytes): 01 00 00 00 00 00 00 00 00 00 09 04 00 04 00 00 ................ 00 00 a7 81 00 00 0a c0 00 00 08 04 00 04 00 00 ................ backtrace (crc 177d48f6): __kmalloc_cache_noprof+0x520/0x730 xive_irq_alloc_data.constprop.0+0x40/0xe0 xive_irq_domain_alloc+0xd0/0x1b0 irq_domain_alloc_irqs_parent+0x44/0x6c pseries_irq_domain_alloc+0x1cc/0x354 irq_domain_alloc_irqs_parent+0x44/0x6c msi_domain_alloc+0xb0/0x220 irq_domain_alloc_irqs_locked+0x138/0x4d0 __irq_domain_alloc_irqs+0x8c/0xfc __msi_domain_alloc_irqs+0x214/0x4d8 msi_domain_alloc_irqs_all_locked+0x70/0xf8 pci_msi_setup_msi_irqs+0x60/0x78 __pci_enable_msix_range+0x54c/0x98c pci_alloc_irq_vectors_affinity+0x16c/0x1d4 nvme_pci_enable+0xac/0x9c0 [nvme] nvme_probe+0x340/0x764 [nvme]
This occurs when allocating MSI-X vectors for an NVMe device. During allocation the XIVE code creates a struct xive_irq_data and stores it in irq_data->chip_data.
When the MSI-X irqdomain is later freed, xive_irq_free_data() is responsible for retrieving this structure and freeing it. However, after commit cc0cc23babc9 ("powerpc/xive: Untangle xive from child interrupt controller drivers"), xive_irq_free_data() retrieves the chip_data using irq_get_chip_data(), which looks up the data through the child domain.
This is incorrect because the XIVE-specific irq data is associated with the XIVE (parent) domain. As a result the lookup fails and the allocated struct xive_irq_data is never freed, leading to the kmemleak report shown above.
Fix this by retrieving the irq_data from the correct domain using irq_domain_get_irq_data() and then accessing the chip_data via irq_data_get_irq_chip_data().
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A memory leak in the Linux kernel's powerpc/xive interrupt controller occurs when freeing MSI-X vectors due to incorrect chip_data domain lookup, potentially leading to denial of service.
Vulnerability
In the Linux kernel's powerpc/xive interrupt controller, a memory leak was introduced by commit cc0cc23babc9 ("powerpc/xive: Untangle xive from child interrupt controller drivers"). The function xive_irq_free_data() retrieves the struct xive_irq_data using irq_get_chip_data(), which looks up the data through the child domain. However, the XIVE-specific irq data is stored in the parent domain's chip_data. As a result, the lookup fails and the allocated structure is never freed when MSI-X vectors are deallocated. This affects kernel versions containing the problematic commit up to the fix.
Exploitation
The bug is triggered during normal operation when the kernel allocates and then frees MSI-X vectors for an NVMe device (or any device using MSI-X). An attacker with the ability to repeatedly trigger device probe and removal (e.g., by hotplugging NVMe devices) could cause the kernel to leak memory. No special privileges are required beyond the ability to trigger device enumeration.
Impact
Each leaked struct xive_irq_data (64 bytes) accumulates over time, leading to kernel memory exhaustion. This can result in system instability, denial of service, or a system crash. There is no privilege escalation or data corruption.
Mitigation
The fix is commit 2546fb8c9acc8c7512ed4339ce2a982cb7407065 [1] in the Linux kernel stable tree, which corrects the lookup by using irq_domain_get_irq_data() on the XIVE domain. Users should update to a kernel version containing this commit. No workaround is available other than avoiding the use of MSI-X on affected PowerPC systems, which is impractical.
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
62546fb8c9accpowerpc/xive: fix kmemleak caused by incorrect chip_data lookup
1 file changed · +11 −6
arch/powerpc/sysdev/xive/common.c+11 −6 modifieddiff --git a/arch/powerpc/sysdev/xive/common.c b/arch/powerpc/sysdev/xive/common.c index 8d0123b0ae8417..6bc373cfef8c64 100644 --- a/arch/powerpc/sysdev/xive/common.c +++ b/arch/powerpc/sysdev/xive/common.c @@ -1038,13 +1038,19 @@ static struct xive_irq_data *xive_irq_alloc_data(unsigned int virq, irq_hw_numbe return xd; } -static void xive_irq_free_data(unsigned int virq) +static void xive_irq_free_data(struct irq_domain *domain, unsigned int virq) { - struct xive_irq_data *xd = irq_get_chip_data(virq); + struct xive_irq_data *xd; + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + + if (!data) + return; + xd = irq_data_get_irq_chip_data(data); if (!xd) return; - irq_set_chip_data(virq, NULL); + + irq_domain_reset_irq_data(data); xive_cleanup_irq_data(xd); kfree(xd); } @@ -1304,7 +1310,7 @@ static int xive_irq_domain_map(struct irq_domain *h, unsigned int virq, static void xive_irq_domain_unmap(struct irq_domain *d, unsigned int virq) { - xive_irq_free_data(virq); + xive_irq_free_data(d, virq); } static int xive_irq_domain_xlate(struct irq_domain *h, struct device_node *ct, @@ -1442,7 +1448,7 @@ static void xive_irq_domain_free(struct irq_domain *domain, pr_debug("%s %d #%d\n", __func__, virq, nr_irqs); for (i = 0; i < nr_irqs; i++) - xive_irq_free_data(virq + i); + xive_irq_free_data(domain, virq + i); } #endif -- cgit 1.3-korg
e66ed135cdf2powerpc/xive: fix kmemleak caused by incorrect chip_data lookup
1 file changed · +11 −6
arch/powerpc/sysdev/xive/common.c+11 −6 modifieddiff --git a/arch/powerpc/sysdev/xive/common.c b/arch/powerpc/sysdev/xive/common.c index e1a4f8a97393f5..6b1b7541ca311e 100644 --- a/arch/powerpc/sysdev/xive/common.c +++ b/arch/powerpc/sysdev/xive/common.c @@ -1038,13 +1038,19 @@ static struct xive_irq_data *xive_irq_alloc_data(unsigned int virq, irq_hw_numbe return xd; } -static void xive_irq_free_data(unsigned int virq) +static void xive_irq_free_data(struct irq_domain *domain, unsigned int virq) { - struct xive_irq_data *xd = irq_get_chip_data(virq); + struct xive_irq_data *xd; + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + + if (!data) + return; + xd = irq_data_get_irq_chip_data(data); if (!xd) return; - irq_set_chip_data(virq, NULL); + + irq_domain_reset_irq_data(data); xive_cleanup_irq_data(xd); kfree(xd); } @@ -1305,7 +1311,7 @@ static int xive_irq_domain_map(struct irq_domain *h, unsigned int virq, static void xive_irq_domain_unmap(struct irq_domain *d, unsigned int virq) { - xive_irq_free_data(virq); + xive_irq_free_data(d, virq); } static int xive_irq_domain_xlate(struct irq_domain *h, struct device_node *ct, @@ -1443,7 +1449,7 @@ static void xive_irq_domain_free(struct irq_domain *domain, pr_debug("%s %d #%d\n", __func__, virq, nr_irqs); for (i = 0; i < nr_irqs; i++) - xive_irq_free_data(virq + i); + xive_irq_free_data(domain, virq + i); } #endif -- cgit 1.3-korg
6771c54728c2powerpc/xive: fix kmemleak caused by incorrect chip_data lookup
1 file changed · +11 −6
arch/powerpc/sysdev/xive/common.c+11 −6 modifieddiff --git a/arch/powerpc/sysdev/xive/common.c b/arch/powerpc/sysdev/xive/common.c index e1a4f8a97393f5..6b1b7541ca311e 100644 --- a/arch/powerpc/sysdev/xive/common.c +++ b/arch/powerpc/sysdev/xive/common.c @@ -1038,13 +1038,19 @@ static struct xive_irq_data *xive_irq_alloc_data(unsigned int virq, irq_hw_numbe return xd; } -static void xive_irq_free_data(unsigned int virq) +static void xive_irq_free_data(struct irq_domain *domain, unsigned int virq) { - struct xive_irq_data *xd = irq_get_chip_data(virq); + struct xive_irq_data *xd; + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + + if (!data) + return; + xd = irq_data_get_irq_chip_data(data); if (!xd) return; - irq_set_chip_data(virq, NULL); + + irq_domain_reset_irq_data(data); xive_cleanup_irq_data(xd); kfree(xd); } @@ -1305,7 +1311,7 @@ static int xive_irq_domain_map(struct irq_domain *h, unsigned int virq, static void xive_irq_domain_unmap(struct irq_domain *d, unsigned int virq) { - xive_irq_free_data(virq); + xive_irq_free_data(d, virq); } static int xive_irq_domain_xlate(struct irq_domain *h, struct device_node *ct, @@ -1443,7 +1449,7 @@ static void xive_irq_domain_free(struct irq_domain *domain, pr_debug("%s %d #%d\n", __func__, virq, nr_irqs); for (i = 0; i < nr_irqs; i++) - xive_irq_free_data(virq + i); + xive_irq_free_data(domain, virq + i); } #endif -- cgit 1.3-korg
e66ed135cdf2powerpc/xive: fix kmemleak caused by incorrect chip_data lookup
1 file changed · +11 −6
arch/powerpc/sysdev/xive/common.c+11 −6 modifieddiff --git a/arch/powerpc/sysdev/xive/common.c b/arch/powerpc/sysdev/xive/common.c index e1a4f8a97393f5..6b1b7541ca311e 100644 --- a/arch/powerpc/sysdev/xive/common.c +++ b/arch/powerpc/sysdev/xive/common.c @@ -1038,13 +1038,19 @@ static struct xive_irq_data *xive_irq_alloc_data(unsigned int virq, irq_hw_numbe return xd; } -static void xive_irq_free_data(unsigned int virq) +static void xive_irq_free_data(struct irq_domain *domain, unsigned int virq) { - struct xive_irq_data *xd = irq_get_chip_data(virq); + struct xive_irq_data *xd; + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + + if (!data) + return; + xd = irq_data_get_irq_chip_data(data); if (!xd) return; - irq_set_chip_data(virq, NULL); + + irq_domain_reset_irq_data(data); xive_cleanup_irq_data(xd); kfree(xd); } @@ -1305,7 +1311,7 @@ static int xive_irq_domain_map(struct irq_domain *h, unsigned int virq, static void xive_irq_domain_unmap(struct irq_domain *d, unsigned int virq) { - xive_irq_free_data(virq); + xive_irq_free_data(d, virq); } static int xive_irq_domain_xlate(struct irq_domain *h, struct device_node *ct, @@ -1443,7 +1449,7 @@ static void xive_irq_domain_free(struct irq_domain *domain, pr_debug("%s %d #%d\n", __func__, virq, nr_irqs); for (i = 0; i < nr_irqs; i++) - xive_irq_free_data(virq + i); + xive_irq_free_data(domain, virq + i); } #endif -- cgit 1.3-korg
2546fb8c9accpowerpc/xive: fix kmemleak caused by incorrect chip_data lookup
1 file changed · +11 −6
arch/powerpc/sysdev/xive/common.c+11 −6 modifieddiff --git a/arch/powerpc/sysdev/xive/common.c b/arch/powerpc/sysdev/xive/common.c index 8d0123b0ae8417..6bc373cfef8c64 100644 --- a/arch/powerpc/sysdev/xive/common.c +++ b/arch/powerpc/sysdev/xive/common.c @@ -1038,13 +1038,19 @@ static struct xive_irq_data *xive_irq_alloc_data(unsigned int virq, irq_hw_numbe return xd; } -static void xive_irq_free_data(unsigned int virq) +static void xive_irq_free_data(struct irq_domain *domain, unsigned int virq) { - struct xive_irq_data *xd = irq_get_chip_data(virq); + struct xive_irq_data *xd; + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + + if (!data) + return; + xd = irq_data_get_irq_chip_data(data); if (!xd) return; - irq_set_chip_data(virq, NULL); + + irq_domain_reset_irq_data(data); xive_cleanup_irq_data(xd); kfree(xd); } @@ -1304,7 +1310,7 @@ static int xive_irq_domain_map(struct irq_domain *h, unsigned int virq, static void xive_irq_domain_unmap(struct irq_domain *d, unsigned int virq) { - xive_irq_free_data(virq); + xive_irq_free_data(d, virq); } static int xive_irq_domain_xlate(struct irq_domain *h, struct device_node *ct, @@ -1442,7 +1448,7 @@ static void xive_irq_domain_free(struct irq_domain *domain, pr_debug("%s %d #%d\n", __func__, virq, nr_irqs); for (i = 0; i < nr_irqs; i++) - xive_irq_free_data(virq + i); + xive_irq_free_data(domain, virq + i); } #endif -- cgit 1.3-korg
6771c54728c2powerpc/xive: fix kmemleak caused by incorrect chip_data lookup
1 file changed · +11 −6
arch/powerpc/sysdev/xive/common.c+11 −6 modifieddiff --git a/arch/powerpc/sysdev/xive/common.c b/arch/powerpc/sysdev/xive/common.c index e1a4f8a97393f5..6b1b7541ca311e 100644 --- a/arch/powerpc/sysdev/xive/common.c +++ b/arch/powerpc/sysdev/xive/common.c @@ -1038,13 +1038,19 @@ static struct xive_irq_data *xive_irq_alloc_data(unsigned int virq, irq_hw_numbe return xd; } -static void xive_irq_free_data(unsigned int virq) +static void xive_irq_free_data(struct irq_domain *domain, unsigned int virq) { - struct xive_irq_data *xd = irq_get_chip_data(virq); + struct xive_irq_data *xd; + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + + if (!data) + return; + xd = irq_data_get_irq_chip_data(data); if (!xd) return; - irq_set_chip_data(virq, NULL); + + irq_domain_reset_irq_data(data); xive_cleanup_irq_data(xd); kfree(xd); } @@ -1305,7 +1311,7 @@ static int xive_irq_domain_map(struct irq_domain *h, unsigned int virq, static void xive_irq_domain_unmap(struct irq_domain *d, unsigned int virq) { - xive_irq_free_data(virq); + xive_irq_free_data(d, virq); } static int xive_irq_domain_xlate(struct irq_domain *h, struct device_node *ct, @@ -1443,7 +1449,7 @@ static void xive_irq_domain_free(struct irq_domain *domain, pr_debug("%s %d #%d\n", __func__, virq, nr_irqs); for (i = 0; i < nr_irqs; i++) - xive_irq_free_data(virq + i); + xive_irq_free_data(domain, virq + i); } #endif -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Incorrect domain lookup in xive_irq_free_data() — after commit cc0cc23babc9, irq_get_chip_data() queries the child domain instead of the XIVE parent domain, so the allocated struct xive_irq_data is never found and never freed."
Attack vector
An attacker with local access triggers the bug by causing MSI-X vector allocation and deallocation for a device (e.g., an NVMe drive). During allocation, xive_irq_alloc_data() stores a struct xive_irq_data in the XIVE (parent) domain's chip_data. When the MSI-X irqdomain is later freed, xive_irq_free_data() calls irq_get_chip_data(), which after commit cc0cc23babc9 looks up chip_data through the child domain rather than the XIVE domain, so the lookup returns NULL and the structure is leaked. No special privileges beyond the ability to trigger device probe/remove are required.
Affected code
The bug is in arch/powerpc/sysdev/xive/common.c in the function xive_irq_free_data(). The original code used irq_get_chip_data(virq) which, after commit cc0cc23babc9, looked up chip_data through the child domain instead of the XIVE parent domain. The callers xive_irq_domain_unmap() and xive_irq_domain_free() also needed updating to pass the domain pointer.
What the fix does
The patch changes xive_irq_free_data() to accept an explicit struct irq_domain pointer and uses irq_domain_get_irq_data(domain, virq) followed by irq_data_get_irq_chip_data() to retrieve the chip_data from the correct XIVE domain [patch_id=2898345]. It also replaces the old irq_set_chip_data(virq, NULL) with irq_domain_reset_irq_data(data) to properly clear the domain-level irq_data. All callers (xive_irq_domain_unmap and xive_irq_domain_free) now pass the domain pointer they already hold, ensuring the lookup always targets the XIVE parent domain.
Preconditions
- configThe system must use the XIVE interrupt controller (PowerPC).
- inputA device (e.g., NVMe) must allocate and then free MSI-X vectors, triggering the domain free path.
- configThe kernel must include commit cc0cc23babc9 ('powerpc/xive: Untangle xive from child interrupt controller drivers').
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.