vhost_scsi_map_to_sgl() takes references on guest buffer pages with
iov_iter_get_pages2() (FOLL_GET) and then performs DMA to/from them.
Per Documentation/core-api/pin_user_pages.rst (Case 1: Direct IO),
pages that are the target of DMA must be pinned with FOLL_PIN instead.
Only FOLL_PIN pins are visible to folio_maybe_dma_pinned(), which fork()
uses to copy rather than COW-share anonymous pages under DMA, and only
FOLL_PIN guarantees a pinned anonymous page is exclusive to the process,
so a later write fault cannot replace it while DMA is in flight.

Switch to iov_iter_extract_pages(), which pins user-backed iterators,
and release the pages with unpin_user_page(). Kernel-backed iterators
take neither a reference nor a pin, so track with
iov_iter_extract_will_pin() whether an unpin is needed.

Cc: Jason Wang <[email protected]>
Cc: Mike Christie <[email protected]>
Cc: John Hubbard <[email protected]>
Cc: Michael S. Tsirkin <[email protected]>
Cc: [email protected]
Signed-off-by: Karthikeyan KS <[email protected]>
---
v1 -> v2:
 - Rebased on vhost next. Sashiko reported that the revert loop in
   vhost_scsi_map_to_sgl() leaks a page when the misaligned-I/O check
   trips on the first page with a non-zero offset. That bug is older
   than this patch (the loop leaked a put_page() reference) and is fixed
   in vhost next by Jia Jia's 058fc59326af ("vhost-scsi: fix page
   reference leak in misaligned I/O fallback"). On top of it, this patch
   only calls unpin_user_page() instead of put_page() in that loop.
 - Reworded the commit message to describe FOLL_GET and FOLL_PIN more
   accurately.

Testing: in QEMU (fileio backend), a 1 MiB write at page offset 3840 is
257 pages, more than BIO_MAX_VECS, so the misaligned-I/O check trips on
the first page and dmesg shows the "vhost-scsi detected misaligned IO"
warning. On v1 (before 058fc59326af) the nr_foll_pin_acquired and
nr_foll_pin_released counters end at 257 and 256 for each such command;
with v2 on vhost next they balance. Plain (256 pages) and T10-PI (260
pages) writes balance too.

v1:       
https://lore.kernel.org/[email protected]
RESEND:   
https://lore.kernel.org/[email protected]
review:   https://lore.kernel.org/[email protected]
leak fix: https://lore.kernel.org/[email protected]

 drivers/vhost/scsi.c | 50 +++++++++++++++++++++++++++++++-------------
 1 file changed, 35 insertions(+), 15 deletions(-)

diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c
index e44044449cef..8beeff06a7a7 100644
--- a/drivers/vhost/scsi.c
+++ b/drivers/vhost/scsi.c
@@ -115,6 +115,8 @@ struct vhost_scsi_cmd {
        u32 tvc_sgl_count;
        u32 tvc_prot_sgl_count;
        u32 copied_iov:1;
+       u32 tvc_need_unpin:1;
+       u32 tvc_prot_need_unpin:1;
        const void *read_iov;
        struct iov_iter *read_iter;
        struct scatterlist *sgl;
@@ -463,8 +465,13 @@ static void vhost_scsi_release_cmd_res(struct se_cmd 
*se_cmd)
 
                        if (tv_cmd->copied_iov)
                                __free_page(page);
-                       else
-                               put_page(page);
+                       else if (tv_cmd->tvc_need_unpin)
+                               unpin_user_page(page);
+                       /*
+                        * Else: iov_iter_extract_pages() took neither a ref
+                        * nor a pin on this page (ITER_KVEC/BVEC/etc), so
+                        * there is nothing to release here.
+                        */
                }
                kfree(tv_cmd->read_iter);
                kfree(tv_cmd->read_iov);
@@ -473,8 +480,8 @@ static void vhost_scsi_release_cmd_res(struct se_cmd 
*se_cmd)
        if (tv_cmd->tvc_prot_sgl_count) {
                for_each_sgtable_sg(&tv_cmd->prot_table, sg, i) {
                        page = sg_page(sg);
-                       if (page)
-                               put_page(page);
+                       if (page && tv_cmd->tvc_prot_need_unpin)
+                               unpin_user_page(page);
                }
                sg_free_table_chained(&tv_cmd->prot_table, vs->inline_sg_cnt);
        }
