Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

xen/gntalloc: fix reference counts on multi-page mappings

When a multi-page mapping of gntalloc is created, the reference counts
of all pages in the vma are incremented. However, the vma open/close
operations only adjusted the reference count of the first page in the
mapping, leaking the other pages. Store a struct in the vm_private_data
to track the original page count to properly free the pages when the
last reference to the vma is closed.

Reported-by: Anil Madhavapeddy <anil@recoil.org>
Signed-off-by: Daniel De Graaf <dgdegra@tycho.nsa.gov>
Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>

authored by

Daniel De Graaf and committed by
Konrad Rzeszutek Wilk
243082e0 0105d2b4

+43 -13
+43 -13
drivers/xen/gntalloc.c
··· 99 99 uint64_t index; 100 100 }; 101 101 102 + struct gntalloc_vma_private_data { 103 + struct gntalloc_gref *gref; 104 + int users; 105 + int count; 106 + }; 107 + 102 108 static void __del_gref(struct gntalloc_gref *gref); 103 109 104 110 static void do_cleanup(void) ··· 457 451 458 452 static void gntalloc_vma_open(struct vm_area_struct *vma) 459 453 { 460 - struct gntalloc_gref *gref = vma->vm_private_data; 461 - if (!gref) 454 + struct gntalloc_vma_private_data *priv = vma->vm_private_data; 455 + 456 + if (!priv) 462 457 return; 463 458 464 459 mutex_lock(&gref_mutex); 465 - gref->users++; 460 + priv->users++; 466 461 mutex_unlock(&gref_mutex); 467 462 } 468 463 469 464 static void gntalloc_vma_close(struct vm_area_struct *vma) 470 465 { 471 - struct gntalloc_gref *gref = vma->vm_private_data; 472 - if (!gref) 466 + struct gntalloc_vma_private_data *priv = vma->vm_private_data; 467 + struct gntalloc_gref *gref, *next; 468 + int i; 469 + 470 + if (!priv) 473 471 return; 474 472 475 473 mutex_lock(&gref_mutex); 476 - gref->users--; 477 - if (gref->users == 0) 478 - __del_gref(gref); 474 + priv->users--; 475 + if (priv->users == 0) { 476 + gref = priv->gref; 477 + for (i = 0; i < priv->count; i++) { 478 + gref->users--; 479 + next = list_entry(gref->next_gref.next, 480 + struct gntalloc_gref, next_gref); 481 + if (gref->users == 0) 482 + __del_gref(gref); 483 + gref = next; 484 + } 485 + kfree(priv); 486 + } 479 487 mutex_unlock(&gref_mutex); 480 488 } 481 489 ··· 501 481 static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) 502 482 { 503 483 struct gntalloc_file_private_data *priv = filp->private_data; 484 + struct gntalloc_vma_private_data *vm_priv; 504 485 struct gntalloc_gref *gref; 505 486 int count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; 506 487 int rv, i; 507 - 508 - pr_debug("%s: priv %p, page %lu+%d\n", __func__, 509 - priv, vma->vm_pgoff, count); 510 488 511 489 if (!(vma->vm_flags & VM_SHARED)) { 512 490 printk(KERN_ERR "%s: Mapping must be shared.\n", __func__); 513 491 return -EINVAL; 514 492 } 515 493 494 + vm_priv = kmalloc(sizeof(*vm_priv), GFP_KERNEL); 495 + if (!vm_priv) 496 + return -ENOMEM; 497 + 516 498 mutex_lock(&gref_mutex); 499 + 500 + pr_debug("%s: priv %p,%p, page %lu+%d\n", __func__, 501 + priv, vm_priv, vma->vm_pgoff, count); 502 + 517 503 gref = find_grefs(priv, vma->vm_pgoff << PAGE_SHIFT, count); 518 504 if (gref == NULL) { 519 505 rv = -ENOENT; ··· 528 502 goto out_unlock; 529 503 } 530 504 531 - vma->vm_private_data = gref; 505 + vm_priv->gref = gref; 506 + vm_priv->users = 1; 507 + vm_priv->count = count; 532 508 533 - vma->vm_flags |= VM_RESERVED; 509 + vma->vm_private_data = vm_priv; 510 + 511 + vma->vm_flags |= VM_RESERVED | VM_DONTEXPAND; 534 512 535 513 vma->vm_ops = &gntalloc_vmops; 536 514