Vulnerability: The kvm_iommu_map_pages function in virt/kvm/iommu.c in the Linux kernel through 3.16.1 miscalculates the number of pages during the handling of a mapping failure, which allows guest OS users to (1) cause a denial of service (host OS memory corruption) or possibly have unspecified other impact by triggering a large gfn value or (2) cause a denial of service (host OS memory consumption) by triggering a small gfn value that leads to permanently pinned pages.
Vulnerability ID: CVE-2014-3601
CWE: CWE-189
Severity score: 6.9
Project: https://github.com/torvalds/linux
Published: 2014-09-01
File: virt/kvm/iommu.c
Commit message: kvm: iommu: fix the third parameter of kvm_iommu_put_pages (CVE-2014-3601)

The third parameter of kvm_iommu_put_pages is wrong,
It should be 'gfn - slot->base_gfn'.

By making gfn very large, malicious guest or userspace can cause kvm to
go to this error path, and subsequently to pass a huge value as size.
Alternatively if gfn is small, then pages would be pinned but never
unpinned, causing host memory leak and local DOS.

Passing a reasonable but large value could be the most dangerous case,
because it would unpin a page that should have stayed pinned, and thus
allow the device to DMA into arbitrary memory.  However, this cannot
happen because of the condition that can trigger the error:

- out of memory (where you can't allocate even a single page)
  should not be possible for the attacker to trigger

- when exceeding the iommu's address space, guest pages after gfn
  will also exceed the iommu's address space, and inside
  kvm_iommu_put_pages() the iommu_iova_to_phys() will fail.  The
  page thus would not be unpinned at all.

Reported-by: Jack Morgenstein <jackm@mellanox.com>
Cc: stable@vger.kernel.org
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Commit author: Michael S. Tsirkin

Vulnerable version (before fix):
 static pfn_t kvm_pin_pages(struct kvm_memory_slot *slot, gfn_t gfn, 	return pfn; }  int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot) { 	gfn_t gfn, end_gfn; int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot) 		if (r) { 			printk(KERN_ERR "kvm_iommu_map_address:" 			       "iommu failed to map pfn=%llx\n", pfn); 			goto unmap_pages; 		}  int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot) 	return 0;  unmap_pages: 	kvm_iommu_put_pages(kvm, slot->base_gfn, gfn); 	return r; }  int kvm_iommu_map_guest(struct kvm *kvm) 	return r; }  static void kvm_unpin_pages(struct kvm *kvm, pfn_t pfn, unsigned long npages) { 	unsigned long i;  	for (i = 0; i < npages; ++i) 		kvm_release_pfn_clean(pfn + i); }  static void kvm_iommu_put_pages(struct kvm *kvm, 				gfn_t base_gfn, unsigned long npages) {

Fixed version (after fix):
 static pfn_t kvm_pin_pages(struct kvm_memory_slot *slot, gfn_t gfn, 	return pfn; }  static void kvm_unpin_pages(struct kvm *kvm, pfn_t pfn, unsigned long npages) { 	unsigned long i;  	for (i = 0; i < npages; ++i) 		kvm_release_pfn_clean(pfn + i); }  int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot) { 	gfn_t gfn, end_gfn; int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot) 		if (r) { 			printk(KERN_ERR "kvm_iommu_map_address:" 			       "iommu failed to map pfn=%llx\n", pfn); 			kvm_unpin_pages(kvm, pfn, page_size); 			goto unmap_pages; 		}  int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot) 	return 0;  unmap_pages: 	kvm_iommu_put_pages(kvm, slot->base_gfn, gfn - slot->base_gfn); 	return r; }  int kvm_iommu_map_guest(struct kvm *kvm) 	return r; }  static void kvm_iommu_put_pages(struct kvm *kvm, 				gfn_t base_gfn, unsigned long npages) {

Patch:
patch: @@ -61,6 +61,14 @@ static pfn_t kvm_pin_pages(struct kvm_memory_slot *slot, gfn_t gfn,
 	return pfn;
 }
 
+static void kvm_unpin_pages(struct kvm *kvm, pfn_t pfn, unsigned long npages)
+{
+	unsigned long i;
+
+	for (i = 0; i < npages; ++i)
+		kvm_release_pfn_clean(pfn + i);
+}
+
 int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot)
 {
 	gfn_t gfn, end_gfn;
@@ -123,6 +131,7 @@ int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot)
 		if (r) {
 			printk(KERN_ERR "kvm_iommu_map_address:"
 			       "iommu failed to map pfn=%llx\n", pfn);
+			kvm_unpin_pages(kvm, pfn, page_size);
 			goto unmap_pages;
 		}
 
@@ -134,7 +143,7 @@ int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot)
 	return 0;
 
 unmap_pages:
-	kvm_iommu_put_pages(kvm, slot->base_gfn, gfn);
+	kvm_iommu_put_pages(kvm, slot->base_gfn, gfn - slot->base_gfn);
 	return r;
 }
 
@@ -266,14 +275,6 @@ int kvm_iommu_map_guest(struct kvm *kvm)
 	return r;
 }
 
-static void kvm_unpin_pages(struct kvm *kvm, pfn_t pfn, unsigned long npages)
-{
-	unsigned long i;
-
-	for (i = 0; i < npages; ++i)
-		kvm_release_pfn_clean(pfn + i);
-}
-
 static void kvm_iommu_put_pages(struct kvm *kvm,
 				gfn_t base_gfn, unsigned long npages)
 {
path: virt/kvm/iommu.c