fix zfs_getpages crash when called from sendfile, followup to r329363

It turns out that sendfile_swapin() has an optimization where it may
insert pointers to bogus_page into the page array that it passes to
VOP_GETPAGES.  That happens to work with buffer cache, because it
extensively uses bogus_page internally, so it has the necessary checks.
However, ZFS did not expect bogus_page as VOP_GETPAGES(9) does not
document such a (ab)use of bogus_page.

So, this commit adds checks and handling of bogus_page.

I expect that use of bogus_page with VOP_GETPAGES will get documented
sooner rather than later.

Reported by:	Andrew Reilly <areilly@bigpond.net.au>, delphij
Tested by:	Andrew Reilly <areilly@bigpond.net.au>
Requested by:	many
MFC after:	1 week
This commit is contained in:
avg 2018-05-25 07:29:52 +00:00
parent a8d82e59ae
commit 80919a9e8a

View File

@ -1732,17 +1732,21 @@ dmu_read_pages(objset_t *os, uint64_t object, vm_page_t *ma, int count,
for (mi = 0, di = 0; mi < count && di < numbufs; ) {
if (pgoff == 0) {
m = ma[mi];
vm_page_assert_xbusied(m);
ASSERT(m->valid == 0);
ASSERT(m->dirty == 0);
ASSERT(!pmap_page_is_mapped(m));
va = zfs_map_page(m, &sf);
if (m != bogus_page) {
vm_page_assert_xbusied(m);
ASSERT(m->valid == 0);
ASSERT(m->dirty == 0);
ASSERT(!pmap_page_is_mapped(m));
va = zfs_map_page(m, &sf);
}
}
if (bufoff == 0)
db = dbp[di];
ASSERT3U(IDX_TO_OFF(m->pindex) + pgoff, ==,
db->db_offset + bufoff);
if (m != bogus_page) {
ASSERT3U(IDX_TO_OFF(m->pindex) + pgoff, ==,
db->db_offset + bufoff);
}
/*
* We do not need to clamp the copy size by the file
@ -1750,13 +1754,16 @@ dmu_read_pages(objset_t *os, uint64_t object, vm_page_t *ma, int count,
* end of file anyway.
*/
tocpy = MIN(db->db_size - bufoff, PAGESIZE - pgoff);
bcopy((char *)db->db_data + bufoff, va + pgoff, tocpy);
if (m != bogus_page)
bcopy((char *)db->db_data + bufoff, va + pgoff, tocpy);
pgoff += tocpy;
ASSERT(pgoff <= PAGESIZE);
if (pgoff == PAGESIZE) {
zfs_unmap_page(sf);
m->valid = VM_PAGE_BITS_ALL;
if (m != bogus_page) {
zfs_unmap_page(sf);
m->valid = VM_PAGE_BITS_ALL;
}
ASSERT(mi < count);
mi++;
pgoff = 0;
@ -1801,6 +1808,7 @@ dmu_read_pages(objset_t *os, uint64_t object, vm_page_t *ma, int count,
}
#endif
if (pgoff != 0) {
ASSERT(m != bogus_page);
bzero(va + pgoff, PAGESIZE - pgoff);
zfs_unmap_page(sf);
m->valid = VM_PAGE_BITS_ALL;