aio: fix use-after-free in aio_migratepage
Dmitry Vyukov managed to trigger a case where aio_migratepage can cause a use-after-free during teardown of the aio ring buffer's mapping. This turns out to be caused by access to the ioctx's ring_pages via the migratepage operation which was not being protected by any locks during ioctx freeing. Use the address_space's private_lock to protect use and updates of the mapping's private_data, and make ioctx teardown unlink the ioctx from the address space. Reported-by: Dmitry Vyukov <dvyukov@google.com> Tested-by: Dmitry Vyukov <dvyukov@google.com> Signed-off-by: Benjamin LaHaise <bcrl@kvack.org>
This commit is contained in:
parent
4b97280675
commit
5e9ae2e5da
52
fs/aio.c
52
fs/aio.c
@ -167,10 +167,25 @@ static int __init aio_setup(void)
|
|||||||
}
|
}
|
||||||
__initcall(aio_setup);
|
__initcall(aio_setup);
|
||||||
|
|
||||||
|
static void put_aio_ring_file(struct kioctx *ctx)
|
||||||
|
{
|
||||||
|
struct file *aio_ring_file = ctx->aio_ring_file;
|
||||||
|
if (aio_ring_file) {
|
||||||
|
truncate_setsize(aio_ring_file->f_inode, 0);
|
||||||
|
|
||||||
|
/* Prevent further access to the kioctx from migratepages */
|
||||||
|
spin_lock(&aio_ring_file->f_inode->i_mapping->private_lock);
|
||||||
|
aio_ring_file->f_inode->i_mapping->private_data = NULL;
|
||||||
|
ctx->aio_ring_file = NULL;
|
||||||
|
spin_unlock(&aio_ring_file->f_inode->i_mapping->private_lock);
|
||||||
|
|
||||||
|
fput(aio_ring_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void aio_free_ring(struct kioctx *ctx)
|
static void aio_free_ring(struct kioctx *ctx)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
struct file *aio_ring_file = ctx->aio_ring_file;
|
|
||||||
|
|
||||||
for (i = 0; i < ctx->nr_pages; i++) {
|
for (i = 0; i < ctx->nr_pages; i++) {
|
||||||
pr_debug("pid(%d) [%d] page->count=%d\n", current->pid, i,
|
pr_debug("pid(%d) [%d] page->count=%d\n", current->pid, i,
|
||||||
@ -178,14 +193,10 @@ static void aio_free_ring(struct kioctx *ctx)
|
|||||||
put_page(ctx->ring_pages[i]);
|
put_page(ctx->ring_pages[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put_aio_ring_file(ctx);
|
||||||
|
|
||||||
if (ctx->ring_pages && ctx->ring_pages != ctx->internal_pages)
|
if (ctx->ring_pages && ctx->ring_pages != ctx->internal_pages)
|
||||||
kfree(ctx->ring_pages);
|
kfree(ctx->ring_pages);
|
||||||
|
|
||||||
if (aio_ring_file) {
|
|
||||||
truncate_setsize(aio_ring_file->f_inode, 0);
|
|
||||||
fput(aio_ring_file);
|
|
||||||
ctx->aio_ring_file = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int aio_ring_mmap(struct file *file, struct vm_area_struct *vma)
|
static int aio_ring_mmap(struct file *file, struct vm_area_struct *vma)
|
||||||
@ -207,9 +218,8 @@ static int aio_set_page_dirty(struct page *page)
|
|||||||
static int aio_migratepage(struct address_space *mapping, struct page *new,
|
static int aio_migratepage(struct address_space *mapping, struct page *new,
|
||||||
struct page *old, enum migrate_mode mode)
|
struct page *old, enum migrate_mode mode)
|
||||||
{
|
{
|
||||||
struct kioctx *ctx = mapping->private_data;
|
struct kioctx *ctx;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
unsigned idx = old->index;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Writeback must be complete */
|
/* Writeback must be complete */
|
||||||
@ -224,10 +234,23 @@ static int aio_migratepage(struct address_space *mapping, struct page *new,
|
|||||||
|
|
||||||
get_page(new);
|
get_page(new);
|
||||||
|
|
||||||
spin_lock_irqsave(&ctx->completion_lock, flags);
|
/* We can potentially race against kioctx teardown here. Use the
|
||||||
migrate_page_copy(new, old);
|
* address_space's private data lock to protect the mapping's
|
||||||
ctx->ring_pages[idx] = new;
|
* private_data.
|
||||||
spin_unlock_irqrestore(&ctx->completion_lock, flags);
|
*/
|
||||||
|
spin_lock(&mapping->private_lock);
|
||||||
|
ctx = mapping->private_data;
|
||||||
|
if (ctx) {
|
||||||
|
pgoff_t idx;
|
||||||
|
spin_lock_irqsave(&ctx->completion_lock, flags);
|
||||||
|
migrate_page_copy(new, old);
|
||||||
|
idx = old->index;
|
||||||
|
if (idx < (pgoff_t)ctx->nr_pages)
|
||||||
|
ctx->ring_pages[idx] = new;
|
||||||
|
spin_unlock_irqrestore(&ctx->completion_lock, flags);
|
||||||
|
} else
|
||||||
|
rc = -EBUSY;
|
||||||
|
spin_unlock(&mapping->private_lock);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -617,8 +640,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
|
|||||||
out_freeref:
|
out_freeref:
|
||||||
free_percpu(ctx->users.pcpu_count);
|
free_percpu(ctx->users.pcpu_count);
|
||||||
out_freectx:
|
out_freectx:
|
||||||
if (ctx->aio_ring_file)
|
put_aio_ring_file(ctx);
|
||||||
fput(ctx->aio_ring_file);
|
|
||||||
kmem_cache_free(kioctx_cachep, ctx);
|
kmem_cache_free(kioctx_cachep, ctx);
|
||||||
pr_debug("error allocating ioctx %d\n", err);
|
pr_debug("error allocating ioctx %d\n", err);
|
||||||
return ERR_PTR(err);
|
return ERR_PTR(err);
|
||||||
|
Loading…
Reference in New Issue
Block a user