Skip to contentSkip to conversation

LoongArch: KVM: EIOINTC: fix INT_ENCODE ipnum out-of-bounds access

PATCH In review Tao Cui 3 participants 7 messages Jul 14

Subsystems (4)
lore
Share & tools
lore

Tao Cui

author
#0 lore raw

cover LoongArch: KVM: EIOINTC: fix INT_ENCODE ipnum out-of-bounds access

From: Tao Cui <[email protected]>

This small series fixes a guest-triggerable out-of-bounds access in the LoongArch KVM EIOINTC emulation, then factors the duplicated IP-number decode into a helper.

The IP-number decode in eiointc_set_sw_coreisr() and eiointc_update_irq() bounds ipnum only in the default (1-hot) mode. In INT_ENCODE mode the raw ipmap byte (0..255) is used to index sw_coreisr[cpu][ipnum], whose second dimension is LOONGSON_IP_NUM (8), so any ipmap byte >= 8 reads/writes past the array. The value is guest-programmable through the EIOINTC virtual extension (VIRT_CONFIG enables INT_ENCODE and the IPMAP IOCSR write is not validated) and is also restored from a migration stream via the LOAD_FINISHED control attribute, so this is a host slab out-of-bounds access reachable from an unprivileged guest.

Patch 1 is the minimal, stable-bound fix: clamp ipnum to [0, LOONGSON_IP_NUM) in INT_ENCODE mode at both call sites. Patch 2 is a follow-up cleanup that extracts the now-identical decode into eiointc_get_ipnum() (no functional change); it is split out so the fix stays surgical for backporting.

Verified with KASAN on Loongson-3A6000: the device-attr restore sequence (INT_ENCODE + ipmap byte 0x80 + LOAD_FINISHED) reports
"BUG: KASAN: slab-out-of-bounds in eiointc_set_sw_coreisr" without the series and is clean with it.

Patch 1 carries Fixes:/Cc: stable; patch 2 is mainline-only.

Tao Cui (2):
LoongArch: KVM: EIOINTC: clamp ipnum to valid range in INT_ENCODE mode
LoongArch: KVM: EIOINTC: factor IP-number decode into a helper

 arch/loongarch/kvm/intc/eiointc.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

--
2.43.0

author
#1 lore raw

PATCH 1/2 LoongArch: KVM: EIOINTC: clamp ipnum to valid range in INT_ENCODE mode

From: Tao Cui <[email protected]>

The IP-number decode in eiointc_set_sw_coreisr() and eiointc_update_irq() clamps ipnum only in the default (1-hot) mode. In INT_ENCODE mode the raw ipmap byte (0..255) is used as the index into sw_coreisr[cpu][ipnum], whose second dimension is LOONGSON_IP_NUM (8), so any ipmap byte >= 8 accesses the array out of bounds.

The value is guest-programmable through the EIOINTC virtual extension (VIRT_CONFIG enables INT_ENCODE and the IPMAP IOCSR write is unvalidated) and is also restored unvalidated from a migration stream via the LOAD_FINISHED control attribute, resulting in a host slab out-of-bounds access reachable from an unprivileged guest.

Clamp ipnum to [0, LOONGSON_IP_NUM) in INT_ENCODE mode as well.

Fixes 3956a52bc05b ("LoongArch: KVM: Add EIOINTC read and write functions")
---
 arch/loongarch/kvm/intc/eiointc.c | 4 ++++
 1 file changed, 4 insertions(+)
arch/loongarch/kvm/intc/eiointc.c +4 -0
index 2b14485d14a7..0c34d7ab264d 100644
@@ -17,6 +17,8 @@ static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s)
 		if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
 			ipnum = count_trailing_zeros(ipnum);
 			ipnum = ipnum < 4 ? ipnum : 0;
+		} else {
+			ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
 		}
 
 		cpuid = ((u8 *)s->coremap)[irq];
@@ -42,6 +44,8 @@ static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level)
 	if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
 		ipnum = count_trailing_zeros(ipnum);
 		ipnum = ipnum < 4 ? ipnum : 0;
+	} else {
+		ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
 	}
 
 	cpu = s->sw_coremap[irq];
Signature · 1 line
2.43.0
#2 lore raw

Re: [PATCH 1/2] LoongArch: KVM: EIOINTC: clamp ipnum to valid range in INT_ENCODE mode

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Guest or host userspace can trigger massive out-of-bounds reads and writes on the host kernel heap and undefined bitshift behavior by supplying an unvalidated MSI data value.
- [High] eiointc_set_sw_coreisr fails to decode cpuid in 1-hot mode (EIOINTC_ENABLE_CPU_ENCODE disabled), reading from the wrong vCPU's coreisr state when reconstructing sw_coreisr during LOAD_FINISHED.
- [High] When a guest writes to EIOINTC_COREISR_START to clear a pending interrupt, eiointc_update_irq clears the fast-path state (sw_coreisr) and de-asserts the parent IRQ on the currently routed CPU from sw_coremap, which may not be the CPU whose coreisr was actually cleared.
- [Medium] Lockless access to map->phys_map[cpuid] in kvm_get_vcpu_by_cpuid() lacks memory barriers.
--

