mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] RDMA/srpt: bound the indirect descriptor table and widen the counters
@ 2026-10-21  0:18 Yehyeong Lee
  2026-10-21 16:58 ` Bart Van Assche
  0 siblings, 1 reply; 2+ messages in thread
From: Yehyeong Lee @ 2026-10-21  0:18 UTC (permalink / raw)
  To: Bart Van Assche
  Cc: Leon Romanovsky, Jason Gunthorpe, linux-rdma, target-devel,
	linux-kernel, stable, yhlee

srpt_get_desc_tbl() bounds the number of indirect data buffer descriptors
by the two counts the initiator put in the SRP_CMD, and both are u8, so it
admits an nbufs of up to 510:

	if (nbufs >
	    (srp_cmd->data_out_desc_cnt + srp_cmd->data_in_desc_cnt)) {
		...
		return -EINVAL;
	}

Nothing checks that the header and those nbufs entries are inside the
information unit that was received.  srpt_alloc_rw_ctxs() walks
idb->desc_list for nbufs entries ending at

	sizeof(struct srp_cmd) + (srp_cmd->add_cdb_len & ~3) +
	sizeof(struct srp_indirect_buf) +
	nbufs * sizeof(struct srp_direct_buf)

which is 8480 bytes for an add_cdb_len of 252 and an nbufs of 510, past
recv_ioctx->byte_len and, at the default srp_max_req_size of 8276, past
the req_buf_cache object.  An add_cdb_len of 52 already suffices.  The
immediate data branch of this same function bounds its length against
srp_max_req_size and against recv_ioctx->byte_len, so bring the indirect
branch in line.

The count also outruns the counters that track the contexts, which are u8,
so at 256 descriptors n_rw_ctx wraps to zero and srpt_release_cmd() skips
the free:

	if (ioctx->n_rw_ctx) {
		srpt_free_rw_ctxs(ch, ioctx);
		ioctx->n_rw_ctx = 0;
	}

Such a command leaks every scatterlist target_alloc_sgl() allocated, every
registration rdma_rw_ctx_init() set up, and the rw_ctxs array, whose
kfree() sits inside srpt_free_rw_ctxs().  ioctx is a ring entry the next
command on the same tag overwrites, so the array pointer is lost too.  A
count between 257 and 510 leaks 256 contexts and frees the remainder.

Both changes are needed: a 510 entry table fits a default 8276 byte IU
when add_cdb_len is zero, so the new bound accepts it and the counters
still have to hold it, and widening the counters does nothing about a
table reaching past the IU.

Check the header against byte_len before reading the table length, and the
table after the existing count guard has bounded nbufs at 510.  The bound
is the number of descriptors the remaining bytes hold, so there is no
multiplication to overflow and a negative byte_len is refused.  Widen both
counters to int, which suits every user: the loops that walk the contexts
backwards use an int index and stop at -1, the sq_wr_avail accounting
passes the value to atomic_add() and atomic_sub_return(), and the three
places that print it use %d.

The KUnit harness below is not part of this patch.  It calls
srpt_get_desc_tbl() with a fabricated SRP_CMD in a buffer from a
kmem_cache shaped like req_buf_cache, and stubs only rdma_rw_ctx_init()
and rdma_rw_ctx_destroy(), the two helpers that need a live QP.  An
add_cdb_len of 252 with an nbufs of 510 gives

  BUG: KASAN: slab-out-of-bounds in srpt_alloc_rw_ctxs+0x6ee/0x950
  Read of size 4 at addr 000000006318205c by task kunit_try_catch/33

  CPU: 0 UID: 0 PID: 33 Comm: kunit_try_catch Tainted: G                 N  7.3.0-dirty #7 VOLUNTARY
  Tainted: [N]=TEST
  Call Trace:
   [<600a43cf>] ? dump_stack_lvl+0x5e/0x7a
   [<600450d2>] ? _printk+0x0/0xd7
   [<60051cce>] ? print_report+0x14b/0x517
   [<6039355e>] ? alloc_pages_node_noprof+0xae/0x120
   [<600450d2>] ? _printk+0x0/0xd7
   [<60a5d70e>] ? srpt_alloc_rw_ctxs+0x6ee/0x950
   [<603c78eb>] ? kasan_report+0x11b/0x160
   [<60a5d70e>] ? srpt_alloc_rw_ctxs+0x6ee/0x950
   [<60a5d70e>] ? srpt_alloc_rw_ctxs+0x6ee/0x950
   [<6008fdc9>] ? srpt_test_run.constprop.0.cold+0x177/0xd8f
   [<60a5fd30>] ? srpt_test_run.constprop.0+0x0/0xea0
    [ ... ]
   [<6072718c>] ? kunit_try_run_case+0x1bc/0x310
   [<60726fd0>] ? kunit_try_run_case+0x0/0x310
   [<6072d250>] ? kunit_generic_run_threadfn_adapter+0x0/0x100
   [<6072d2cc>] ? kunit_generic_run_threadfn_adapter+0x7c/0x100
   [<6014750e>] ? kthread+0x36e/0x560
   [<601471a0>] ? kthread+0x0/0x560
   [<600a90de>] ? new_thread_handler+0xce/0x130
  The buggy address belongs to the object at 0000000063180000
   which belongs to the cache srpt-kunit-iu of size 8276
  The buggy address is located 8 bytes to the right of
   allocated 8276-byte region [0000000063180000, 0000000063182054)

  Memory state around the buggy address:
   0000000063181f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
   0000000063181f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  >0000000063182000: 00 00 00 00 00 00 00 00 00 00 04 fc fc fc fc fc
                                                      ^
   0000000063182080: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

With 256 descriptors inside the IU there is no splat, because that case is
a leak rather than an access past an object: the harness counts 256
rdma_rw_ctx_init() calls and no rdma_rw_ctx_destroy() call.  After this
patch both shapes are refused, and 1, 128, 255, 256 and 510 descriptors
that fit the IU are accepted with every context freed again.

Fixes: a42d985bd5b2 ("ib_srpt: Initial SRP Target merge for v3.3-rc1")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
v2: also bound the indirect descriptor walk against recv_ioctx->byte_len,
as raised in review; widen the counters as in v1.
---
 drivers/infiniband/ulp/srpt/ib_srpt.c | 21 +++++++++++++++++++--
 drivers/infiniband/ulp/srpt/ib_srpt.h |  4 ++--
 2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.c b/drivers/infiniband/ulp/srpt/ib_srpt.c
index 7197d95f2216b..5a12581286194 100644
--- a/drivers/infiniband/ulp/srpt/ib_srpt.c
+++ b/drivers/infiniband/ulp/srpt/ib_srpt.c
@@ -959,7 +959,7 @@ static int srpt_alloc_rw_ctxs(struct srpt_send_ioctx *ioctx,
 	struct srpt_rdma_ch *ch = ioctx->ch;
 	struct scatterlist *prev = NULL;
 	unsigned prev_nents;
-	u8 n_rdma, n_rw_ctx;
+	int n_rdma, n_rw_ctx;
 	int ret, i;
 
 	if (nbufs == 1) {
@@ -1115,7 +1115,16 @@ static int srpt_get_desc_tbl(struct srpt_recv_ioctx *recv_ioctx,
 	} else if (((srp_cmd->buf_fmt & 0xf) == SRP_DATA_DESC_INDIRECT) ||
 		   ((srp_cmd->buf_fmt >> 4) == SRP_DATA_DESC_INDIRECT)) {
 		struct srp_indirect_buf *idb = srpt_get_desc_buf(srp_cmd);
-		int nbufs = be32_to_cpu(idb->table_desc.len) /
+		int desc_off = (u8 *)idb - (u8 *)srp_cmd;
+		int nbufs, room;
+
+		if (desc_off > recv_ioctx->byte_len - (int)sizeof(*idb)) {
+			pr_err("received SRP_CMD with a truncated indirect descriptor header (%d + %zu > %d)\n",
+			       desc_off, sizeof(*idb), recv_ioctx->byte_len);
+			return -EINVAL;
+		}
+
+		nbufs = be32_to_cpu(idb->table_desc.len) /
 				sizeof(struct srp_direct_buf);
 
 		if (nbufs >
@@ -1128,6 +1137,14 @@ static int srpt_get_desc_tbl(struct srpt_recv_ioctx *recv_ioctx,
 			return -EINVAL;
 		}
 
+		room = (recv_ioctx->byte_len - desc_off - (int)sizeof(*idb)) /
+			(int)sizeof(struct srp_direct_buf);
+		if (nbufs > room) {
+			pr_err("received SRP_CMD with %d descriptors but room for %d in the IU\n",
+			       nbufs, room);
+			return -EINVAL;
+		}
+
 		*data_len = be32_to_cpu(idb->len);
 		return srpt_alloc_rw_ctxs(ioctx, idb->desc_list, nbufs,
 				sg, sg_cnt);
diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.h b/drivers/infiniband/ulp/srpt/ib_srpt.h
index 4c46b301eea18..1dce9bcb490f0 100644
--- a/drivers/infiniband/ulp/srpt/ib_srpt.h
+++ b/drivers/infiniband/ulp/srpt/ib_srpt.h
@@ -223,8 +223,8 @@ struct srpt_send_ioctx {
 	struct ib_cqe		rdma_cqe;
 	enum srpt_command_state	state;
 	struct se_cmd		cmd;
-	u8			n_rdma;
-	u8			n_rw_ctx;
+	int			n_rdma;
+	int			n_rw_ctx;
 	bool			queue_status_only;
 	u8			sense_data[TRANSPORT_SENSE_BUFFER];
 };

base-commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
-- 
2.43.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH v2] RDMA/srpt: bound the indirect descriptor table and widen the counters
  2026-10-21  0:18 [PATCH v2] RDMA/srpt: bound the indirect descriptor table and widen the counters Yehyeong Lee
@ 2026-10-21 16:58 ` Bart Van Assche
  0 siblings, 0 replies; 2+ messages in thread
