Skip to content

Commit

Permalink
xen/gntalloc: fix reference counts on multi-page mappings
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Daniel De Graaf authored and Konrad Rzeszutek Wilk committed Dec 16, 2011
1 parent 0105d2b commit 243082e
Showing 1 changed file with 43 additions and 13 deletions.
56 changes: 43 additions & 13 deletions drivers/xen/gntalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ struct gntalloc_file_private_data {
uint64_t index;
};

struct gntalloc_vma_private_data {
struct gntalloc_gref *gref;
int users;
int count;
};

static void __del_gref(struct gntalloc_gref *gref);

static void do_cleanup(void)
Expand Down Expand Up @@ -451,25 +457,39 @@ static long gntalloc_ioctl(struct file *filp, unsigned int cmd,

static void gntalloc_vma_open(struct vm_area_struct *vma)
{
struct gntalloc_gref *gref = vma->vm_private_data;
if (!gref)
struct gntalloc_vma_private_data *priv = vma->vm_private_data;

if (!priv)
return;

mutex_lock(&gref_mutex);
gref->users++;
priv->users++;
mutex_unlock(&gref_mutex);
}

static void gntalloc_vma_close(struct vm_area_struct *vma)
{
struct gntalloc_gref *gref = vma->vm_private_data;
if (!gref)
struct gntalloc_vma_private_data *priv = vma->vm_private_data;
struct gntalloc_gref *gref, *next;
int i;

if (!priv)
return;

mutex_lock(&gref_mutex);
gref->users--;
if (gref->users == 0)
__del_gref(gref);
priv->users--;
if (priv->users == 0) {
gref = priv->gref;
for (i = 0; i < priv->count; i++) {
gref->users--;
next = list_entry(gref->next_gref.next,
struct gntalloc_gref, next_gref);
if (gref->users == 0)
__del_gref(gref);
gref = next;
}
kfree(priv);
}
mutex_unlock(&gref_mutex);
}

Expand All @@ -481,19 +501,25 @@ static struct vm_operations_struct gntalloc_vmops = {
static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct gntalloc_file_private_data *priv = filp->private_data;
struct gntalloc_vma_private_data *vm_priv;
struct gntalloc_gref *gref;
int count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
int rv, i;

pr_debug("%s: priv %p, page %lu+%d\n", __func__,
priv, vma->vm_pgoff, count);

if (!(vma->vm_flags & VM_SHARED)) {
printk(KERN_ERR "%s: Mapping must be shared.\n", __func__);
return -EINVAL;
}

vm_priv = kmalloc(sizeof(*vm_priv), GFP_KERNEL);
if (!vm_priv)
return -ENOMEM;

mutex_lock(&gref_mutex);

pr_debug("%s: priv %p,%p, page %lu+%d\n", __func__,
priv, vm_priv, vma->vm_pgoff, count);

gref = find_grefs(priv, vma->vm_pgoff << PAGE_SHIFT, count);
if (gref == NULL) {
rv = -ENOENT;
Expand All @@ -502,9 +528,13 @@ static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma)
goto out_unlock;
}

vma->vm_private_data = gref;
vm_priv->gref = gref;
vm_priv->users = 1;
vm_priv->count = count;

vma->vm_private_data = vm_priv;

vma->vm_flags |= VM_RESERVED;
vma->vm_flags |= VM_RESERVED | VM_DONTEXPAND;

vma->vm_ops = &gntalloc_vmops;

Expand Down

0 comments on commit 243082e

Please sign in to comment.