--- Patch [1]: [PATCH 1/2] LoongArch: KVM: EIOINTC: clamp ipnum to valid range in INT_ENCODE mode --- commit 21ec11994b801fe6a14903dedc825bbe4a3246e5
Author: Tao Cui <[email protected]>

LoongArch: KVM: EIOINTC: clamp ipnum to valid range in INT_ENCODE mode

This commit clamps the ipnum variable to [0, LOONGSON_IP_NUM) in INT_ENCODE mode. It prevents out-of-bounds accesses on the sw_coreisr array which is guest-programmable, preventing out-of-bounds accesses reachable from an unprivileged guest.

arch/loongarch/kvm/intc/eiointc.c static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s) · line 24

Quoted 13 lines
arch/loongarch/kvm/intc/eiointc.c +2 -0
index 2b14485d14a70..0c34d7ab264dc 100644
@@ -17,6 +17,8 @@ static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s)
 		if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
 			ipnum = count_trailing_zeros(ipnum);
 			ipnum = ipnum < 4 ? ipnum : 0;
+		} else {
+			ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
 		}
 
 		cpuid = ((u8 *)s->coremap)[irq];

[Severity: High]
This is a pre-existing issue, but does eiointc_set_sw_coreisr() fail to decode cpuid in 1-hot mode when EIOINTC_ENABLE_CPU_ENCODE is disabled?

It looks like it fetches cpuid directly from s->coremap[irq] without decoding it. In the default 1-hot mode, cpuid represents a bitmask, but the subsequent call to kvm_get_vcpu_by_cpuid() expects an exact ID. Could this result in looking up the wrong vCPU during state restoration, causing lost interrupts?

[Severity: Medium]
This is a pre-existing issue, but is the lockless access to map->phys_map[cpuid] safe in kvm_get_vcpu_by_cpuid() when called here?

When kvm_get_vcpu_by_cpuid() reads map->phys_map[cpuid].enabled and vcpu, it doesn't use memory barriers like smp_load_acquire(). Concurrently, kvm_set_cpuid() updates these fields using a lock but without smp_store_release(). Could this compiler or CPU reordering cause kvm_get_vcpu_by_cpuid() to transiently return NULL?

[ ... ]

arch/loongarch/kvm/intc/eiointc.c static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level) · line 51

Quoted 9 lines
arch/loongarch/kvm/intc/eiointc.c +2 -0
@@ -42,6 +44,8 @@ static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level)
 	if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
 		ipnum = count_trailing_zeros(ipnum);
 		ipnum = ipnum < 4 ? ipnum : 0;
+	} else {
+		ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
 	}
 
 	cpu = s->sw_coremap[irq];

[Severity: Critical]
This is a pre-existing issue, but is the irq index validated before being used for array accesses here?

The KVM MSI routing mechanism allows guests or host userspace to inject MSIs with an arbitrary 32-bit msi.data payload. In pch_msi_set_irq():

arch/loongarch/kvm/intc/pch_pic.c:pch_msi_set_irq() {
    ...
    eiointc_set_irq(kvm->arch.eiointc, e->msi.data, level);
    ...
}

The user-supplied e->msi.data is passed unvalidated to eiointc_set_irq() as the irq parameter. This unvalidated irq is then used as an index for bitwise operations like __set_bit(irq, isr) in eiointc_set_irq() and array accesses like s->sw_coremap[irq] in eiointc_update_irq(). Can this allow unprivileged guest code to perform arbitrary out-of-bounds memory writes on the host kernel heap?

[Severity: High]
This isn't a bug introduced by this patch, but does eiointc_update_irq() operate on the correct CPU when clearing a pending interrupt?

When a guest writes to EIOINTC_COREISR_START to clear a pending interrupt:

arch/loongarch/kvm/intc/eiointc.c:loongarch_eiointc_write() {
    ...
    eiointc_update_irq(s, irq + index * 64, 0);
    ...
}

The write handler issues the clear request without explicitly passing the original vCPU. Then eiointc_update_irq() dynamically fetches the target CPU from s->sw_coremap[irq]. If the guest has re-routed the IRQ while it was pending, sw_coremap will point to the new CPU. Will this clear the fast-path sw_coreisr state on the new CPU, leaving the old CPU with a permanently asserted parent IRQ and an unrecoverable interrupt storm?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer
#3 lore raw

