[PATCH] RISC-V: KVM: Fix use-after-free in kvm_riscv_aia_aplic_has_attr()

From: Jiakai Xu

Date: Tue Feb 24 2026 - 05:47:31 EST


Fuzzer reports a KASAN use-after-free bug triggered by a race
between KVM_HAS_DEVICE_ATTR and KVM_SET_DEVICE_ATTR ioctls on the AIA
device. The root cause is that aia_has_attr() invokes
kvm_riscv_aia_aplic_has_attr() without holding dev->kvm->lock, while
a concurrent aia_set_attr() may call aia_init() under that lock. When
aia_init() fails after kvm_riscv_aia_aplic_init() has succeeded, it
calls kvm_riscv_aia_aplic_cleanup() in its fail_cleanup_imsics path,
which frees both aplic_state and aplic_state->irqs. The concurrent
has_attr path can then dereference the freed aplic->irqs in
aplic_read_pending():
irqd = &aplic->irqs[irq]; /* UAF here */

KASAN report:
BUG: KASAN: slab-use-after-free in aplic_read_pending
arch/riscv/kvm/aia_aplic.c:119 [inline]
BUG: KASAN: slab-use-after-free in aplic_read_pending_word
arch/riscv/kvm/aia_aplic.c:351 [inline]
BUG: KASAN: slab-use-after-free in aplic_mmio_read_offset
arch/riscv/kvm/aia_aplic.c:406
Read of size 8 at addr ff600000ba965d58 by task 9498
Call Trace:
aplic_read_pending arch/riscv/kvm/aia_aplic.c:119 [inline]
aplic_read_pending_word arch/riscv/kvm/aia_aplic.c:351 [inline]
aplic_mmio_read_offset arch/riscv/kvm/aia_aplic.c:406
kvm_riscv_aia_aplic_has_attr arch/riscv/kvm/aia_aplic.c:566
aia_has_attr arch/riscv/kvm/aia_device.c:469
allocated by task 9473:
kvm_riscv_aia_aplic_init arch/riscv/kvm/aia_aplic.c:583
aia_init arch/riscv/kvm/aia_device.c:248 [inline]
aia_set_attr arch/riscv/kvm/aia_device.c:334
freed by task 9473:
kvm_riscv_aia_aplic_cleanup arch/riscv/kvm/aia_aplic.c:644
aia_init arch/riscv/kvm/aia_device.c:292 [inline]
aia_set_attr arch/riscv/kvm/aia_device.c:334

The patch replaces the actual MMIO read in kvm_riscv_aia_aplic_has_attr()
with a new aplic_mmio_has_offset() that only validates whether the given
offset falls within a known APLIC region, without touching any
dynamically allocated state. This is consistent with the KVM API
documentation for KVM_HAS_DEVICE_ATTR:
"Tests whether a device supports a particular attribute. A successful
return indicates the attribute is implemented. It does not necessarily
indicate that the attribute can be read or written in the device's
current state."
The upper bounds of each region are taken directly from the
RISC-V AIA specification, so the check is independent of the runtime
values of nr_irqs and nr_words.

This patch both fixes the use-after-free and makes the has_attr
implementation semantically correct.

Fixes: 289a007b98b06d ("RISC-V: KVM: Expose APLIC registers as attributes of AIA irqchip")
Signed-off-by: Jiakai Xu <jiakaiPeanut@xxxxxxxxx>
Signed-off-by: Jiakai Xu <xujiakai2025@xxxxxxxxxxx>
---
arch/riscv/kvm/aia_aplic.c | 28 ++++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/arch/riscv/kvm/aia_aplic.c b/arch/riscv/kvm/aia_aplic.c
index f59d1c0c8c43a..5e7a1055b2de6 100644
--- a/arch/riscv/kvm/aia_aplic.c
+++ b/arch/riscv/kvm/aia_aplic.c
@@ -527,6 +527,31 @@ static struct kvm_io_device_ops aplic_iodoev_ops = {
.write = aplic_mmio_write,
};

+static int aplic_mmio_has_offset(struct kvm *kvm, gpa_t off)
+{
+ if ((off & 0x3) != 0)
+ return -EOPNOTSUPP;
+
+ if ((off == APLIC_DOMAINCFG) ||
+ (off >= APLIC_SOURCECFG_BASE && off < (APLIC_SOURCECFG_BASE + 1023 * 4)) ||
+ (off >= APLIC_SETIP_BASE && off < (APLIC_SETIP_BASE + 32 * 4)) ||
+ (off == APLIC_SETIPNUM) ||
+ (off >= APLIC_CLRIP_BASE && off < (APLIC_CLRIP_BASE + 32 * 4)) ||
+ (off == APLIC_CLRIPNUM) ||
+ (off >= APLIC_SETIE_BASE && off < (APLIC_SETIE_BASE + 32 * 4)) ||
+ (off == APLIC_SETIENUM) ||
+ (off >= APLIC_CLRIE_BASE && off < (APLIC_CLRIE_BASE + 32 * 4)) ||
+ (off == APLIC_CLRIENUM) ||
+ (off == APLIC_SETIPNUM_LE) ||
+ (off == APLIC_SETIPNUM_BE) ||
+ (off == APLIC_GENMSI) ||
+ (off >= APLIC_TARGET_BASE && off < (APLIC_TARGET_BASE + 1203 * 4))
+ )
+ return 0;
+ else
+ return -ENODEV;
+}
+
int kvm_riscv_aia_aplic_set_attr(struct kvm *kvm, unsigned long type, u32 v)
{
int rc;
@@ -558,12 +583,11 @@ int kvm_riscv_aia_aplic_get_attr(struct kvm *kvm, unsigned long type, u32 *v)
int kvm_riscv_aia_aplic_has_attr(struct kvm *kvm, unsigned long type)
{
int rc;
- u32 val;

if (!kvm->arch.aia.aplic_state)
return -ENODEV;

- rc = aplic_mmio_read_offset(kvm, type, &val);
+ rc = aplic_mmio_has_offset(kvm, type);
if (rc)
return rc;

--
2.34.1