@@ -1566,7 +1566,8 @@ static int fuse_get_user_pages(struct fuse_args_pages *ap, struct iov_iter *ii,
}
while (nbytes < *nbytesp && nr_pages < max_pages) {
- unsigned nfolios, i;
+ struct folio *prev_folio = NULL;
+ unsigned npages, i;
size_t start;
ret = iov_iter_extract_pages(ii, &pages,
@@ -1578,23 +1579,48 @@ static int fuse_get_user_pages(struct fuse_args_pages *ap, struct iov_iter *ii,
nbytes += ret;
- nfolios = DIV_ROUND_UP(ret + start, PAGE_SIZE);
+ npages = DIV_ROUND_UP(ret + start, PAGE_SIZE);
- for (i = 0; i < nfolios; i++) {
+ /*
+ * We must check each extracted page. We can't assume every page
+ * in a large folio is used. For example, userspace may mmap() a
+ * file PROT_WRITE, MAP_PRIVATE, and then store to the middle of
+ * a large folio, in which case the extracted pages could be
+ *
+ * folio A page 0
+ * folio A page 1
+ * folio B page 0
+ * folio A page 3
+ *
+ * where folio A belongs to the file and folio B is an anonymous
+ * COW page.
+ */
+ for (i = 0; i < npages && ret; i++) {
struct folio *folio = page_folio(pages[i]);
- unsigned int offset = start +
- (folio_page_idx(folio, pages[i]) << PAGE_SHIFT);
- unsigned int len = min_t(unsigned int, ret, PAGE_SIZE - start);
+ unsigned int offset;
+ unsigned int len;
+
+ WARN_ON(!folio);
+
+ len = min_t(unsigned int, ret, PAGE_SIZE - start);
+
+ if (folio == prev_folio && pages[i] != pages[i - 1]) {
+ WARN_ON(ap->folios[ap->num_folios - 1] != folio);
+ ap->descs[ap->num_folios - 1].length += len;
+ WARN_ON(ap->descs[ap->num_folios - 1].length > folio_size(folio));
+ } else {
+ offset = start + (folio_page_idx(folio, pages[i]) << PAGE_SHIFT);
+ ap->descs[ap->num_folios].offset = offset;
+ ap->descs[ap->num_folios].length = len;
+ ap->folios[ap->num_folios] = folio;
+ start = 0;
+ ap->num_folios++;
+ prev_folio = folio;
+ }
- ap->descs[ap->num_folios].offset = offset;
- ap->descs[ap->num_folios].length = len;
- ap->folios[ap->num_folios] = folio;
- start = 0;
ret -= len;
- ap->num_folios++;
}
-
- nr_pages += nfolios;
+ nr_pages += npages;
}
kfree(pages);