Skip to content

Commit 0a8e7b7

Browse files
committed
SUNRPC: Revert 241b1f4 ("SUNRPC: Remove xdr_buf_trim()")
I've noticed that when krb5i or krb5p security is in use, retransmitted requests are missing the server's duplicate reply cache. The computed checksum on the retransmitted request does not match the cached checksum, resulting in the server performing the retransmitted request again instead of returning the cached reply. The assumptions made when removing xdr_buf_trim() were not correct. In the send paths, the upper layer has already set the segment lengths correctly, and shorting the buffer's content is simply a matter of reducing buf->len. xdr_buf_trim() is the right answer in the receive/unwrap path on both the client and the server. The buffer segment lengths have to be shortened one-by-one. On the server side in particular, head.iov_len needs to be updated correctly to enable nfsd_cache_csum() to work correctly. The simple buf->len computation doesn't do that, and that results in checksumming stale data in the buffer. The problem isn't noticed until there's significant instability of the RPC transport. At that point, the reliability of retransmit detection on the server becomes crucial. Fixes: 241b1f4 ("SUNRPC: Remove xdr_buf_trim()") Signed-off-by: Chuck Lever <[email protected]>
1 parent a7e429a commit 0a8e7b7

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

include/linux/sunrpc/xdr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ xdr_adjust_iovec(struct kvec *iov, __be32 *p)
184184
extern void xdr_shift_buf(struct xdr_buf *, size_t);
185185
extern void xdr_buf_from_iov(struct kvec *, struct xdr_buf *);
186186
extern int xdr_buf_subsegment(struct xdr_buf *, struct xdr_buf *, unsigned int, unsigned int);
187+
extern void xdr_buf_trim(struct xdr_buf *, unsigned int);
187188
extern int read_bytes_from_xdr_buf(struct xdr_buf *, unsigned int, void *, unsigned int);
188189
extern int write_bytes_to_xdr_buf(struct xdr_buf *, unsigned int, void *, unsigned int);
189190

net/sunrpc/auth_gss/gss_krb5_wrap.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,15 +580,14 @@ gss_unwrap_kerberos_v2(struct krb5_ctx *kctx, int offset, int len,
580580
*/
581581
movelen = min_t(unsigned int, buf->head[0].iov_len, len);
582582
movelen -= offset + GSS_KRB5_TOK_HDR_LEN + headskip;
583-
if (offset + GSS_KRB5_TOK_HDR_LEN + headskip + movelen >
584-
buf->head[0].iov_len)
585-
return GSS_S_FAILURE;
583+
BUG_ON(offset + GSS_KRB5_TOK_HDR_LEN + headskip + movelen >
584+
buf->head[0].iov_len);
586585
memmove(ptr, ptr + GSS_KRB5_TOK_HDR_LEN + headskip, movelen);
587586
buf->head[0].iov_len -= GSS_KRB5_TOK_HDR_LEN + headskip;
588587
buf->len = len - GSS_KRB5_TOK_HDR_LEN + headskip;
589588

590589
/* Trim off the trailing "extra count" and checksum blob */
591-
buf->len -= ec + GSS_KRB5_TOK_HDR_LEN + tailskip;
590+
xdr_buf_trim(buf, ec + GSS_KRB5_TOK_HDR_LEN + tailskip);
592591

593592
*align = XDR_QUADLEN(GSS_KRB5_TOK_HDR_LEN + headskip);
594593
*slack = *align + XDR_QUADLEN(ec + GSS_KRB5_TOK_HDR_LEN + tailskip);

net/sunrpc/auth_gss/svcauth_gss.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ unwrap_integ_data(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq, struct g
906906
if (svc_getnl(&buf->head[0]) != seq)
907907
goto out;
908908
/* trim off the mic and padding at the end before returning */
909-
buf->len -= 4 + round_up_to_quad(mic.len);
909+
xdr_buf_trim(buf, round_up_to_quad(mic.len) + 4);
910910
stat = 0;
911911
out:
912912
kfree(mic.data);

net/sunrpc/xdr.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,47 @@ xdr_buf_subsegment(struct xdr_buf *buf, struct xdr_buf *subbuf,
11501150
}
11511151
EXPORT_SYMBOL_GPL(xdr_buf_subsegment);
11521152

1153+
/**
1154+
* xdr_buf_trim - lop at most "len" bytes off the end of "buf"
1155+
* @buf: buf to be trimmed
1156+
* @len: number of bytes to reduce "buf" by
1157+
*
1158+
* Trim an xdr_buf by the given number of bytes by fixing up the lengths. Note
1159+
* that it's possible that we'll trim less than that amount if the xdr_buf is
1160+
* too small, or if (for instance) it's all in the head and the parser has
1161+
* already read too far into it.
1162+
*/
1163+
void xdr_buf_trim(struct xdr_buf *buf, unsigned int len)
1164+
{
1165+
size_t cur;
1166+
unsigned int trim = len;
1167+
1168+
if (buf->tail[0].iov_len) {
1169+
cur = min_t(size_t, buf->tail[0].iov_len, trim);
1170+
buf->tail[0].iov_len -= cur;
1171+
trim -= cur;
1172+
if (!trim)
1173+
goto fix_len;
1174+
}
1175+
1176+
if (buf->page_len) {
1177+
cur = min_t(unsigned int, buf->page_len, trim);
1178+
buf->page_len -= cur;
1179+
trim -= cur;
1180+
if (!trim)
1181+
goto fix_len;
1182+
}
1183+
1184+
if (buf->head[0].iov_len) {
1185+
cur = min_t(size_t, buf->head[0].iov_len, trim);
1186+
buf->head[0].iov_len -= cur;
1187+
trim -= cur;
1188+
}
1189+
fix_len:
1190+
buf->len -= (len - trim);
1191+
}
1192+
EXPORT_SYMBOL_GPL(xdr_buf_trim);
1193+
11531194
static void __read_bytes_from_xdr_buf(struct xdr_buf *subbuf, void *obj, unsigned int len)
11541195
{
11551196
unsigned int this_len;

0 commit comments

Comments
 (0)