summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/sunrpc/svc_rdma_pcl.h2
-rw-r--r--net/sunrpc/xprtrdma/svc_rdma_pcl.c61
-rw-r--r--net/sunrpc/xprtrdma/svc_rdma_recvfrom.c3
3 files changed, 63 insertions, 3 deletions
diff --git a/include/linux/sunrpc/svc_rdma_pcl.h b/include/linux/sunrpc/svc_rdma_pcl.h
index 655681cf8fed..6346d8cf2587 100644
--- a/include/linux/sunrpc/svc_rdma_pcl.h
+++ b/include/linux/sunrpc/svc_rdma_pcl.h
@@ -119,6 +119,8 @@ extern bool pcl_alloc_call(struct svc_rdma_recv_ctxt *rctxt, __be32 *p);
extern bool pcl_alloc_read(struct svc_rdma_recv_ctxt *rctxt, __be32 *p);
extern bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt,
struct svc_rdma_pcl *pcl, __be32 *p);
+extern bool pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt *rctxt,
+ unsigned int inline_len);
extern int pcl_process_nonpayloads(const struct svc_rdma_pcl *pcl,
const struct xdr_buf *xdr,
int (*actor)(const struct xdr_buf *,
diff --git a/net/sunrpc/xprtrdma/svc_rdma_pcl.c b/net/sunrpc/xprtrdma/svc_rdma_pcl.c
index 18d1045799ce..8623722790f2 100644
--- a/net/sunrpc/xprtrdma/svc_rdma_pcl.c
+++ b/net/sunrpc/xprtrdma/svc_rdma_pcl.c
@@ -149,9 +149,6 @@ bool pcl_alloc_call(struct svc_rdma_recv_ctxt *rctxt, __be32 *p)
* cl_count is updated to be the number of chunks (ie.
* unique position values) in the Read list.
* %false: Memory allocation failed.
- *
- * TODO:
- * - Check for chunk range overlaps
*/
bool pcl_alloc_read(struct svc_rdma_recv_ctxt *rctxt, __be32 *p)
{
@@ -229,6 +226,64 @@ bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt,
return true;
}
+/**
+ * pcl_check_read_chunk_positions - Validate Read chunk positions
+ * @rctxt: Ingress receive context with populated chunk lists
+ * @inline_len: Length of the inline RPC body after the transport header
+ *
+ * Read chunk positions are offsets in the unreduced XDR stream
+ * (RFC 8166 Section 3.4.4), so each position includes the
+ * cumulative length of preceding Read chunks. This function
+ * subtracts those lengths to recover the inline-body offset
+ * before comparing against @inline_len or the Call chunk length.
+ *
+ * Rejects frames where a Read chunk's inline-body offset exceeds
+ * the bound, where adjacent Read chunks overlap, or where any
+ * single chunk length exceeds the page budget.
+ *
+ * Return values:
+ * %true: Read chunk positions and lengths are valid
+ * %false: Malformed chunk list detected
+ */
+bool pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt *rctxt,
+ unsigned int inline_len)
+{
+ unsigned int max_len, bound, total_read;
+ struct svc_rdma_chunk *chunk, *next;
+
+ max_len = rctxt->rc_maxpages << PAGE_SHIFT;
+
+ if (!pcl_is_empty(&rctxt->rc_call_pcl)) {
+ chunk = pcl_first_chunk(&rctxt->rc_call_pcl);
+ if (chunk->ch_length > max_len)
+ return false;
+ bound = chunk->ch_length;
+ } else {
+ bound = inline_len;
+ }
+
+ if (pcl_is_empty(&rctxt->rc_read_pcl))
+ return true;
+
+ total_read = 0;
+ pcl_for_each_chunk(chunk, &rctxt->rc_read_pcl) {
+ if (chunk->ch_position - total_read > bound)
+ return false;
+ if (chunk->ch_length > max_len)
+ return false;
+
+ next = pcl_next_chunk(&rctxt->rc_read_pcl, chunk);
+ if (!next)
+ break;
+
+ if (chunk->ch_position + chunk->ch_length > next->ch_position)
+ return false;
+ total_read += chunk->ch_length;
+ }
+
+ return true;
+}
+
static int pcl_process_region(const struct xdr_buf *xdr,
unsigned int offset, unsigned int length,
int (*actor)(const struct xdr_buf *, void *),
diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c
index f6a7533a7555..d64b5f78ce8a 100644
--- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c
+++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c
@@ -724,6 +724,9 @@ static int svc_rdma_xdr_decode_req(struct xdr_buf *rq_arg,
rq_arg->head[0].iov_base = rctxt->rc_stream.p;
hdr_len = xdr_stream_pos(&rctxt->rc_stream);
+ if (!pcl_check_read_chunk_positions(rctxt,
+ rq_arg->head[0].iov_len - hdr_len))
+ goto out_inval;
rq_arg->head[0].iov_len -= hdr_len;
rq_arg->len -= hdr_len;
trace_svcrdma_decode_rqst(rctxt, rdma_argp, hdr_len);