Re: [PATCH 1/2] LoongArch: KVM: EIOINTC: clamp ipnum to valid range in INT_ENCODE mode

In reply to Tao Cui

Hi Tao,

Thanks to catch this, there is similar modification which can be located at:
https://lore.kernel.org/lkml/[email protected]/

Regards
Bibo Mao

Quoted 46 lines · Tao Cui

From: Tao Cui <[email protected]>

The IP-number decode in eiointc_set_sw_coreisr() and eiointc_update_irq() clamps ipnum only in the default (1-hot) mode. In INT_ENCODE mode the raw ipmap byte (0..255) is used as the index into sw_coreisr[cpu][ipnum], whose second dimension is LOONGSON_IP_NUM (8), so any ipmap byte >= 8 accesses the array out of bounds.

The value is guest-programmable through the EIOINTC virtual extension (VIRT_CONFIG enables INT_ENCODE and the IPMAP IOCSR write is unvalidated) and is also restored unvalidated from a migration stream via the LOAD_FINISHED control attribute, resulting in a host slab out-of-bounds access reachable from an unprivileged guest.

Clamp ipnum to [0, LOONGSON_IP_NUM) in INT_ENCODE mode as well.

Fixes 3956a52bc05b ("LoongArch: KVM: Add EIOINTC read and write functions")
---
  arch/loongarch/kvm/intc/eiointc.c | 4 ++++
  1 file changed, 4 insertions(+)
arch/loongarch/kvm/intc/eiointc.c +4 -0
index 2b14485d14a7..0c34d7ab264d 100644
@@ -17,6 +17,8 @@ static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s)
  		if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
  			ipnum = count_trailing_zeros(ipnum);
  			ipnum = ipnum < 4 ? ipnum : 0;
+		} else {
+			ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
  		}
 
  		cpuid = ((u8 *)s->coremap)[irq];
@@ -42,6 +44,8 @@ static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level)
  	if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
  		ipnum = count_trailing_zeros(ipnum);
  		ipnum = ipnum < 4 ? ipnum : 0;
+	} else {
+		ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
  	}
 
  	cpu = s->sw_coremap[irq];
author
#4 lore raw

Re: [PATCH 1/2] LoongArch: KVM: EIOINTC: clamp ipnum to valid range in INT_ENCODE mode

Quoted 5 lines · Bibo Mao

Hi Tao,

Thanks to catch this, there is similar modification which can be located at:
https://lore.kernel.org/lkml/[email protected]/

Hi Bibo,

Haha, looks like we raced to the same fix :) I came across it while debugging VM migration (the LOAD_FINISHED restore path). Your patch takes priority — I'll go ahead and drop my series.

Thanks,
Tao

Quoted 49 lines

Regards
Bibo Mao

Quoted 44 lines · Tao Cui

From: Tao Cui <[email protected]>

The IP-number decode in eiointc_set_sw_coreisr() and eiointc_update_irq() clamps ipnum only in the default (1-hot) mode. In INT_ENCODE mode the raw ipmap byte (0..255) is used as the index into sw_coreisr[cpu][ipnum], whose second dimension is LOONGSON_IP_NUM (8), so any ipmap byte >= 8 accesses the array out of bounds.

The value is guest-programmable through the EIOINTC virtual extension (VIRT_CONFIG enables INT_ENCODE and the IPMAP IOCSR write is unvalidated) and is also restored unvalidated from a migration stream via the LOAD_FINISHED control attribute, resulting in a host slab out-of-bounds access reachable from an unprivileged guest.

Clamp ipnum to [0, LOONGSON_IP_NUM) in INT_ENCODE mode as well.

Fixes 3956a52bc05b ("LoongArch: KVM: Add EIOINTC read and write functions")

---
  arch/loongarch/kvm/intc/eiointc.c | 4 ++++

  1 file changed, 4 insertions(+)
arch/loongarch/kvm/intc/eiointc.c +4 -0
index 2b14485d14a7..0c34d7ab264d 100644
@@ -17,6 +17,8 @@ static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s)
           if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
               ipnum = count_trailing_zeros(ipnum);
               ipnum = ipnum < 4 ? ipnum : 0;
+        } else {
+            ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
           }
             cpuid = ((u8 *)s->coremap)[irq];
@@ -42,6 +44,8 @@ static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level)
       if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
           ipnum = count_trailing_zeros(ipnum);
           ipnum = ipnum < 4 ? ipnum : 0;
+    } else {
+        ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
       }
         cpu = s->sw_coremap[irq];
 
author
#5 lore raw

PATCH 2/2 LoongArch: KVM: EIOINTC: factor IP-number decode into a helper

In reply to Tao Cui

From: Tao Cui <[email protected]>

