apic_update_irq() wouldn't be called at all in case the guest set LINT0
as masked, which would prevent a previously asserted CPU_INTERRUPT_HARD
from an ExtINT source (like the legacy PIT) from getting cleared.
This would usually go unnoticed since masking the PIC while LINT0 is
still unmasked would clear said interrupt via pic_irq_request().
However, a guest that masks LINT0 first, and only then the PIC still
keeps CPU_INTERRUPT_HARD asserted, which then fires on its first sti.
Since APIC's IRR is empty, and the PIC is never even asked since LINT0
is masked, a -1 is returned from cpu_get_pic_interrupt(), which is then
propagated all the way to IDT dispatch, which fails the IDT limit check
against 0xFFFFFFFF (-1) and causes the guest to take a bogus #GP with an
error code of 0xFFFFFFFA (aka -1 * 8 | (1 << 1)).
This can be reproduced by simply booting via SeaBIOS, it leaves the PIT
running, LINT0 unmasked and configured as ExtINT, and PIT unmasked at
the PIC. A guest that then masks LINT0, and then the PIC, receives
the #GP mentioned above following its first sti instruction.
Fix this by calling apic_update_irq() unconditionally after a LINT0
write.
Fixes: a94820ddc3 ("apic: Reevaluate pending interrupts on LVT_LINT0 changes")
Signed-off-by: Daniil Tatianin <[email protected]>
---
hw/intc/apic.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hw/intc/apic.c b/hw/intc/apic.c
index 0e8932005f..6fb941cc47 100644
--- a/hw/intc/apic.c
+++ b/hw/intc/apic.c
@@ -1007,7 +1007,8 @@ static int apic_register_write(APICCommonState *s, int
index, uint64_t val)
s->lvt[n] = val;
if (n == APIC_LVT_TIMER) {
apic_timer_update(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
- } else if (n == APIC_LVT_LINT0 && apic_check_pic(s)) {
+ } else if (n == APIC_LVT_LINT0) {
+ apic_check_pic(s);
apic_update_irq(s);
}
}
--
2.43.0