From: Bart Van Assche @ 2026-10-21 16:58 UTC (permalink / raw)
  To: Yehyeong Lee
  Cc: Leon Romanovsky, Jason Gunthorpe, linux-rdma, target-devel,
	linux-kernel, stable

On 10/20/26 5:18 PM, Yehyeong Lee wrote:
>    BUG: KASAN: slab-out-of-bounds in srpt_alloc_rw_ctxs+0x6ee/0x950
>    Read of size 4 at addr 000000006318205c by task kunit_try_catch/33

Some feedback about this patch:
* Since this patch fixes two separate bugs, should it perhaps be split
   into two patches? The fixed issues are:
    - The missing bound on the descriptor table goes back to
      commit a42d985bd5b2. The original code did `memcpy(ioctx->rbufs,
      db, n_rbuf * sizeof *db)` with the same missing check.
    - The `u8` `n_rw_ctx`/`n_rdma` counters came in with commit
      b99f8e4d7bcd ("IB/srpt: convert to the generic RDMA READ/WRITE
      API").
* n_rdma can overflow without many descriptors. It is the sum of
   rdma_rw_ctx_init() return values (work requests per context), and that
   can exceed 255 even for a few large descriptors. Widening it fixes
   more than the commit message says; please mention that.
* The `idb` header ends at most at offset 320, and `MIN_MAX_REQ_SIZE` is
   996, so reading it is never out of bounds; at worst it reads stale
   data. The extra check is fine, but both checks could be folded into
   one, placed after the existing descriptor-count check.

Thanks,

Bart.

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-21 16:58 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-21  0:18 [PATCH v2] RDMA/srpt: bound the indirect descriptor table and widen the counters Yehyeong Lee
2026-10-21 16:58 ` Bart Van Assche

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®