The ipmap IP-number decode is duplicated in eiointc_set_sw_coreisr() and eiointc_update_irq(). Factor it into eiointc_get_ipnum().

No functional change.

---
 arch/loongarch/kvm/intc/eiointc.c | 30 ++++++++++++++++--------------
 1 file changed, 16 insertions(+), 14 deletions(-)
arch/loongarch/kvm/intc/eiointc.c +16 -14
index 0c34d7ab264d..c1e0cd8dca16 100644
@@ -7,19 +7,27 @@
 #include <asm/kvm_vcpu.h>
 #include <linux/count_zeros.h>
 
+static int eiointc_get_ipnum(struct loongarch_eiointc *s, int irq)
+{
+	int ipnum = (s->ipmap >> (irq / 32 * 8)) & 0xff;
+
+	if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
+		ipnum = count_trailing_zeros(ipnum);
+		ipnum = ipnum < 4 ? ipnum : 0;
+	} else {
+		ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
+	}
+
+	return ipnum;
+}
+
 static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s)
 {
 	int ipnum, cpu, cpuid, irq;
 	struct kvm_vcpu *vcpu;
 
 	for (irq = 0; irq < EIOINTC_IRQS; irq++) {
-		ipnum = (s->ipmap >> (irq / 32 * 8)) & 0xff;
-		if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
-			ipnum = count_trailing_zeros(ipnum);
-			ipnum = ipnum < 4 ? ipnum : 0;
-		} else {
-			ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
-		}
+		ipnum = eiointc_get_ipnum(s, irq);
 
 		cpuid = ((u8 *)s->coremap)[irq];
 		vcpu = kvm_get_vcpu_by_cpuid(s->kvm, cpuid);
@@ -40,13 +48,7 @@ static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level)
 	struct kvm_vcpu *vcpu;
 	struct kvm_interrupt vcpu_irq;
 
-	ipnum = (s->ipmap >> (irq / 32 * 8)) & 0xff;
-	if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
-		ipnum = count_trailing_zeros(ipnum);
-		ipnum = ipnum < 4 ? ipnum : 0;
-	} else {
-		ipnum = (ipnum < LOONGSON_IP_NUM) ? ipnum : 0;
-	}
+	ipnum = eiointc_get_ipnum(s, irq);
 
 	cpu = s->sw_coremap[irq];
 	vcpu = kvm_get_vcpu_by_id(s->kvm, cpu);
Signature · 1 line
2.43.0
#6 lore raw

Re: [PATCH 2/2] LoongArch: KVM: EIOINTC: factor IP-number decode into a helper

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Missing bounds check on untrusted guest-controlled MSI data (e->msi.data) allows out-of-bounds array accesses and arbitrary host memory corruption. --

--- Patch [2]: [PATCH 2/2] LoongArch: KVM: EIOINTC: factor IP-number decode into a helper --- commit 819d482a674f6e7813eb24d40a7c6711789b26a8
Author: Tao Cui <[email protected]>

LoongArch: KVM: EIOINTC: factor IP-number decode into a helper

This commit factors the ipmap IP-number decoding logic, which was previously duplicated in eiointc_set_sw_coreisr() and eiointc_update_irq(), into a new helper function named eiointc_get_ipnum().

arch/loongarch/kvm/intc/eiointc.c line 12

Quoted 11 lines
arch/loongarch/kvm/intc/eiointc.c +3 -0
index 0c34d7ab264dc..c1e0cd8dca16c 100644
@@ -7,19 +7,27 @@
 #include <asm/kvm_vcpu.h>
 #include <linux/count_zeros.h>
 
+static int eiointc_get_ipnum(struct loongarch_eiointc *s, int irq)
+{
+	int ipnum = (s->ipmap >> (irq / 32 * 8)) & 0xff;

[Severity: Critical]
This is a pre-existing issue, but does this bit shift result in undefined behavior if an out-of-bounds irq is provided?

If a malicious guest or userspace provides an unvalidated MSI data payload, it is passed as the irq parameter through the following call chain:

kvm_send_userspace_msi() -> kvm_set_msi() -> pch_msi_set_irq()

pch_msi_set_irq() {
    ...
    eiointc_set_irq(kvm->arch.eiointc, e->msi.data, level);
}

The unvalidated irq is used in the bit shift above, and also leads to out-of-bounds array reads in eiointc_update_irq():

    cpu = s->sw_coremap[irq];

Could this also allow arbitrary host memory corruption when setting bits in eiointc_set_irq() since isr is bounded to EIOINTC_IRQS?

    level ? __set_bit(irq, isr) : __clear_bit(irq, isr);
Quoted 3 lines
arch/loongarch/kvm/intc/eiointc.c +3 -0
+
+	if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
+		ipnum = count_trailing_zeros(ipnum);

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2