diff options
author | Daniel De Graaf <dgdegra@tycho.nsa.gov> | 2011-11-28 11:49:11 -0500 |
---|---|---|
committer | Konrad Rzeszutek Wilk <konrad.wilk@oracle.com> | 2011-12-16 11:26:10 -0500 |
commit | 243082e0d59f169a1fa502f51ee5a820889fae93 (patch) | |
tree | 8359afd781878ca4e75ca34f1cd328f469815f5d /drivers/xen/gntalloc.c | |
parent | 0105d2b4fbc24c2fb6ca9bae650784dd7ddf0b12 (diff) |
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>
Diffstat (limited to 'drivers/xen/gntalloc.c')
-rw-r--r-- | drivers/xen/gntalloc.c | 56 |
1 files changed, 43 insertions, 13 deletions
diff --git a/drivers/xen/gntalloc.c b/drivers/xen/gntalloc.c index f330a4b8b685..e8ea56583b4c 100644 --- a/drivers/xen/gntalloc.c +++ b/drivers/xen/gntalloc.c | |||
@@ -99,6 +99,12 @@ struct gntalloc_file_private_data { | |||
99 | uint64_t index; | 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 | static void __del_gref(struct gntalloc_gref *gref); | 108 | static void __del_gref(struct gntalloc_gref *gref); |
103 | 109 | ||
104 | static void do_cleanup(void) | 110 | static void do_cleanup(void) |
@@ -451,25 +457,39 @@ static long gntalloc_ioctl(struct file *filp, unsigned int cmd, | |||
451 | 457 | ||
452 | static void gntalloc_vma_open(struct vm_area_struct *vma) | 458 | static void gntalloc_vma_open(struct vm_area_struct *vma) |
453 | { | 459 | { |
454 | struct gntalloc_gref *gref = vma->vm_private_data; | 460 | struct gntalloc_vma_private_data *priv = vma->vm_private_data; |
455 | if (!gref) | 461 | |
462 | if (!priv) | ||
456 | return; | 463 | return; |
457 | 464 | ||
458 | mutex_lock(&gref_mutex); | 465 | mutex_lock(&gref_mutex); |
459 | gref->users++; | 466 | priv->users++; |
460 | mutex_unlock(&gref_mutex); | 467 | mutex_unlock(&gref_mutex); |
461 | } | 468 | } |
462 | 469 | ||
463 | static void gntalloc_vma_close(struct vm_area_struct *vma) | 470 | static void gntalloc_vma_close(struct vm_area_struct *vma) |
464 | { | 471 | { |
465 | struct gntalloc_gref *gref = vma->vm_private_data; | 472 | struct gntalloc_vma_private_data *priv = vma->vm_private_data; |
466 | if (!gref) | 473 | struct gntalloc_gref *gref, *next; |
474 | int i; | ||
475 | |||
476 | if (!priv) | ||
467 | return; | 477 | return; |
468 | 478 | ||
469 | mutex_lock(&gref_mutex); | 479 | mutex_lock(&gref_mutex); |
470 | gref->users--; | 480 | priv->users--; |
471 | if (gref->users == 0) | 481 | if (priv->users == 0) { |
472 | __del_gref(gref); | 482 | gref = priv->gref; |
483 | for (i = 0; i < priv->count; i++) { | ||
484 | gref->users--; | ||
485 | next = list_entry(gref->next_gref.next, | ||
486 | struct gntalloc_gref, next_gref); | ||
487 | if (gref->users == 0) | ||
488 | __del_gref(gref); | ||
489 | gref = next; | ||
490 | } | ||
491 | kfree(priv); | ||
492 | } | ||
473 | mutex_unlock(&gref_mutex); | 493 | mutex_unlock(&gref_mutex); |
474 | } | 494 | } |
475 | 495 | ||
@@ -481,19 +501,25 @@ static struct vm_operations_struct gntalloc_vmops = { | |||
481 | static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) | 501 | static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) |
482 | { | 502 | { |
483 | struct gntalloc_file_private_data *priv = filp->private_data; | 503 | struct gntalloc_file_private_data *priv = filp->private_data; |
504 | struct gntalloc_vma_private_data *vm_priv; | ||
484 | struct gntalloc_gref *gref; | 505 | struct gntalloc_gref *gref; |
485 | int count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; | 506 | int count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; |
486 | int rv, i; | 507 | int rv, i; |
487 | 508 | ||
488 | pr_debug("%s: priv %p, page %lu+%d\n", __func__, | ||
489 | priv, vma->vm_pgoff, count); | ||
490 | |||
491 | if (!(vma->vm_flags & VM_SHARED)) { | 509 | if (!(vma->vm_flags & VM_SHARED)) { |
492 | printk(KERN_ERR "%s: Mapping must be shared.\n", __func__); | 510 | printk(KERN_ERR "%s: Mapping must be shared.\n", __func__); |
493 | return -EINVAL; | 511 | return -EINVAL; |
494 | } | 512 | } |
495 | 513 | ||
514 | vm_priv = kmalloc(sizeof(*vm_priv), GFP_KERNEL); | ||
515 | if (!vm_priv) | ||
516 | return -ENOMEM; | ||
517 | |||
496 | mutex_lock(&gref_mutex); | 518 | mutex_lock(&gref_mutex); |
519 | |||
520 | pr_debug("%s: priv %p,%p, page %lu+%d\n", __func__, | ||
521 | priv, vm_priv, vma->vm_pgoff, count); | ||
522 | |||
497 | gref = find_grefs(priv, vma->vm_pgoff << PAGE_SHIFT, count); | 523 | gref = find_grefs(priv, vma->vm_pgoff << PAGE_SHIFT, count); |
498 | if (gref == NULL) { | 524 | if (gref == NULL) { |
499 | rv = -ENOENT; | 525 | rv = -ENOENT; |
@@ -502,9 +528,13 @@ static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) | |||
502 | goto out_unlock; | 528 | goto out_unlock; |
503 | } | 529 | } |
504 | 530 | ||
505 | vma->vm_private_data = gref; | 531 | vm_priv->gref = gref; |
532 | vm_priv->users = 1; | ||
533 | vm_priv->count = count; | ||
534 | |||
535 | vma->vm_private_data = vm_priv; | ||
506 | 536 | ||
507 | vma->vm_flags |= VM_RESERVED; | 537 | vma->vm_flags |= VM_RESERVED | VM_DONTEXPAND; |
508 | 538 | ||
509 | vma->vm_ops = &gntalloc_vmops; | 539 | vma->vm_ops = &gntalloc_vmops; |
510 | 540 | ||