mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 03:35:32 -04:00
svcrdma: Fix offset arithmetic in read_chunk_range
svc_rdma_read_chunk_range() walks a Read chunk's segment list to
build a sub-range starting at byte offset and spanning length bytes
for a Position-Zero or Call chunk. Two arithmetic defects in the
per-segment loop produce wrong DMA lengths and a u32 underflow:
pcl_for_each_segment(segment, chunk) {
if (offset > segment->rs_length) {
offset -= segment->rs_length;
continue;
}
dummy.rs_handle = segment->rs_handle;
dummy.rs_length = min_t(u32, length,
segment->rs_length) - offset;
dummy.rs_offset = segment->rs_offset + offset;
First, the skip predicate uses '>' instead of '>='. When offset
equals the segment's full rs_length, the segment is fully consumed
and should be skipped, but the loop falls through into the body.
The resulting dummy.rs_length is min_t(u32, length, rs_length) -
rs_length, which underflows to a near-UINT_MAX u32 when length is
smaller than rs_length, or is zero otherwise.
Second, the length formula subtracts offset from the min_t() result
rather than from segment->rs_length before the cap. For offset > 0
the segment's residual is rs_length - offset, not rs_length, so the
cap must be applied to the residual. With the current bracketing,
whenever length is smaller than rs_length - offset the per-segment
length becomes length - offset instead of length, silently dropping
offset bytes from the rebuilt chunk. Combined with the boundary
case above it also enables the u32 underflow path, which propagates
a huge nr_bvec into svc_rdma_build_read_segment() and a multi-MiB
kmalloc_array_node() in svc_rdma_get_rw_ctxt().
Additionally, svc_rdma_read_call_chunk() can invoke this function
with length == 0 when the last Read chunk ends exactly at the end
of the Call chunk. With the corrected >= predicate, every segment
is skipped and the function returns the initial -EINVAL, rejecting
a valid request. Return success immediately when length is zero.
Also break out of the loop once length is fully consumed to avoid
passing zero-length segments to svc_rdma_build_read_segment().
Fix by using '>=' so a fully-consumed segment is skipped, by
moving '- offset' inside min_t() so the cap is applied to the
segment's residual length, by returning success for zero-length
requests, and by stopping iteration when the requested range has
been consumed.
Fixes: d7cc739726 ("svcrdma: support multiple Read chunks per RPC")
Cc: stable@vger.kernel.org
Assisted-by: kres (claude-opus-4-7)
Signed-off-by: Chris Mason <clm@meta.com>
Acked-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-2-e251306ccca9@oracle.com
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
@@ -1009,17 +1009,20 @@ static int svc_rdma_read_chunk_range(struct svc_rqst *rqstp,
|
||||
const struct svc_rdma_segment *segment;
|
||||
int ret;
|
||||
|
||||
if (!length)
|
||||
return 0;
|
||||
|
||||
ret = -EINVAL;
|
||||
pcl_for_each_segment(segment, chunk) {
|
||||
struct svc_rdma_segment dummy;
|
||||
|
||||
if (offset > segment->rs_length) {
|
||||
if (offset >= segment->rs_length) {
|
||||
offset -= segment->rs_length;
|
||||
continue;
|
||||
}
|
||||
|
||||
dummy.rs_handle = segment->rs_handle;
|
||||
dummy.rs_length = min_t(u32, length, segment->rs_length) - offset;
|
||||
dummy.rs_length = min_t(u32, length, segment->rs_length - offset);
|
||||
dummy.rs_offset = segment->rs_offset + offset;
|
||||
|
||||
ret = svc_rdma_build_read_segment(rqstp, head, &dummy);
|
||||
@@ -1028,6 +1031,8 @@ static int svc_rdma_read_chunk_range(struct svc_rqst *rqstp,
|
||||
|
||||
head->rc_readbytes += dummy.rs_length;
|
||||
length -= dummy.rs_length;
|
||||
if (!length)
|
||||
break;
|
||||
offset = 0;
|
||||
}
|
||||
return ret;
|
||||
|
||||
Reference in New Issue
Block a user