CVE-2014-8369: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: The kvm_iommu_map_pages function in virt/kvm/iommu.c in the Linux kernel through 3.17.2 miscalculates the number of pages during the handling of a mapping failure, which allows guest OS users to cause a denial of service (host OS page unpinning) or possibly have unspecified other impact by leveraging guest OS privileges.  NOTE: this vulnerability exists because of an incorrect fix for CVE-2014-3601.
CWE description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
CVSS v3 base score: 7.8
Published: 2014-11-10T11:55Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: virt/kvm/iommu.c
Commit date: 2014-10-24 13:30:37 +0200
Commit message: kvm: fix excessive pages un-pinning in kvm_iommu_map error path.

The third parameter of kvm_unpin_pages() when called from
kvm_iommu_map_pages() is wrong, it should be the number of pages to un-pin
and not the page size.

This error was facilitated with an inconsistent API: kvm_pin_pages() takes
a size, but kvn_unpin_pages() takes a number of pages, so fix the problem
by matching the two.

This was introduced by commit 350b8bd ("kvm: iommu: fix the third parameter
of kvm_iommu_put_pages (CVE-2014-3601)"), which fixes the lack of
un-pinning for pages intended to be un-pinned (i.e. memory leak) but
unfortunately potentially aggravated the number of pages we un-pin that
should have stayed pinned. As far as I understand though, the same
practical mitigations apply.

This issue was found during review of Red Hat 6.6 patches to prepare
Ksplice rebootless updates.

Thanks to Vegard for his time on a late Friday evening to help me in
understanding this code.

Fixes: 350b8bd ("kvm: iommu: fix the third parameter of... (CVE-2014-3601)")
Cc: stable@vger.kernel.org
Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
Signed-off-by: Jamie Iles <jamie.iles@oracle.com>
Reviewed-by: Sasha Levin <sasha.levin@oracle.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

Diff: diff --git a/virt/kvm/iommu.c b/virt/kvm/iommu.c
index e51d9f9b995f18..c1e6ae989a4321 100644
--- a/virt/kvm/iommu.c
+++ b/virt/kvm/iommu.c
@@ -41,17 +41,17 @@ MODULE_PARM_DESC(allow_unsafe_assigned_interrupts,
 static int kvm_iommu_unmap_memslots(struct kvm *kvm);
 static void kvm_iommu_put_pages(struct kvm *kvm,
 				gfn_t base_gfn, unsigned long npages);
 
 static pfn_t kvm_pin_pages(struct kvm_memory_slot *slot, gfn_t gfn,
-			   unsigned long size)
+			   unsigned long npages)
 {
 	gfn_t end_gfn;
 	pfn_t pfn;
 
 	pfn     = gfn_to_pfn_memslot(slot, gfn);
-	end_gfn = gfn + (size >> PAGE_SHIFT);
+	end_gfn = gfn + npages;
 	gfn    += 1;
 
 	if (is_error_noslot_pfn(pfn))
 		return pfn;
 
@@ -117,11 +117,11 @@ int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot)
 
 		/*
 		 * Pin all pages we are about to map in memory. This is
 		 * important because we unmap and unpin in 4kb steps later.
 		 */
-		pfn = kvm_pin_pages(slot, gfn, page_size);
+		pfn = kvm_pin_pages(slot, gfn, page_size >> PAGE_SHIFT);
 		if (is_error_noslot_pfn(pfn)) {
 			gfn += 1;
 			continue;
 		}
 
@@ -129,11 +129,11 @@ int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot)
 		r = iommu_map(domain, gfn_to_gpa(gfn), pfn_to_hpa(pfn),
 			      page_size, flags);
 		if (r) {
 			printk(KERN_ERR "kvm_iommu_map_address:"
 			       "iommu failed to map pfn=%llx\n", pfn);
-			kvm_unpin_pages(kvm, pfn, page_size);
+			kvm_unpin_pages(kvm, pfn, page_size >> PAGE_SHIFT);
 			goto unmap_pages;
 		}
 
 		gfn += page_size >> PAGE_SHIFT;
 

Vulnerable code: 			   unsigned long size)
	end_gfn = gfn + (size >> PAGE_SHIFT);
		pfn = kvm_pin_pages(slot, gfn, page_size);
			kvm_unpin_pages(kvm, pfn, page_size);
Fixed code: 			   unsigned long npages)
	end_gfn = gfn + npages;
		pfn = kvm_pin_pages(slot, gfn, page_size >> PAGE_SHIFT);
			kvm_unpin_pages(kvm, pfn, page_size >> PAGE_SHIFT);