]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
nvmet-rdma: handle inline data with a nonzero offset
authorBryam Vargas <hexlabsecurity@proton.me>
Thu, 4 Jun 2026 19:36:54 +0000 (19:36 +0000)
committerKeith Busch <kbusch@kernel.org>
Tue, 9 Jun 2026 21:53:00 +0000 (14:53 -0700)
nvmet_rdma_use_inline_sg() maps the host-controlled inline data offset
into the per-command inline scatterlist.  The bounds check admits any
offset with off + len <= inline_data_size, but the mapping still assumes
the data begins in the first inline page:

sg->offset = off;
sg->length = min_t(int, len, PAGE_SIZE - off);

When a port is configured with inline_data_size > PAGE_SIZE (settable up
to max(SZ_16K, PAGE_SIZE)), an offset in (PAGE_SIZE, inline_data_size]
makes "PAGE_SIZE - off" underflow, so sg->length is set to ~4 GiB and
the block backend reads far past the first inline page.  num_pages(len)
also ignores the offset, so an in-bounds offset whose [off, off+len)
span crosses a page boundary under-counts the scatterlist.

Map the offset properly: split it into a page index and an in-page
offset, start the scatterlist at that page, and size the page count from
page_off + len.  Because the request scatterlist may now start at
inline_sg[page_idx] rather than inline_sg[0], generalize the inline-SGL
identity test in nvmet_rdma_release_rsp() to a range test; otherwise the
persistent inline scatterlist is mistaken for an allocated one and
nvmet_req_free_sgls() frees an inline page (and warns in
free_large_kmalloc()).

Fixes: 0d5ee2b2ab4f ("nvmet-rdma: support max(16KB, PAGE_SIZE) inline data")
Cc: stable@vger.kernel.org
Suggested-by: Keith Busch <kbusch@kernel.org>
Reported-by: Bryam Vargas <hexlabsecurity@proton.me>
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
Signed-off-by: Keith Busch <kbusch@kernel.org>
drivers/nvme/target/rdma.c

index ac26f4f774c4de62e163ded12bded70edbab68f7..ea1185b8267ef2857ede8181b913bfd9adbe986b 100644 (file)
@@ -666,7 +666,8 @@ static void nvmet_rdma_release_rsp(struct nvmet_rdma_rsp *rsp)
        if (rsp->n_rdma)
                nvmet_rdma_rw_ctx_destroy(rsp);
 
-       if (rsp->req.sg != rsp->cmd->inline_sg)
+       if (rsp->req.sg < rsp->cmd->inline_sg ||
+           rsp->req.sg >= rsp->cmd->inline_sg + queue->dev->inline_page_count)
                nvmet_req_free_sgls(&rsp->req);
 
        if (unlikely(!list_empty_careful(&queue->rsp_wr_wait_list)))
@@ -821,24 +822,25 @@ static void nvmet_rdma_write_data_done(struct ib_cq *cq, struct ib_wc *wc)
 static void nvmet_rdma_use_inline_sg(struct nvmet_rdma_rsp *rsp, u32 len,
                u64 off)
 {
-       int sg_count = num_pages(len);
+       u64 page_off = off % PAGE_SIZE;
+       u64 page_idx = off / PAGE_SIZE;
+       int sg_count = num_pages(page_off + len);
        struct scatterlist *sg;
        int i;
 
-       sg = rsp->cmd->inline_sg;
+       sg = &rsp->cmd->inline_sg[page_idx];
        for (i = 0; i < sg_count; i++, sg++) {
                if (i < sg_count - 1)
                        sg_unmark_end(sg);
                else
                        sg_mark_end(sg);
-               sg->offset = off;
-               sg->length = min_t(int, len, PAGE_SIZE - off);
+               sg->offset = page_off;
+               sg->length = min_t(u64, len, PAGE_SIZE - page_off);
                len -= sg->length;
-               if (!i)
-                       off = 0;
+               page_off = 0;
        }
 
-       rsp->req.sg = rsp->cmd->inline_sg;
+       rsp->req.sg = &rsp->cmd->inline_sg[page_idx];
        rsp->req.sg_cnt = sg_count;
 }