1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2020 Oracle. All rights reserved.
4 */
5
6 #include <linux/sunrpc/svc_rdma.h>
7 #include <linux/sunrpc/rpc_rdma.h>
8
9 #include "xprt_rdma.h"
10 #include <trace/events/rpcrdma.h>
11
12 /**
13 * pcl_free - Release all memory associated with a parsed chunk list
14 * @pcl: parsed chunk list
15 *
16 */
pcl_free(struct svc_rdma_pcl * pcl)17 void pcl_free(struct svc_rdma_pcl *pcl)
18 {
19 while (!list_empty(&pcl->cl_chunks)) {
20 struct svc_rdma_chunk *chunk;
21
22 chunk = pcl_first_chunk(pcl);
23 list_del(&chunk->ch_list);
24 kfree(chunk);
25 }
26 }
27
pcl_alloc_chunk(u32 segcount,u32 position)28 static struct svc_rdma_chunk *pcl_alloc_chunk(u32 segcount, u32 position)
29 {
30 struct svc_rdma_chunk *chunk;
31
32 chunk = kmalloc_flex(*chunk, ch_segments, segcount);
33 if (!chunk)
34 return NULL;
35
36 chunk->ch_position = position;
37 chunk->ch_length = 0;
38 chunk->ch_payload_length = 0;
39 chunk->ch_segcount = 0;
40 return chunk;
41 }
42
43 static struct svc_rdma_chunk *
pcl_lookup_position(struct svc_rdma_pcl * pcl,u32 position)44 pcl_lookup_position(struct svc_rdma_pcl *pcl, u32 position)
45 {
46 struct svc_rdma_chunk *pos;
47
48 pcl_for_each_chunk(pos, pcl) {
49 if (pos->ch_position == position)
50 return pos;
51 }
52 return NULL;
53 }
54
pcl_insert_position(struct svc_rdma_pcl * pcl,struct svc_rdma_chunk * chunk)55 static void pcl_insert_position(struct svc_rdma_pcl *pcl,
56 struct svc_rdma_chunk *chunk)
57 {
58 struct svc_rdma_chunk *pos;
59
60 pcl_for_each_chunk(pos, pcl) {
61 if (pos->ch_position > chunk->ch_position)
62 break;
63 }
64 __list_add(&chunk->ch_list, pos->ch_list.prev, &pos->ch_list);
65 pcl->cl_count++;
66 }
67
pcl_set_read_segment(const struct svc_rdma_recv_ctxt * rctxt,struct svc_rdma_chunk * chunk,u32 handle,u32 length,u64 offset)68 static void pcl_set_read_segment(const struct svc_rdma_recv_ctxt *rctxt,
69 struct svc_rdma_chunk *chunk,
70 u32 handle, u32 length, u64 offset)
71 {
72 struct svc_rdma_segment *segment;
73
74 segment = &chunk->ch_segments[chunk->ch_segcount];
75 segment->rs_handle = handle;
76 segment->rs_length = length;
77 segment->rs_offset = offset;
78
79 trace_svcrdma_decode_rseg(&rctxt->rc_cid, chunk, segment);
80
81 chunk->ch_length += length;
82 chunk->ch_segcount++;
83 }
84
85 /**
86 * pcl_alloc_call - Construct a parsed chunk list for the Call body
87 * @rctxt: Ingress receive context
88 * @p: Start of an un-decoded Read list
89 *
90 * Assumptions:
91 * - The incoming Read list has already been sanity checked.
92 * - cl_count is already set to the number of segments in
93 * the un-decoded list.
94 * - The list might not be in order by position.
95 *
96 * Return values:
97 * %true: Parsed chunk list was successfully constructed, and
98 * cl_count is updated to be the number of chunks (ie.
99 * unique positions) in the Read list.
100 * %false: Memory allocation failed.
101 */
pcl_alloc_call(struct svc_rdma_recv_ctxt * rctxt,__be32 * p)102 bool pcl_alloc_call(struct svc_rdma_recv_ctxt *rctxt, __be32 *p)
103 {
104 struct svc_rdma_pcl *pcl = &rctxt->rc_call_pcl;
105 unsigned int i, segcount = pcl->cl_count;
106
107 pcl->cl_count = 0;
108 for (i = 0; i < segcount; i++) {
109 struct svc_rdma_chunk *chunk;
110 u32 position, handle, length;
111 u64 offset;
112
113 p++; /* skip the list discriminator */
114 p = xdr_decode_read_segment(p, &position, &handle,
115 &length, &offset);
116 if (position != 0)
117 continue;
118
119 if (pcl_is_empty(pcl)) {
120 chunk = pcl_alloc_chunk(segcount, position);
121 if (!chunk)
122 return false;
123 pcl_insert_position(pcl, chunk);
124 } else {
125 chunk = list_first_entry(&pcl->cl_chunks,
126 struct svc_rdma_chunk,
127 ch_list);
128 }
129
130 pcl_set_read_segment(rctxt, chunk, handle, length, offset);
131 }
132
133 return true;
134 }
135
136 /**
137 * pcl_alloc_read - Construct a parsed chunk list for normal Read chunks
138 * @rctxt: Ingress receive context
139 * @p: Start of an un-decoded Read list
140 *
141 * Assumptions:
142 * - The incoming Read list has already been sanity checked.
143 * - cl_count is already set to the number of segments in
144 * the un-decoded list.
145 * - The list might not be in order by position.
146 *
147 * Return values:
148 * %true: Parsed chunk list was successfully constructed, and
149 * cl_count is updated to be the number of chunks (ie.
150 * unique position values) in the Read list.
151 * %false: Memory allocation failed.
152 */
pcl_alloc_read(struct svc_rdma_recv_ctxt * rctxt,__be32 * p)153 bool pcl_alloc_read(struct svc_rdma_recv_ctxt *rctxt, __be32 *p)
154 {
155 struct svc_rdma_pcl *pcl = &rctxt->rc_read_pcl;
156 unsigned int i, segcount = pcl->cl_count;
157
158 pcl->cl_count = 0;
159 for (i = 0; i < segcount; i++) {
160 struct svc_rdma_chunk *chunk;
161 u32 position, handle, length;
162 u64 offset;
163
164 p++; /* skip the list discriminator */
165 p = xdr_decode_read_segment(p, &position, &handle,
166 &length, &offset);
167 if (position == 0)
168 continue;
169
170 chunk = pcl_lookup_position(pcl, position);
171 if (!chunk) {
172 chunk = pcl_alloc_chunk(segcount, position);
173 if (!chunk)
174 return false;
175 pcl_insert_position(pcl, chunk);
176 }
177
178 pcl_set_read_segment(rctxt, chunk, handle, length, offset);
179 }
180
181 return true;
182 }
183
184 /**
185 * pcl_alloc_write - Construct a parsed chunk list from a Write list
186 * @rctxt: Ingress receive context
187 * @pcl: Parsed chunk list to populate
188 * @p: Start of an un-decoded Write list
189 *
190 * Assumptions:
191 * - The incoming Write list has already been sanity checked, and
192 * - cl_count is set to the number of chunks in the un-decoded list.
193 *
194 * Return values:
195 * %true: Parsed chunk list was successfully constructed.
196 * %false: Memory allocation failed.
197 */
pcl_alloc_write(struct svc_rdma_recv_ctxt * rctxt,struct svc_rdma_pcl * pcl,__be32 * p)198 bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt,
199 struct svc_rdma_pcl *pcl, __be32 *p)
200 {
201 struct svc_rdma_segment *segment;
202 struct svc_rdma_chunk *chunk;
203 unsigned int i, j;
204 u32 segcount;
205
206 for (i = 0; i < pcl->cl_count; i++) {
207 p++; /* skip the list discriminator */
208 segcount = be32_to_cpup(p++);
209
210 chunk = pcl_alloc_chunk(segcount, 0);
211 if (!chunk)
212 return false;
213
214 for (j = 0; j < segcount; j++) {
215 segment = &chunk->ch_segments[j];
216 p = xdr_decode_rdma_segment(p, &segment->rs_handle,
217 &segment->rs_length,
218 &segment->rs_offset);
219 trace_svcrdma_decode_wseg(&rctxt->rc_cid, chunk, j);
220
221 chunk->ch_length += segment->rs_length;
222 chunk->ch_segcount++;
223 }
224 list_add_tail(&chunk->ch_list, &pcl->cl_chunks);
225 }
226 return true;
227 }
228
229 /**
230 * pcl_check_read_chunk_positions - Validate Read chunk positions
231 * @rctxt: Ingress receive context with populated chunk lists
232 * @inline_len: Length of the inline RPC body after the transport header
233 *
234 * Read chunk positions are offsets in the unreduced XDR stream
235 * (RFC 8166 Section 3.4.4), so each position includes the
236 * cumulative length of preceding Read chunks. This function
237 * subtracts those lengths to recover the inline-body offset
238 * before comparing against @inline_len or the Call chunk length.
239 *
240 * Rejects frames where a Read chunk's inline-body offset exceeds
241 * the bound, where adjacent Read chunks overlap, or where any
242 * single chunk length exceeds the page budget.
243 *
244 * Return values:
245 * %true: Read chunk positions and lengths are valid
246 * %false: Malformed chunk list detected
247 */
pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt * rctxt,unsigned int inline_len)248 bool pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt *rctxt,
249 unsigned int inline_len)
250 {
251 unsigned int max_len, bound, total_read;
252 struct svc_rdma_chunk *chunk, *next;
253
254 max_len = rctxt->rc_maxpages << PAGE_SHIFT;
255
256 if (!pcl_is_empty(&rctxt->rc_call_pcl)) {
257 chunk = pcl_first_chunk(&rctxt->rc_call_pcl);
258 if (chunk->ch_length > max_len)
259 return false;
260 bound = chunk->ch_length;
261 } else {
262 bound = inline_len;
263 }
264
265 if (pcl_is_empty(&rctxt->rc_read_pcl))
266 return true;
267
268 total_read = 0;
269 pcl_for_each_chunk(chunk, &rctxt->rc_read_pcl) {
270 if (chunk->ch_position - total_read > bound)
271 return false;
272 if (chunk->ch_length > max_len)
273 return false;
274
275 next = pcl_next_chunk(&rctxt->rc_read_pcl, chunk);
276 if (!next)
277 break;
278
279 if (chunk->ch_position + chunk->ch_length > next->ch_position)
280 return false;
281 total_read += chunk->ch_length;
282 }
283
284 return true;
285 }
286
pcl_process_region(const struct xdr_buf * xdr,unsigned int offset,unsigned int length,int (* actor)(const struct xdr_buf *,void *),void * data)287 static int pcl_process_region(const struct xdr_buf *xdr,
288 unsigned int offset, unsigned int length,
289 int (*actor)(const struct xdr_buf *, void *),
290 void *data)
291 {
292 struct xdr_buf subbuf;
293
294 if (!length)
295 return 0;
296 if (xdr_buf_subsegment(xdr, &subbuf, offset, length))
297 return -EMSGSIZE;
298 return actor(&subbuf, data);
299 }
300
301 /**
302 * pcl_process_nonpayloads - Process non-payload regions inside @xdr
303 * @pcl: Chunk list to process
304 * @xdr: xdr_buf to process
305 * @actor: Function to invoke on each non-payload region
306 * @data: Arguments for @actor
307 *
308 * This mechanism must ignore not only result payloads that were already
309 * sent via RDMA Write, but also XDR padding for those payloads that
310 * the upper layer has added.
311 *
312 * Assumptions:
313 * The xdr->len and ch_position fields are aligned to 4-byte multiples.
314 *
315 * Returns:
316 * On success, zero,
317 * %-EMSGSIZE on XDR buffer overflow, or
318 * The return value of @actor
319 */
pcl_process_nonpayloads(const struct svc_rdma_pcl * pcl,const struct xdr_buf * xdr,int (* actor)(const struct xdr_buf *,void *),void * data)320 int pcl_process_nonpayloads(const struct svc_rdma_pcl *pcl,
321 const struct xdr_buf *xdr,
322 int (*actor)(const struct xdr_buf *, void *),
323 void *data)
324 {
325 struct svc_rdma_chunk *chunk, *next;
326 unsigned int start;
327 int ret;
328
329 chunk = pcl_first_chunk(pcl);
330
331 /* No result payloads were generated */
332 if (!chunk || !chunk->ch_payload_length)
333 return actor(xdr, data);
334
335 /* Process the region before the first result payload */
336 ret = pcl_process_region(xdr, 0, chunk->ch_position, actor, data);
337 if (ret < 0)
338 return ret;
339
340 /* Process the regions between each middle result payload */
341 while ((next = pcl_next_chunk(pcl, chunk))) {
342 if (!next->ch_payload_length)
343 break;
344
345 start = pcl_chunk_end_offset(chunk);
346 ret = pcl_process_region(xdr, start, next->ch_position - start,
347 actor, data);
348 if (ret < 0)
349 return ret;
350
351 chunk = next;
352 }
353
354 /* Process the region after the last result payload */
355 start = pcl_chunk_end_offset(chunk);
356 ret = pcl_process_region(xdr, start, xdr->len - start, actor, data);
357 if (ret < 0)
358 return ret;
359
360 return 0;
361 }
362