Commit bc82d122 authored by Alexey Kardashevskiy's avatar Alexey Kardashevskiy Committed by Michael Ellerman

vfio/spapr: Reference mm in tce_container

In some situations the userspace memory context may live longer than
the userspace process itself so if we need to do proper memory context
cleanup, we better have tce_container take a reference to mm_struct and
use it later when the process is gone (@current or @current->mm is NULL).

This references mm and stores the pointer in the container; this is done
in a new helper - tce_iommu_mm_set() - when one of the following happens:
- a container is enabled (IOMMU v1);
- a first attempt to pre-register memory is made (IOMMU v2);
- a DMA window is created (IOMMU v2).
The @mm stays referenced till the container is destroyed.

This replaces current->mm with container->mm everywhere except debug
prints.

This adds a check that current->mm is the same as the one stored in
the container to prevent userspace from making changes to a memory
context of other processes.

DMA map/unmap ioctls() do not check for @mm as they already check
for @enabled which is set after tce_iommu_mm_set() is called.

This does not reference a task as multiple threads within the same mm
are allowed to ioctl() to vfio and supposedly they will have same limits
and capabilities and if they do not, we'll just fail with no harm made.
Signed-off-by: default avatarAlexey Kardashevskiy <aik@ozlabs.ru>
Acked-by: default avatarAlex Williamson <alex.williamson@redhat.com>
Reviewed-by: default avatarDavid Gibson <david@gibson.dropbear.id.au>
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
parent d9c72894
...@@ -31,49 +31,49 @@ ...@@ -31,49 +31,49 @@
static void tce_iommu_detach_group(void *iommu_data, static void tce_iommu_detach_group(void *iommu_data,
struct iommu_group *iommu_group); struct iommu_group *iommu_group);
static long try_increment_locked_vm(long npages) static long try_increment_locked_vm(struct mm_struct *mm, long npages)
{ {
long ret = 0, locked, lock_limit; long ret = 0, locked, lock_limit;
if (!current || !current->mm) if (WARN_ON_ONCE(!mm))
return -ESRCH; /* process exited */ return -EPERM;
if (!npages) if (!npages)
return 0; return 0;
down_write(&current->mm->mmap_sem); down_write(&mm->mmap_sem);
locked = current->mm->locked_vm + npages; locked = mm->locked_vm + npages;
lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
if (locked > lock_limit && !capable(CAP_IPC_LOCK)) if (locked > lock_limit && !capable(CAP_IPC_LOCK))
ret = -ENOMEM; ret = -ENOMEM;
else else
current->mm->locked_vm += npages; mm->locked_vm += npages;
pr_debug("[%d] RLIMIT_MEMLOCK +%ld %ld/%ld%s\n", current->pid, pr_debug("[%d] RLIMIT_MEMLOCK +%ld %ld/%ld%s\n", current->pid,
npages << PAGE_SHIFT, npages << PAGE_SHIFT,
current->mm->locked_vm << PAGE_SHIFT, mm->locked_vm << PAGE_SHIFT,
rlimit(RLIMIT_MEMLOCK), rlimit(RLIMIT_MEMLOCK),
ret ? " - exceeded" : ""); ret ? " - exceeded" : "");
up_write(&current->mm->mmap_sem); up_write(&mm->mmap_sem);
return ret; return ret;
} }
static void decrement_locked_vm(long npages) static void decrement_locked_vm(struct mm_struct *mm, long npages)
{ {
if (!current || !current->mm || !npages) if (!mm || !npages)
return; /* process exited */ return;
down_write(&current->mm->mmap_sem); down_write(&mm->mmap_sem);
if (WARN_ON_ONCE(npages > current->mm->locked_vm)) if (WARN_ON_ONCE(npages > mm->locked_vm))
npages = current->mm->locked_vm; npages = mm->locked_vm;
current->mm->locked_vm -= npages; mm->locked_vm -= npages;
pr_debug("[%d] RLIMIT_MEMLOCK -%ld %ld/%ld\n", current->pid, pr_debug("[%d] RLIMIT_MEMLOCK -%ld %ld/%ld\n", current->pid,
npages << PAGE_SHIFT, npages << PAGE_SHIFT,
current->mm->locked_vm << PAGE_SHIFT, mm->locked_vm << PAGE_SHIFT,
rlimit(RLIMIT_MEMLOCK)); rlimit(RLIMIT_MEMLOCK));
up_write(&current->mm->mmap_sem); up_write(&mm->mmap_sem);
} }
/* /*
...@@ -99,26 +99,38 @@ struct tce_container { ...@@ -99,26 +99,38 @@ struct tce_container {
bool v2; bool v2;
bool def_window_pending; bool def_window_pending;
unsigned long locked_pages; unsigned long locked_pages;
struct mm_struct *mm;
struct iommu_table *tables[IOMMU_TABLE_GROUP_MAX_TABLES]; struct iommu_table *tables[IOMMU_TABLE_GROUP_MAX_TABLES];
struct list_head group_list; struct list_head group_list;
}; };
static long tce_iommu_mm_set(struct tce_container *container)
{
if (container->mm) {
if (container->mm == current->mm)
return 0;
return -EPERM;
}
BUG_ON(!current->mm);
container->mm = current->mm;
atomic_inc(&container->mm->mm_count);
return 0;
}
static long tce_iommu_unregister_pages(struct tce_container *container, static long tce_iommu_unregister_pages(struct tce_container *container,
__u64 vaddr, __u64 size) __u64 vaddr, __u64 size)
{ {
struct mm_iommu_table_group_mem_t *mem; struct mm_iommu_table_group_mem_t *mem;
if (!current || !current->mm)
return -ESRCH; /* process exited */
if ((vaddr & ~PAGE_MASK) || (size & ~PAGE_MASK)) if ((vaddr & ~PAGE_MASK) || (size & ~PAGE_MASK))
return -EINVAL; return -EINVAL;
mem = mm_iommu_find(current->mm, vaddr, size >> PAGE_SHIFT); mem = mm_iommu_find(container->mm, vaddr, size >> PAGE_SHIFT);
if (!mem) if (!mem)
return -ENOENT; return -ENOENT;
return mm_iommu_put(current->mm, mem); return mm_iommu_put(container->mm, mem);
} }
static long tce_iommu_register_pages(struct tce_container *container, static long tce_iommu_register_pages(struct tce_container *container,
...@@ -128,14 +140,11 @@ static long tce_iommu_register_pages(struct tce_container *container, ...@@ -128,14 +140,11 @@ static long tce_iommu_register_pages(struct tce_container *container,
struct mm_iommu_table_group_mem_t *mem = NULL; struct mm_iommu_table_group_mem_t *mem = NULL;
unsigned long entries = size >> PAGE_SHIFT; unsigned long entries = size >> PAGE_SHIFT;
if (!current || !current->mm)
return -ESRCH; /* process exited */
if ((vaddr & ~PAGE_MASK) || (size & ~PAGE_MASK) || if ((vaddr & ~PAGE_MASK) || (size & ~PAGE_MASK) ||
((vaddr + size) < vaddr)) ((vaddr + size) < vaddr))
return -EINVAL; return -EINVAL;
ret = mm_iommu_get(current->mm, vaddr, entries, &mem); ret = mm_iommu_get(container->mm, vaddr, entries, &mem);
if (ret) if (ret)
return ret; return ret;
...@@ -144,7 +153,8 @@ static long tce_iommu_register_pages(struct tce_container *container, ...@@ -144,7 +153,8 @@ static long tce_iommu_register_pages(struct tce_container *container,
return 0; return 0;
} }
static long tce_iommu_userspace_view_alloc(struct iommu_table *tbl) static long tce_iommu_userspace_view_alloc(struct iommu_table *tbl,
struct mm_struct *mm)
{ {
unsigned long cb = _ALIGN_UP(sizeof(tbl->it_userspace[0]) * unsigned long cb = _ALIGN_UP(sizeof(tbl->it_userspace[0]) *
tbl->it_size, PAGE_SIZE); tbl->it_size, PAGE_SIZE);
...@@ -153,13 +163,13 @@ static long tce_iommu_userspace_view_alloc(struct iommu_table *tbl) ...@@ -153,13 +163,13 @@ static long tce_iommu_userspace_view_alloc(struct iommu_table *tbl)
BUG_ON(tbl->it_userspace); BUG_ON(tbl->it_userspace);
ret = try_increment_locked_vm(cb >> PAGE_SHIFT); ret = try_increment_locked_vm(mm, cb >> PAGE_SHIFT);
if (ret) if (ret)
return ret; return ret;
uas = vzalloc(cb); uas = vzalloc(cb);
if (!uas) { if (!uas) {
decrement_locked_vm(cb >> PAGE_SHIFT); decrement_locked_vm(mm, cb >> PAGE_SHIFT);
return -ENOMEM; return -ENOMEM;
} }
tbl->it_userspace = uas; tbl->it_userspace = uas;
...@@ -167,7 +177,8 @@ static long tce_iommu_userspace_view_alloc(struct iommu_table *tbl) ...@@ -167,7 +177,8 @@ static long tce_iommu_userspace_view_alloc(struct iommu_table *tbl)
return 0; return 0;
} }
static void tce_iommu_userspace_view_free(struct iommu_table *tbl) static void tce_iommu_userspace_view_free(struct iommu_table *tbl,
struct mm_struct *mm)
{ {
unsigned long cb = _ALIGN_UP(sizeof(tbl->it_userspace[0]) * unsigned long cb = _ALIGN_UP(sizeof(tbl->it_userspace[0]) *
tbl->it_size, PAGE_SIZE); tbl->it_size, PAGE_SIZE);
...@@ -177,7 +188,7 @@ static void tce_iommu_userspace_view_free(struct iommu_table *tbl) ...@@ -177,7 +188,7 @@ static void tce_iommu_userspace_view_free(struct iommu_table *tbl)
vfree(tbl->it_userspace); vfree(tbl->it_userspace);
tbl->it_userspace = NULL; tbl->it_userspace = NULL;
decrement_locked_vm(cb >> PAGE_SHIFT); decrement_locked_vm(mm, cb >> PAGE_SHIFT);
} }
static bool tce_page_is_contained(struct page *page, unsigned page_shift) static bool tce_page_is_contained(struct page *page, unsigned page_shift)
...@@ -237,9 +248,6 @@ static int tce_iommu_enable(struct tce_container *container) ...@@ -237,9 +248,6 @@ static int tce_iommu_enable(struct tce_container *container)
struct iommu_table_group *table_group; struct iommu_table_group *table_group;
struct tce_iommu_group *tcegrp; struct tce_iommu_group *tcegrp;
if (!current->mm)
return -ESRCH; /* process exited */
if (container->enabled) if (container->enabled)
return -EBUSY; return -EBUSY;
...@@ -284,8 +292,12 @@ static int tce_iommu_enable(struct tce_container *container) ...@@ -284,8 +292,12 @@ static int tce_iommu_enable(struct tce_container *container)
if (!table_group->tce32_size) if (!table_group->tce32_size)
return -EPERM; return -EPERM;
ret = tce_iommu_mm_set(container);
if (ret)
return ret;
locked = table_group->tce32_size >> PAGE_SHIFT; locked = table_group->tce32_size >> PAGE_SHIFT;
ret = try_increment_locked_vm(locked); ret = try_increment_locked_vm(container->mm, locked);
if (ret) if (ret)
return ret; return ret;
...@@ -303,10 +315,8 @@ static void tce_iommu_disable(struct tce_container *container) ...@@ -303,10 +315,8 @@ static void tce_iommu_disable(struct tce_container *container)
container->enabled = false; container->enabled = false;
if (!current->mm) BUG_ON(!container->mm);
return; decrement_locked_vm(container->mm, container->locked_pages);
decrement_locked_vm(container->locked_pages);
} }
static void *tce_iommu_open(unsigned long arg) static void *tce_iommu_open(unsigned long arg)
...@@ -333,7 +343,8 @@ static void *tce_iommu_open(unsigned long arg) ...@@ -333,7 +343,8 @@ static void *tce_iommu_open(unsigned long arg)
static int tce_iommu_clear(struct tce_container *container, static int tce_iommu_clear(struct tce_container *container,
struct iommu_table *tbl, struct iommu_table *tbl,
unsigned long entry, unsigned long pages); unsigned long entry, unsigned long pages);
static void tce_iommu_free_table(struct iommu_table *tbl); static void tce_iommu_free_table(struct tce_container *container,
struct iommu_table *tbl);
static void tce_iommu_release(void *iommu_data) static void tce_iommu_release(void *iommu_data)
{ {
...@@ -358,10 +369,12 @@ static void tce_iommu_release(void *iommu_data) ...@@ -358,10 +369,12 @@ static void tce_iommu_release(void *iommu_data)
continue; continue;
tce_iommu_clear(container, tbl, tbl->it_offset, tbl->it_size); tce_iommu_clear(container, tbl, tbl->it_offset, tbl->it_size);
tce_iommu_free_table(tbl); tce_iommu_free_table(container, tbl);
} }
tce_iommu_disable(container); tce_iommu_disable(container);
if (container->mm)
mmdrop(container->mm);
mutex_destroy(&container->lock); mutex_destroy(&container->lock);
kfree(container); kfree(container);
...@@ -376,13 +389,14 @@ static void tce_iommu_unuse_page(struct tce_container *container, ...@@ -376,13 +389,14 @@ static void tce_iommu_unuse_page(struct tce_container *container,
put_page(page); put_page(page);
} }
static int tce_iommu_prereg_ua_to_hpa(unsigned long tce, unsigned long size, static int tce_iommu_prereg_ua_to_hpa(struct tce_container *container,
unsigned long tce, unsigned long size,
unsigned long *phpa, struct mm_iommu_table_group_mem_t **pmem) unsigned long *phpa, struct mm_iommu_table_group_mem_t **pmem)
{ {
long ret = 0; long ret = 0;
struct mm_iommu_table_group_mem_t *mem; struct mm_iommu_table_group_mem_t *mem;
mem = mm_iommu_lookup(current->mm, tce, size); mem = mm_iommu_lookup(container->mm, tce, size);
if (!mem) if (!mem)
return -EINVAL; return -EINVAL;
...@@ -395,18 +409,18 @@ static int tce_iommu_prereg_ua_to_hpa(unsigned long tce, unsigned long size, ...@@ -395,18 +409,18 @@ static int tce_iommu_prereg_ua_to_hpa(unsigned long tce, unsigned long size,
return 0; return 0;
} }
static void tce_iommu_unuse_page_v2(struct iommu_table *tbl, static void tce_iommu_unuse_page_v2(struct tce_container *container,
unsigned long entry) struct iommu_table *tbl, unsigned long entry)
{ {
struct mm_iommu_table_group_mem_t *mem = NULL; struct mm_iommu_table_group_mem_t *mem = NULL;
int ret; int ret;
unsigned long hpa = 0; unsigned long hpa = 0;
unsigned long *pua = IOMMU_TABLE_USERSPACE_ENTRY(tbl, entry); unsigned long *pua = IOMMU_TABLE_USERSPACE_ENTRY(tbl, entry);
if (!pua || !current || !current->mm) if (!pua)
return; return;
ret = tce_iommu_prereg_ua_to_hpa(*pua, IOMMU_PAGE_SIZE(tbl), ret = tce_iommu_prereg_ua_to_hpa(container, *pua, IOMMU_PAGE_SIZE(tbl),
&hpa, &mem); &hpa, &mem);
if (ret) if (ret)
pr_debug("%s: tce %lx at #%lx was not cached, ret=%d\n", pr_debug("%s: tce %lx at #%lx was not cached, ret=%d\n",
...@@ -436,7 +450,7 @@ static int tce_iommu_clear(struct tce_container *container, ...@@ -436,7 +450,7 @@ static int tce_iommu_clear(struct tce_container *container,
continue; continue;
if (container->v2) { if (container->v2) {
tce_iommu_unuse_page_v2(tbl, entry); tce_iommu_unuse_page_v2(container, tbl, entry);
continue; continue;
} }
...@@ -517,7 +531,7 @@ static long tce_iommu_build_v2(struct tce_container *container, ...@@ -517,7 +531,7 @@ static long tce_iommu_build_v2(struct tce_container *container,
enum dma_data_direction dirtmp; enum dma_data_direction dirtmp;
if (!tbl->it_userspace) { if (!tbl->it_userspace) {
ret = tce_iommu_userspace_view_alloc(tbl); ret = tce_iommu_userspace_view_alloc(tbl, container->mm);
if (ret) if (ret)
return ret; return ret;
} }
...@@ -527,8 +541,8 @@ static long tce_iommu_build_v2(struct tce_container *container, ...@@ -527,8 +541,8 @@ static long tce_iommu_build_v2(struct tce_container *container,
unsigned long *pua = IOMMU_TABLE_USERSPACE_ENTRY(tbl, unsigned long *pua = IOMMU_TABLE_USERSPACE_ENTRY(tbl,
entry + i); entry + i);
ret = tce_iommu_prereg_ua_to_hpa(tce, IOMMU_PAGE_SIZE(tbl), ret = tce_iommu_prereg_ua_to_hpa(container,
&hpa, &mem); tce, IOMMU_PAGE_SIZE(tbl), &hpa, &mem);
if (ret) if (ret)
break; break;
...@@ -549,7 +563,7 @@ static long tce_iommu_build_v2(struct tce_container *container, ...@@ -549,7 +563,7 @@ static long tce_iommu_build_v2(struct tce_container *container,
ret = iommu_tce_xchg(tbl, entry + i, &hpa, &dirtmp); ret = iommu_tce_xchg(tbl, entry + i, &hpa, &dirtmp);
if (ret) { if (ret) {
/* dirtmp cannot be DMA_NONE here */ /* dirtmp cannot be DMA_NONE here */
tce_iommu_unuse_page_v2(tbl, entry + i); tce_iommu_unuse_page_v2(container, tbl, entry + i);
pr_err("iommu_tce: %s failed ioba=%lx, tce=%lx, ret=%ld\n", pr_err("iommu_tce: %s failed ioba=%lx, tce=%lx, ret=%ld\n",
__func__, entry << tbl->it_page_shift, __func__, entry << tbl->it_page_shift,
tce, ret); tce, ret);
...@@ -557,7 +571,7 @@ static long tce_iommu_build_v2(struct tce_container *container, ...@@ -557,7 +571,7 @@ static long tce_iommu_build_v2(struct tce_container *container,
} }
if (dirtmp != DMA_NONE) if (dirtmp != DMA_NONE)
tce_iommu_unuse_page_v2(tbl, entry + i); tce_iommu_unuse_page_v2(container, tbl, entry + i);
*pua = tce; *pua = tce;
...@@ -585,7 +599,7 @@ static long tce_iommu_create_table(struct tce_container *container, ...@@ -585,7 +599,7 @@ static long tce_iommu_create_table(struct tce_container *container,
if (!table_size) if (!table_size)
return -EINVAL; return -EINVAL;
ret = try_increment_locked_vm(table_size >> PAGE_SHIFT); ret = try_increment_locked_vm(container->mm, table_size >> PAGE_SHIFT);
if (ret) if (ret)
return ret; return ret;
...@@ -598,13 +612,14 @@ static long tce_iommu_create_table(struct tce_container *container, ...@@ -598,13 +612,14 @@ static long tce_iommu_create_table(struct tce_container *container,
return ret; return ret;
} }
static void tce_iommu_free_table(struct iommu_table *tbl) static void tce_iommu_free_table(struct tce_container *container,
struct iommu_table *tbl)
{ {
unsigned long pages = tbl->it_allocated_size >> PAGE_SHIFT; unsigned long pages = tbl->it_allocated_size >> PAGE_SHIFT;
tce_iommu_userspace_view_free(tbl); tce_iommu_userspace_view_free(tbl, container->mm);
tbl->it_ops->free(tbl); tbl->it_ops->free(tbl);
decrement_locked_vm(pages); decrement_locked_vm(container->mm, pages);
} }
static long tce_iommu_create_window(struct tce_container *container, static long tce_iommu_create_window(struct tce_container *container,
...@@ -667,7 +682,7 @@ static long tce_iommu_create_window(struct tce_container *container, ...@@ -667,7 +682,7 @@ static long tce_iommu_create_window(struct tce_container *container,
table_group = iommu_group_get_iommudata(tcegrp->grp); table_group = iommu_group_get_iommudata(tcegrp->grp);
table_group->ops->unset_window(table_group, num); table_group->ops->unset_window(table_group, num);
} }
tce_iommu_free_table(tbl); tce_iommu_free_table(container, tbl);
return ret; return ret;
} }
...@@ -705,7 +720,7 @@ static long tce_iommu_remove_window(struct tce_container *container, ...@@ -705,7 +720,7 @@ static long tce_iommu_remove_window(struct tce_container *container,
/* Free table */ /* Free table */
tce_iommu_clear(container, tbl, tbl->it_offset, tbl->it_size); tce_iommu_clear(container, tbl, tbl->it_offset, tbl->it_size);
tce_iommu_free_table(tbl); tce_iommu_free_table(container, tbl);
container->tables[num] = NULL; container->tables[num] = NULL;
return 0; return 0;
...@@ -760,7 +775,17 @@ static long tce_iommu_ioctl(void *iommu_data, ...@@ -760,7 +775,17 @@ static long tce_iommu_ioctl(void *iommu_data,
} }
return (ret < 0) ? 0 : ret; return (ret < 0) ? 0 : ret;
}
/*
* Sanity check to prevent one userspace from manipulating
* another userspace mm.
*/
BUG_ON(!container);
if (container->mm && container->mm != current->mm)
return -EPERM;
switch (cmd) {
case VFIO_IOMMU_SPAPR_TCE_GET_INFO: { case VFIO_IOMMU_SPAPR_TCE_GET_INFO: {
struct vfio_iommu_spapr_tce_info info; struct vfio_iommu_spapr_tce_info info;
struct tce_iommu_group *tcegrp; struct tce_iommu_group *tcegrp;
...@@ -929,6 +954,10 @@ static long tce_iommu_ioctl(void *iommu_data, ...@@ -929,6 +954,10 @@ static long tce_iommu_ioctl(void *iommu_data,
minsz = offsetofend(struct vfio_iommu_spapr_register_memory, minsz = offsetofend(struct vfio_iommu_spapr_register_memory,
size); size);
ret = tce_iommu_mm_set(container);
if (ret)
return ret;
if (copy_from_user(&param, (void __user *)arg, minsz)) if (copy_from_user(&param, (void __user *)arg, minsz))
return -EFAULT; return -EFAULT;
...@@ -952,6 +981,9 @@ static long tce_iommu_ioctl(void *iommu_data, ...@@ -952,6 +981,9 @@ static long tce_iommu_ioctl(void *iommu_data,
if (!container->v2) if (!container->v2)
break; break;
if (!container->mm)
return -EPERM;
minsz = offsetofend(struct vfio_iommu_spapr_register_memory, minsz = offsetofend(struct vfio_iommu_spapr_register_memory,
size); size);
...@@ -1010,6 +1042,10 @@ static long tce_iommu_ioctl(void *iommu_data, ...@@ -1010,6 +1042,10 @@ static long tce_iommu_ioctl(void *iommu_data,
if (!container->v2) if (!container->v2)
break; break;
ret = tce_iommu_mm_set(container);
if (ret)
return ret;
if (!tce_groups_attached(container)) if (!tce_groups_attached(container))
return -ENXIO; return -ENXIO;
...@@ -1048,6 +1084,10 @@ static long tce_iommu_ioctl(void *iommu_data, ...@@ -1048,6 +1084,10 @@ static long tce_iommu_ioctl(void *iommu_data,
if (!container->v2) if (!container->v2)
break; break;
ret = tce_iommu_mm_set(container);
if (ret)
return ret;
if (!tce_groups_attached(container)) if (!tce_groups_attached(container))
return -ENXIO; return -ENXIO;
...@@ -1093,7 +1133,7 @@ static void tce_iommu_release_ownership(struct tce_container *container, ...@@ -1093,7 +1133,7 @@ static void tce_iommu_release_ownership(struct tce_container *container,
continue; continue;
tce_iommu_clear(container, tbl, tbl->it_offset, tbl->it_size); tce_iommu_clear(container, tbl, tbl->it_offset, tbl->it_size);
tce_iommu_userspace_view_free(tbl); tce_iommu_userspace_view_free(tbl, container->mm);
if (tbl->it_map) if (tbl->it_map)
iommu_release_ownership(tbl); iommu_release_ownership(tbl);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment