Fix a race in release_page().

Since r354156 we may call release_page() without the page's object lock
held, specifically following the page copy during a CoW fault.
release_page() must therefore unbusy the page only after scheduling the
requeue, to avoid racing with a free of the page.  Previously, the
object lock prevented this race from occurring.

Add some assertions that were helpful in tracking this down.

Reported by:	pho, syzkaller
Tested by:	pho
Reviewed by:	alc, jeff, kib
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D22234
This commit is contained in:
markj 2019-11-06 16:59:16 +00:00
parent 8227969cfa
commit b75fed1661
2 changed files with 12 additions and 2 deletions

View File

@ -155,10 +155,14 @@ release_page(struct faultstate *fs)
{
if (fs->m != NULL) {
vm_page_xunbusy(fs->m);
/*
* fs->m's object lock might not be held, so the page must be
* kept busy until we are done with it.
*/
vm_page_lock(fs->m);
vm_page_deactivate(fs->m);
vm_page_unlock(fs->m);
vm_page_xunbusy(fs->m);
fs->m = NULL;
}
}

View File

@ -3421,7 +3421,7 @@ vm_page_dequeue(vm_page_t m)
struct vm_pagequeue *pq, *pq1;
uint8_t aflags;
KASSERT(mtx_owned(vm_page_lockptr(m)) || m->object == NULL,
KASSERT(mtx_owned(vm_page_lockptr(m)) || m->ref_count == 0,
("page %p is allocated and unlocked", m));
for (pq = vm_page_pagequeue(m);; pq = pq1) {
@ -3475,6 +3475,8 @@ vm_page_enqueue(vm_page_t m, uint8_t queue)
vm_page_assert_locked(m);
KASSERT(m->queue == PQ_NONE && (m->aflags & PGA_QUEUE_STATE_MASK) == 0,
("%s: page %p is already enqueued", __func__, m));
KASSERT(m->ref_count > 0,
("%s: page %p does not carry any references", __func__, m));
m->queue = queue;
if ((m->aflags & PGA_REQUEUE) == 0)
@ -3496,6 +3498,8 @@ vm_page_requeue(vm_page_t m)
vm_page_assert_locked(m);
KASSERT(vm_page_queue(m) != PQ_NONE,
("%s: page %p is not logically enqueued", __func__, m));
KASSERT(m->ref_count > 0,
("%s: page %p does not carry any references", __func__, m));
if ((m->aflags & PGA_REQUEUE) == 0)
vm_page_aflag_set(m, PGA_REQUEUE);
@ -3889,6 +3893,8 @@ vm_page_mvqueue(vm_page_t m, const uint8_t nqueue)
vm_page_assert_locked(m);
KASSERT((m->oflags & VPO_UNMANAGED) == 0,
("vm_page_mvqueue: page %p is unmanaged", m));
KASSERT(m->ref_count > 0,
("%s: page %p does not carry any references", __func__, m));
if (vm_page_queue(m) != nqueue) {
vm_page_dequeue(m);