@@ -820,7 +827,8 @@ vhost_scsi_get_cmd(struct vhost_virtqueue *vq, u64 scsi_tag)
 
 static void vhost_scsi_revert_map_iov_to_sgl(struct iov_iter *iter,
                                             struct scatterlist *curr,
-                                            struct scatterlist *end)
+                                            struct scatterlist *end,
+                                            bool need_unpin)
 {
        size_t revert_bytes = 0;
        struct page *page;
@@ -829,7 +837,8 @@ static void vhost_scsi_revert_map_iov_to_sgl(struct 
iov_iter *iter,
                page = sg_page(curr);
 
                if (page) {
-                       put_page(page);
+                       if (need_unpin)
+                               unpin_user_page(page);
                        revert_bytes += curr->length;
                }
                /* Clear so we can re-use it for the copy path */
@@ -859,9 +868,9 @@ vhost_scsi_map_to_sgl(struct vhost_scsi_cmd *cmd,
        size_t offset;
        unsigned int n, npages = 0;
 
-       bytes = iov_iter_get_pages2(iter, pages, LONG_MAX,
-                               VHOST_SCSI_PREALLOC_UPAGES, &offset);
-       /* No pages were pinned */
+       bytes = iov_iter_extract_pages(iter, &pages, LONG_MAX,
+                                      VHOST_SCSI_PREALLOC_UPAGES, 0, &offset);
+       /* No pages were extracted */
        if (bytes <= 0)
                return bytes < 0 ? bytes : -EFAULT;
 
@@ -884,7 +893,7 @@ vhost_scsi_map_to_sgl(struct vhost_scsi_cmd *cmd,
                    cmd->tvc_sgl_count > BIO_MAX_VECS) {
                        WARN_ONCE(true,
                                  "vhost-scsi detected misaligned IO. 
Performance may be degraded.");
-                       goto revert_iter_get_pages;
+                       goto revert_iter_extract_pages;
                }
 
                sg_set_page(sg, pages[npages++], n, offset);
@@ -896,8 +905,10 @@ vhost_scsi_map_to_sgl(struct vhost_scsi_cmd *cmd,
        *sgl = sg;
        return npages;
 
-revert_iter_get_pages:
-       vhost_scsi_revert_map_iov_to_sgl(iter, *sgl, sg);
+revert_iter_extract_pages:
+       vhost_scsi_revert_map_iov_to_sgl(iter, *sgl, sg,
+                                        is_prot ? cmd->tvc_prot_need_unpin :
+                                                  cmd->tvc_need_unpin);
 
        iov_iter_revert(iter, bytes);
        /*
@@ -907,7 +918,9 @@ vhost_scsi_map_to_sgl(struct vhost_scsi_cmd *cmd,
        while (bytes) {
                n = min_t(unsigned int, PAGE_SIZE - offset, bytes);
 
-               put_page(pages[npages++]);
+               if (is_prot ? cmd->tvc_prot_need_unpin : cmd->tvc_need_unpin)
+                       unpin_user_page(pages[npages]);
+               npages++;
                bytes -= n;
                offset = 0;
        }
@@ -983,6 +996,7 @@ vhost_scsi_copy_iov_to_sgl(struct vhost_scsi_cmd *cmd, 
struct iov_iter *iter,
        }
 
        cmd->copied_iov = 1;
+       cmd->tvc_need_unpin = 0;
        return 0;
 
 err:
@@ -1007,11 +1021,17 @@ vhost_scsi_map_iov_to_sgl(struct vhost_scsi_cmd *cmd, 
struct iov_iter *iter,
        struct scatterlist *sg = sg_table->sgl;
        int ret;
 
+       if (is_prot)
+               cmd->tvc_prot_need_unpin = iov_iter_extract_will_pin(iter);
+       else
+               cmd->tvc_need_unpin = iov_iter_extract_will_pin(iter);
+
        while (iov_iter_count(iter)) {
                ret = vhost_scsi_map_to_sgl(cmd, iter, sg_table, &sg, is_prot);
                if (ret < 0) {
                        vhost_scsi_revert_map_iov_to_sgl(iter, sg_table->sgl,
-                                                        sg);
+                                       sg, is_prot ? cmd->tvc_prot_need_unpin :
+                                                     cmd->tvc_need_unpin);
                        return ret;
                }
        }
@@ -1449,7 +1469,7 @@ vhost_scsi_handle_vq(struct vhost_scsi *vs, struct 
vhost_virtqueue *vq)
                /*
                 * If T10_PI header + payload is present, setup prot_iter values
                 * and recalculate data_iter for vhost_scsi_mapal() mapping to
-                * host scatterlists via get_user_pages_fast().
+                * host scatterlists via iov_iter_extract_pages().
                 */
                if (t10_pi) {
                        if (v_req_pi.pi_bytesout) {

base-commit: 1a577f9402c1780cc1e663f48d5dd54c02051b09
-- 
2.43.0


Reply via email to