svcrdma: Validate Read chunk positions before reconstruction

The RPC/RDMA Read chunk position field is supplied by the remote
client and stored verbatim in the parsed chunk list.
xdr_count_read_segments() checks only 4-byte alignment; it never
compares the position against the received inline body length.

In the single-chunk path, svc_rdma_read_complete_one() splits the
head and tail kvecs at ch_position. A position past the inline
body underflows the tail length, exposing adjacent slab memory to
the upper XDR decoder.

In the multi-chunk path, svc_rdma_read_multiple_chunks() computes
gap lengths between chunks as unsigned subtractions from
ch_position. Overlapping Read chunks cause these subtractions to
underflow. A final position past the inline body likewise
underflows the trailing gap length. svc_rdma_copy_inline_range()
then copies past the receive buffer into request pages that are
returned to the client through the Reply channel.

Bound inline-range copies in svc_rdma_copy_inline_range() against
the decoded inline RPC body saved in rc_saved_arg. Reject a
single Read chunk positioned beyond that body, and reject
multi-chunk lists where accumulated read bytes exceed the next
chunk's position. Apply the same position and overlap checks in
the call-chunk interleaving path.

Fixes: d96962e6d0 ("svcrdma: Use the new parsed chunk list when pulling Read chunks")
Cc: stable@vger.kernel.org
Acked-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-1-e251306ccca9@oracle.com
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Chuck Lever 2026-05-26 09:35:55 -04:00 committed by Chuck Lever
parent 650d370cfb
commit 3779b7b9e7

View File

@ -847,7 +847,7 @@ static int svc_rdma_build_read_chunk(struct svc_rqst *rqstp,
* svc_rdma_copy_inline_range - Copy part of the inline content into pages
* @rqstp: RPC transaction context
* @head: context for ongoing I/O
* @offset: offset into the Receive buffer of region to copy
* @offset: offset into the inline content of region to copy
* @remaining: length of region to copy
*
* Take a page at a time from rqstp->rq_pages and copy the inline
@ -864,9 +864,13 @@ static int svc_rdma_copy_inline_range(struct svc_rqst *rqstp,
unsigned int offset,
unsigned int remaining)
{
unsigned char *dst, *src = head->rc_recv_buf;
unsigned char *dst, *src = head->rc_saved_arg.head[0].iov_base;
unsigned int inline_len = head->rc_saved_arg.head[0].iov_len;
unsigned int page_no, numpages;
if (offset > inline_len || remaining > inline_len - offset)
return -EINVAL;
numpages = PAGE_ALIGN(head->rc_pageoff + remaining) >> PAGE_SHIFT;
for (page_no = 0; page_no < numpages; page_no++) {
unsigned int page_len;
@ -917,9 +921,10 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp,
{
const struct svc_rdma_pcl *pcl = &head->rc_read_pcl;
struct svc_rdma_chunk *chunk, *next;
unsigned int start, length;
unsigned int inline_len, start, length;
int ret;
inline_len = head->rc_saved_arg.head[0].iov_len;
start = 0;
chunk = pcl_first_chunk(pcl);
length = chunk->ch_position;
@ -937,6 +942,8 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp,
break;
start += length;
if (head->rc_readbytes > next->ch_position)
return -EINVAL;
length = next->ch_position - head->rc_readbytes;
ret = svc_rdma_copy_inline_range(rqstp, head, start, length);
if (ret < 0)
@ -944,7 +951,9 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp,
}
start += length;
length = head->rc_byte_len - start;
if (start > inline_len)
return -EINVAL;
length = inline_len - start;
return svc_rdma_copy_inline_range(rqstp, head, start, length);
}
@ -969,8 +978,12 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp,
static int svc_rdma_read_data_item(struct svc_rqst *rqstp,
struct svc_rdma_recv_ctxt *head)
{
return svc_rdma_build_read_chunk(rqstp, head,
pcl_first_chunk(&head->rc_read_pcl));
struct svc_rdma_chunk *chunk = pcl_first_chunk(&head->rc_read_pcl);
if (chunk->ch_position > head->rc_saved_arg.head[0].iov_len)
return -EINVAL;
return svc_rdma_build_read_chunk(rqstp, head, chunk);
}
/**
@ -1039,14 +1052,17 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp,
pcl_first_chunk(&head->rc_call_pcl);
const struct svc_rdma_pcl *pcl = &head->rc_read_pcl;
struct svc_rdma_chunk *chunk, *next;
unsigned int start, length;
unsigned int call_len, start, length;
int ret;
if (pcl_is_empty(pcl))
return svc_rdma_build_read_chunk(rqstp, head, call_chunk);
call_len = call_chunk->ch_length;
start = 0;
chunk = pcl_first_chunk(pcl);
if (chunk->ch_position > call_len)
return -EINVAL;
length = chunk->ch_position;
ret = svc_rdma_read_chunk_range(rqstp, head, call_chunk,
start, length);
@ -1063,6 +1079,10 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp,
break;
start += length;
if (next->ch_position > call_len)
return -EINVAL;
if (head->rc_readbytes > next->ch_position)
return -EINVAL;
length = next->ch_position - head->rc_readbytes;
ret = svc_rdma_read_chunk_range(rqstp, head, call_chunk,
start, length);
@ -1071,7 +1091,9 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp,
}
start += length;
length = call_chunk->ch_length - start;
if (start > call_len)
return -EINVAL;
length = call_len - start;
return svc_rdma_read_chunk_range(rqstp, head, call_chunk,
start, length);
}