[PATCH v5 1/3] RDMA/srpt: bound the indirect descriptor table to the IU
From: Yehyeong Lee
Date: Sat Oct 24 2026 - 21:38:52 EST
srpt_get_desc_tbl() takes the number of INDIRECT descriptors from
idb->table_desc.len, which the initiator controls, and checks it only
against data_out_desc_cnt + data_in_desc_cnt, two more initiator fields.
Both are u8, so the pair admits up to 510 descriptors, and nothing
compares the table against the receive IU it has to live in.
srpt_alloc_rw_ctxs() then walks idb->desc_list for that many entries.
With the default srp_max_req_size of 8276 the table fits as long as the
additional CDB is short, but ADDITIONAL CDB LENGTH is an initiator field
too, and at its maximum of 252 bytes 510 descriptors end at
48 + 252 + 20 + 510 * 16 = 8480, which is past the end of the object.
BUG: KASAN: slab-out-of-bounds in srpt_alloc_rw_ctxs+0x631/0x870
Read of size 4 at addr ffff888002d6a05c by task kunit_try_catch/43
CPU: 0 UID: 0 PID: 43 Comm: kunit_try_catch Tainted: G N 7.3.0-12378-g6fba331225f5-dirty #1 PREEMPT(lazy)
Tainted: [N]=TEST
Call Trace:
<TASK>
dump_stack_lvl+0x3f/0x50
print_report+0xcd/0x620
? alloc_pages_node_noprof+0xa2/0x140
? srpt_alloc_rw_ctxs+0x631/0x870
kasan_report+0xde/0x110
? srpt_alloc_rw_ctxs+0x631/0x870
srpt_alloc_rw_ctxs+0x631/0x870
srpt_test_parse+0x114/0x580
? __pfx_srpt_test_ctx_common.constprop.0+0x10/0x10
? __pfx_srpt_test_parse+0x10/0x10
? srpt_test_ctx_init.constprop.0+0x4cb/0x6c0
srpt_test_run.constprop.0+0xc1/0xa60
? pick_task_fair+0x126/0x1950
? __pfx_srpt_test_run.constprop.0+0x10/0x10
? update_curr.constprop.0+0x4a/0x390
? __set_cpus_allowed_ptr+0x181/0x240
? __pfx_read_tsc+0x10/0x10
? ktime_get_ts64+0x192/0x250
kunit_try_run_case+0x1ca/0x4b0
? __pfx_kunit_try_run_case+0x10/0x10
? kthread_affine_node+0x24b/0x370
? __pfx_kthread_affine_node+0x10/0x10
? __pfx_kunit_try_run_case+0x10/0x10
? __pfx_kunit_generic_run_threadfn_adapter+0x10/0x10
kunit_generic_run_threadfn_adapter+0x7b/0xe0
kthread+0x36e/0x500
? __pfx_kthread+0x10/0x10
ret_from_fork+0x34d/0x620
? __pfx_ret_from_fork+0x10/0x10
? __switch_to+0x22/0xdd0
? __pfx_kthread+0x10/0x10
ret_from_fork_asm+0x19/0x30
</TASK>
Allocated by task 43:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_slab_alloc+0x6e/0x70
kmem_cache_alloc_noprof+0xfc/0x2b0
srpt_test_ctx_common.constprop.0+0x46f/0x9b0
srpt_test_ctx_init.constprop.0+0x30/0x6c0
srpt_test_run.constprop.0+0xb6/0xa60
kunit_try_run_case+0x1ca/0x4b0
kunit_generic_run_threadfn_adapter+0x7b/0xe0
kthread+0x36e/0x500
ret_from_fork+0x34d/0x620
ret_from_fork_asm+0x19/0x30
The buggy address belongs to the object at ffff888002d68000
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 [ffff888002d68000, ffff888002d6a054)
The KUnit harness that forces this is not part of this patch.
A table_desc.len below one descriptor is the other way in. It gives nbufs
zero, which the count check admits and a span check cannot catch either,
since an empty table fits in any IU. srpt_alloc_rw_ctxs() then returns
success with n_rw_ctx zero, no scatterlist is chained, and
srpt_write_pending() reaches ib_post_send() with an empty work request
list. rvt_post_send() evaluates !wr->next before the loop that tests wr
against NULL, so an rdmavt device faults there whenever its send queue is
idle.
Reject a table that does not fit by comparing the span it occupies
against byte_len, and reject an empty one. The span is the table offset
plus struct_size(idb, desc_list, nbufs), and nbufs is at most 510 once
the existing count check has passed, so it cannot saturate.
The header always lies inside the allocated buffer, since the largest
offset srpt_get_desc_buf() can return is 48 + 252 and the header is 20
bytes, so it ends at 320 while srp_max_req_size is at least
MIN_MAX_REQ_SIZE, which is 996. It does not always lie inside the bytes
the initiator actually sent, which is why the span starts at the table
offset and covers the header as well.
Fixes: a42d985bd5b2 ("ib_srpt: Initial SRP Target merge for v3.3-rc1")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
drivers/infiniband/ulp/srpt/ib_srpt.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.c b/drivers/infiniband/ulp/srpt/ib_srpt.c
index 43c3ff1ad8e7e..40d796840befc 100644
--- a/drivers/infiniband/ulp/srpt/ib_srpt.c
+++ b/drivers/infiniband/ulp/srpt/ib_srpt.c
@@ -1117,6 +1117,7 @@ static int srpt_get_desc_tbl(struct srpt_recv_ioctx *recv_ioctx,
struct srp_indirect_buf *idb = srpt_get_desc_buf(srp_cmd);
int nbufs = be32_to_cpu(idb->table_desc.len) /
sizeof(struct srp_direct_buf);
+ size_t desc_off = (uintptr_t)idb - (uintptr_t)srp_cmd;
if (nbufs >
(srp_cmd->data_out_desc_cnt + srp_cmd->data_in_desc_cnt)) {
@@ -1128,6 +1129,18 @@ static int srpt_get_desc_tbl(struct srpt_recv_ioctx *recv_ioctx,
return -EINVAL;
}
+ if (nbufs == 0) {
+ pr_err("received SRP_CMD with an empty indirect descriptor table\n");
+ return -EINVAL;
+ }
+
+ if (desc_off + struct_size(idb, desc_list, nbufs) >
+ recv_ioctx->byte_len) {
+ pr_err("received SRP_CMD indirect table (%d descriptors) that does not fit the %d-byte IU\n",
+ nbufs, recv_ioctx->byte_len);
+ return -EINVAL;
+ }
+
*data_len = be32_to_cpu(idb->len);
return srpt_alloc_rw_ctxs(ioctx, idb->desc_list, nbufs,
sg, sg_cnt);
--
2.43.0