apic: fix delivery bitmask with modified xAPIC ids
Self-IPIs (or all-but-self IPIs) in QEMU can cause a out-of-bounds access to deliver_bitmask, because the access uses the APIC ID register which is writable by the guest. However, foreach_apic uses the delivery bitmask indexes to look up the local_apics[] array, which is indexed by *initial* APIC id. Using the right id fixes both a possible heap write overflow if the modified APIC id is too large for max_apic_words, and a mis-delivery of both self and all-but-self IPIs. Reported-by: Wei Che Kao <skps96g313.cs10@gmail.com> Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Paolo Bonzini committed
May 15, 2026 at 12:10 UTC
153dc2fa7bbe0491290d22c4bbb6807074f24260
1 file changed
+8
-9
hw/intc/apic.c
+8
-9
@@ -648,13 +648,6 @@ static void apic_deliver(APICCommonState *s, uint32_t dest, uint8_t dest_mode,
648
APICCommonState *apic_iter;
649
uint32_t deliver_bitmask_size = max_apic_words * sizeof(uint32_t);
650
g_autofree uint32_t *deliver_bitmask = g_new(uint32_t, max_apic_words);
651
- uint32_t current_apic_id;
652
-
653
- if (is_x2apic_mode(s)) {
654
- current_apic_id = s->initial_apic_id;
655
- } else {
656
- current_apic_id = s->id;
657
- }
651
652
switch (dest_shorthand) {
653
case 0:
@@ -662,14 +655,20 @@ static void apic_deliver(APICCommonState *s, uint32_t dest, uint8_t dest_mode,
655
break;
656
case 1:
657
memset(deliver_bitmask, 0x00, deliver_bitmask_size);
665
- apic_set_bit(deliver_bitmask, current_apic_id);
658
+ /*
659
+ * The self and all-but-self cases do not use apic_match_dest() and
660
+ * directly fill in deliver_bitmask; the bitmask's indexes in turn
661
+ * map to local_apics[] slots which are never changed even if the
662
+ * xAPIC id is modified. So use s->initial_apic_id instead of s->id.
663
+ */
664
+ apic_set_bit(deliver_bitmask, s->initial_apic_id);
665
break;
666
case 2:
667
memset(deliver_bitmask, 0xff, deliver_bitmask_size);
668
break;
669
case 3:
670
memset(deliver_bitmask, 0xff, deliver_bitmask_size);
672
- apic_reset_bit(deliver_bitmask, current_apic_id);
671
+ apic_reset_bit(deliver_bitmask, s->initial_apic_id);
672
break;
673
}
674