@samitouri / QOSamiQemu / commits / 153dc2fa7b

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