From 75d61fbcf563373696578570e914f555e12c8d97 Mon Sep 17 00:00:00 2001 From: Takuya Yoshikawa Date: Wed, 30 Jan 2013 19:40:41 +0900 Subject: KVM: set_memory_region: Disallow changing read-only attribute later As Xiao pointed out, there are a few problems with it: - kvm_arch_commit_memory_region() write protects the memory slot only for GET_DIRTY_LOG when modifying the flags. - FNAME(sync_page) uses the old spte value to set a new one without checking KVM_MEM_READONLY flag. Since we flush all shadow pages when creating a new slot, the simplest fix is to disallow such problematic flag changes: this is safe because no one is doing such things. Reviewed-by: Gleb Natapov Signed-off-by: Takuya Yoshikawa Cc: Xiao Guangrong Cc: Alex Williamson Signed-off-by: Marcelo Tosatti --- Documentation/virtual/kvm/api.txt | 12 ++++++------ virt/kvm/kvm_main.c | 35 ++++++++++++----------------------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/Documentation/virtual/kvm/api.txt b/Documentation/virtual/kvm/api.txt index 09905cbcbb0b..0e03b1968272 100644 --- a/Documentation/virtual/kvm/api.txt +++ b/Documentation/virtual/kvm/api.txt @@ -874,12 +874,12 @@ It is recommended that the lower 21 bits of guest_phys_addr and userspace_addr be identical. This allows large pages in the guest to be backed by large pages in the host. -The flags field supports two flag, KVM_MEM_LOG_DIRTY_PAGES, which instructs -kvm to keep track of writes to memory within the slot. See KVM_GET_DIRTY_LOG -ioctl. The KVM_CAP_READONLY_MEM capability indicates the availability of the -KVM_MEM_READONLY flag. When this flag is set for a memory region, KVM only -allows read accesses. Writes will be posted to userspace as KVM_EXIT_MMIO -exits. +The flags field supports two flags: KVM_MEM_LOG_DIRTY_PAGES and +KVM_MEM_READONLY. The former can be set to instruct KVM to keep track of +writes to memory within the slot. See KVM_GET_DIRTY_LOG ioctl to know how to +use it. The latter can be set, if KVM_CAP_READONLY_MEM capability allows it, +to make a new slot read-only. In this case, writes to this memory will be +posted to userspace as KVM_EXIT_MMIO exits. When the KVM_CAP_SYNC_MMU capability is available, changes in the backing of the memory region are automatically reflected into the guest. For example, an diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 64c5dc37c6a1..2e93630b4add 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -754,7 +754,6 @@ int __kvm_set_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot; struct kvm_memory_slot old, new; struct kvm_memslots *slots = NULL, *old_memslots; - bool old_iommu_mapped; enum kvm_mr_change change; r = check_memory_region_flags(mem); @@ -797,15 +796,14 @@ int __kvm_set_memory_region(struct kvm *kvm, new.npages = npages; new.flags = mem->flags; - old_iommu_mapped = old.npages; - r = -EINVAL; if (npages) { if (!old.npages) change = KVM_MR_CREATE; else { /* Modify an existing slot. */ if ((mem->userspace_addr != old.userspace_addr) || - (npages != old.npages)) + (npages != old.npages) || + ((new.flags ^ old.flags) & KVM_MEM_READONLY)) goto out; if (base_gfn != old.base_gfn) @@ -867,7 +865,6 @@ int __kvm_set_memory_region(struct kvm *kvm, /* slot was deleted or moved, clear iommu mapping */ kvm_iommu_unmap_pages(kvm, &old); - old_iommu_mapped = false; /* From this point no new shadow pages pointing to a deleted, * or moved, memslot will be created. * @@ -898,25 +895,17 @@ int __kvm_set_memory_region(struct kvm *kvm, /* * IOMMU mapping: New slots need to be mapped. Old slots need to be - * un-mapped and re-mapped if their base changes or if flags that the - * iommu cares about change (read-only). Base change unmapping is - * handled above with slot deletion, so we only unmap incompatible - * flags here. Anything else the iommu might care about for existing - * slots (size changes, userspace addr changes) is disallowed above, - * so any other attribute changes getting here can be skipped. + * un-mapped and re-mapped if their base changes. Since base change + * unmapping is handled above with slot deletion, mapping alone is + * needed here. Anything else the iommu might care about for existing + * slots (size changes, userspace addr changes and read-only flag + * changes) is disallowed above, so any other attribute changes getting + * here can be skipped. */ - if (change != KVM_MR_DELETE) { - if (old_iommu_mapped && - ((new.flags ^ old.flags) & KVM_MEM_READONLY)) { - kvm_iommu_unmap_pages(kvm, &old); - old_iommu_mapped = false; - } - - if (!old_iommu_mapped) { - r = kvm_iommu_map_pages(kvm, &new); - if (r) - goto out_slots; - } + if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) { + r = kvm_iommu_map_pages(kvm, &new); + if (r) + goto out_slots; } /* actual memory is freed via old in kvm_free_physmem_slot below */ -- cgit v1.2.3