Skip to content

Commit e649b3f

Browse files
Etsukatabonzini
authored andcommitted
KVM: x86: Fix APIC page invalidation race
Commit b1394e7 ("KVM: x86: fix APIC page invalidation") tried to fix inappropriate APIC page invalidation by re-introducing arch specific kvm_arch_mmu_notifier_invalidate_range() and calling it from kvm_mmu_notifier_invalidate_range_start. However, the patch left a possible race where the VMCS APIC address cache is updated *before* it is unmapped: (Invalidator) kvm_mmu_notifier_invalidate_range_start() (Invalidator) kvm_make_all_cpus_request(kvm, KVM_REQ_APIC_PAGE_RELOAD) (KVM VCPU) vcpu_enter_guest() (KVM VCPU) kvm_vcpu_reload_apic_access_page() (Invalidator) actually unmap page Because of the above race, there can be a mismatch between the host physical address stored in the APIC_ACCESS_PAGE VMCS field and the host physical address stored in the EPT entry for the APIC GPA (0xfee0000). When this happens, the processor will not trap APIC accesses, and will instead show the raw contents of the APIC-access page. Because Windows OS periodically checks for unexpected modifications to the LAPIC register, this will show up as a BSOD crash with BugCheck CRITICAL_STRUCTURE_CORRUPTION (109) we are currently seeing in https://bugzilla.redhat.com/show_bug.cgi?id=1751017. The root cause of the issue is that kvm_arch_mmu_notifier_invalidate_range() cannot guarantee that no additional references are taken to the pages in the range before kvm_mmu_notifier_invalidate_range_end(). Fortunately, this case is supported by the MMU notifier API, as documented in include/linux/mmu_notifier.h: * If the subsystem * can't guarantee that no additional references are taken to * the pages in the range, it has to implement the * invalidate_range() notifier to remove any references taken * after invalidate_range_start(). The fix therefore is to reload the APIC-access page field in the VMCS from kvm_mmu_notifier_invalidate_range() instead of ..._range_start(). Cc: stable@vger.kernel.org Fixes: b1394e7 ("KVM: x86: fix APIC page invalidation") Fixes: https://bugzilla.kernel.org/show_bug.cgi?id=197951 Signed-off-by: Eiichi Tsukata <eiichi.tsukata@nutanix.com> Message-Id: <20200606042627.61070-1-eiichi.tsukata@nutanix.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent fb7333d commit e649b3f

File tree

3 files changed

+20
-17
lines changed

3 files changed

+20
-17
lines changed

arch/x86/kvm/x86.c

+2-5
Original file line numberDiff line numberDiff line change
@@ -8270,9 +8270,8 @@ static void vcpu_load_eoi_exitmap(struct kvm_vcpu *vcpu)
82708270
kvm_x86_ops.load_eoi_exitmap(vcpu, eoi_exit_bitmap);
82718271
}
82728272

8273-
int kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
8274-
unsigned long start, unsigned long end,
8275-
bool blockable)
8273+
void kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
8274+
unsigned long start, unsigned long end)
82768275
{
82778276
unsigned long apic_address;
82788277

@@ -8283,8 +8282,6 @@ int kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
82838282
apic_address = gfn_to_hva(kvm, APIC_DEFAULT_PHYS_BASE >> PAGE_SHIFT);
82848283
if (start <= apic_address && apic_address < end)
82858284
kvm_make_all_cpus_request(kvm, KVM_REQ_APIC_PAGE_RELOAD);
8286-
8287-
return 0;
82888285
}
82898286

82908287
void kvm_vcpu_reload_apic_access_page(struct kvm_vcpu *vcpu)

include/linux/kvm_host.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -1420,8 +1420,8 @@ static inline long kvm_arch_vcpu_async_ioctl(struct file *filp,
14201420
}
14211421
#endif /* CONFIG_HAVE_KVM_VCPU_ASYNC_IOCTL */
14221422

1423-
int kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
1424-
unsigned long start, unsigned long end, bool blockable);
1423+
void kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
1424+
unsigned long start, unsigned long end);
14251425

14261426
#ifdef CONFIG_HAVE_KVM_VCPU_RUN_PID_CHANGE
14271427
int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu);

virt/kvm/kvm_main.c

+16-10
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,9 @@ static void kvm_uevent_notify_change(unsigned int type, struct kvm *kvm);
155155
static unsigned long long kvm_createvm_count;
156156
static unsigned long long kvm_active_vms;
157157

158-
__weak int kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
159-
unsigned long start, unsigned long end, bool blockable)
158+
__weak void kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
159+
unsigned long start, unsigned long end)
160160
{
161-
return 0;
162161
}
163162

164163
bool kvm_is_zone_device_pfn(kvm_pfn_t pfn)
@@ -384,6 +383,18 @@ static inline struct kvm *mmu_notifier_to_kvm(struct mmu_notifier *mn)
384383
return container_of(mn, struct kvm, mmu_notifier);
385384
}
386385

386+
static void kvm_mmu_notifier_invalidate_range(struct mmu_notifier *mn,
387+
struct mm_struct *mm,
388+
unsigned long start, unsigned long end)
389+
{
390+
struct kvm *kvm = mmu_notifier_to_kvm(mn);
391+
int idx;
392+
393+
idx = srcu_read_lock(&kvm->srcu);
394+
kvm_arch_mmu_notifier_invalidate_range(kvm, start, end);
395+
srcu_read_unlock(&kvm->srcu, idx);
396+
}
397+
387398
static void kvm_mmu_notifier_change_pte(struct mmu_notifier *mn,
388399
struct mm_struct *mm,
389400
unsigned long address,
@@ -408,7 +419,6 @@ static int kvm_mmu_notifier_invalidate_range_start(struct mmu_notifier *mn,
408419
{
409420
struct kvm *kvm = mmu_notifier_to_kvm(mn);
410421
int need_tlb_flush = 0, idx;
411-
int ret;
412422

413423
idx = srcu_read_lock(&kvm->srcu);
414424
spin_lock(&kvm->mmu_lock);
@@ -425,14 +435,9 @@ static int kvm_mmu_notifier_invalidate_range_start(struct mmu_notifier *mn,
425435
kvm_flush_remote_tlbs(kvm);
426436

427437
spin_unlock(&kvm->mmu_lock);
428-
429-
ret = kvm_arch_mmu_notifier_invalidate_range(kvm, range->start,
430-
range->end,
431-
mmu_notifier_range_blockable(range));
432-
433438
srcu_read_unlock(&kvm->srcu, idx);
434439

435-
return ret;
440+
return 0;
436441
}
437442

438443
static void kvm_mmu_notifier_invalidate_range_end(struct mmu_notifier *mn,
@@ -538,6 +543,7 @@ static void kvm_mmu_notifier_release(struct mmu_notifier *mn,
538543
}
539544

540545
static const struct mmu_notifier_ops kvm_mmu_notifier_ops = {
546+
.invalidate_range = kvm_mmu_notifier_invalidate_range,
541547
.invalidate_range_start = kvm_mmu_notifier_invalidate_range_start,
542548
.invalidate_range_end = kvm_mmu_notifier_invalidate_range_end,
543549
.clear_flush_young = kvm_mmu_notifier_clear_flush_young,

0 commit comments

Comments
 (0)