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>
This commit is contained in:
Daniel De Graaf 2011-11-28 11:49:11 -05:00 коммит произвёл Konrad Rzeszutek Wilk
Родитель 0105d2b4fb
Коммит 243082e0d5
1 изменённых файлов: 43 добавлений и 13 удалений

Просмотреть файл

@ -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)
@ -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);
}
@ -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;
@ -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_flags |= VM_RESERVED;
vma->vm_private_data = vm_priv;
vma->vm_flags |= VM_RESERVED | VM_DONTEXPAND;
vma->vm_ops = &gntalloc_vmops;