Skip to content

Commit bf51c52

Browse files
committed
NFSD: Fix checksum mismatches in the duplicate reply cache
nfsd_cache_csum() currently assumes that the server's RPC layer has been advancing rq_arg.head[0].iov_base as it decodes an incoming request, because that's the way it used to work. On entry, it expects that buf->head[0].iov_base points to the start of the NFS header, and excludes the already-decoded RPC header. These days however, head[0].iov_base now points to the start of the RPC header during all processing. It no longer points at the NFS Call header when execution arrives at nfsd_cache_csum(). In a retransmitted RPC the XID and the NFS header are supposed to be the same as the original message, but the contents of the retransmitted RPC header can be different. For example, for krb5, the GSS sequence number will be different between the two. Thus if the RPC header is always included in the DRC checksum computation, the checksum of the retransmitted message might not match the checksum of the original message, even though the NFS part of these messages is identical. The result is that, even if a matching XID is found in the DRC, the checksum mismatch causes the server to execute the retransmitted RPC transaction again. Reviewed-by: Jeff Layton <jlayton@kernel.org> Tested-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
1 parent 1caf5f6 commit bf51c52

File tree

3 files changed

+54
-24
lines changed

3 files changed

+54
-24
lines changed

fs/nfsd/cache.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ int nfsd_net_reply_cache_init(struct nfsd_net *nn);
8484
void nfsd_net_reply_cache_destroy(struct nfsd_net *nn);
8585
int nfsd_reply_cache_init(struct nfsd_net *);
8686
void nfsd_reply_cache_shutdown(struct nfsd_net *);
87-
int nfsd_cache_lookup(struct svc_rqst *rqstp,
88-
struct nfsd_cacherep **cacherep);
87+
int nfsd_cache_lookup(struct svc_rqst *rqstp, unsigned int start,
88+
unsigned int len, struct nfsd_cacherep **cacherep);
8989
void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp,
9090
int cachetype, __be32 *statp);
9191
int nfsd_reply_cache_stats_show(struct seq_file *m, void *v);

fs/nfsd/nfscache.c

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -368,33 +368,52 @@ nfsd_reply_cache_scan(struct shrinker *shrink, struct shrink_control *sc)
368368
return freed;
369369
}
370370

371-
/*
372-
* Walk an xdr_buf and get a CRC for at most the first RC_CSUMLEN bytes
371+
/**
372+
* nfsd_cache_csum - Checksum incoming NFS Call arguments
373+
* @buf: buffer containing a whole RPC Call message
374+
* @start: starting byte of the NFS Call header
375+
* @remaining: size of the NFS Call header, in bytes
376+
*
377+
* Compute a weak checksum of the leading bytes of an NFS procedure
378+
* call header to help verify that a retransmitted Call matches an
379+
* entry in the duplicate reply cache.
380+
*
381+
* To avoid assumptions about how the RPC message is laid out in
382+
* @buf and what else it might contain (eg, a GSS MIC suffix), the
383+
* caller passes us the exact location and length of the NFS Call
384+
* header.
385+
*
386+
* Returns a 32-bit checksum value, as defined in RFC 793.
373387
*/
374-
static __wsum
375-
nfsd_cache_csum(struct svc_rqst *rqstp)
388+
static __wsum nfsd_cache_csum(struct xdr_buf *buf, unsigned int start,
389+
unsigned int remaining)
376390
{
391+
unsigned int base, len;
392+
struct xdr_buf subbuf;
393+
__wsum csum = 0;
394+
void *p;
377395
int idx;
378-
unsigned int base;
379-
__wsum csum;
380-
struct xdr_buf *buf = &rqstp->rq_arg;
381-
const unsigned char *p = buf->head[0].iov_base;
382-
size_t csum_len = min_t(size_t, buf->head[0].iov_len + buf->page_len,
383-
RC_CSUMLEN);
384-
size_t len = min(buf->head[0].iov_len, csum_len);
396+
397+
if (remaining > RC_CSUMLEN)
398+
remaining = RC_CSUMLEN;
399+
if (xdr_buf_subsegment(buf, &subbuf, start, remaining))
400+
return csum;
385401

386402
/* rq_arg.head first */
387-
csum = csum_partial(p, len, 0);
388-
csum_len -= len;
403+
if (subbuf.head[0].iov_len) {
404+
len = min_t(unsigned int, subbuf.head[0].iov_len, remaining);
405+
csum = csum_partial(subbuf.head[0].iov_base, len, csum);
406+
remaining -= len;
407+
}
389408

390409
/* Continue into page array */
391-
idx = buf->page_base / PAGE_SIZE;
392-
base = buf->page_base & ~PAGE_MASK;
393-
while (csum_len) {
394-
p = page_address(buf->pages[idx]) + base;
395-
len = min_t(size_t, PAGE_SIZE - base, csum_len);
410+
idx = subbuf.page_base / PAGE_SIZE;
411+
base = subbuf.page_base & ~PAGE_MASK;
412+
while (remaining) {
413+
p = page_address(subbuf.pages[idx]) + base;
414+
len = min_t(unsigned int, PAGE_SIZE - base, remaining);
396415
csum = csum_partial(p, len, csum);
397-
csum_len -= len;
416+
remaining -= len;
398417
base = 0;
399418
++idx;
400419
}
@@ -465,6 +484,8 @@ nfsd_cache_insert(struct nfsd_drc_bucket *b, struct nfsd_cacherep *key,
465484
/**
466485
* nfsd_cache_lookup - Find an entry in the duplicate reply cache
467486
* @rqstp: Incoming Call to find
487+
* @start: starting byte in @rqstp->rq_arg of the NFS Call header
488+
* @len: size of the NFS Call header, in bytes
468489
* @cacherep: OUT: DRC entry for this request
469490
*
470491
* Try to find an entry matching the current call in the cache. When none
@@ -478,7 +499,8 @@ nfsd_cache_insert(struct nfsd_drc_bucket *b, struct nfsd_cacherep *key,
478499
* %RC_REPLY: Reply from cache
479500
* %RC_DROPIT: Do not process the request further
480501
*/
481-
int nfsd_cache_lookup(struct svc_rqst *rqstp, struct nfsd_cacherep **cacherep)
502+
int nfsd_cache_lookup(struct svc_rqst *rqstp, unsigned int start,
503+
unsigned int len, struct nfsd_cacherep **cacherep)
482504
{
483505
struct nfsd_net *nn;
484506
struct nfsd_cacherep *rp, *found;
@@ -494,7 +516,7 @@ int nfsd_cache_lookup(struct svc_rqst *rqstp, struct nfsd_cacherep **cacherep)
494516
goto out;
495517
}
496518

497-
csum = nfsd_cache_csum(rqstp);
519+
csum = nfsd_cache_csum(&rqstp->rq_arg, start, len);
498520

499521
/*
500522
* Since the common case is a cache miss followed by an insert,

fs/nfsd/nfssvc.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
981981
const struct svc_procedure *proc = rqstp->rq_procinfo;
982982
__be32 *statp = rqstp->rq_accept_statp;
983983
struct nfsd_cacherep *rp;
984+
unsigned int start, len;
984985
__be32 *nfs_reply;
985986

986987
/*
@@ -989,6 +990,13 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
989990
*/
990991
rqstp->rq_cachetype = proc->pc_cachetype;
991992

993+
/*
994+
* ->pc_decode advances the argument stream past the NFS
995+
* Call header, so grab the header's starting location and
996+
* size now for the call to nfsd_cache_lookup().
997+
*/
998+
start = xdr_stream_pos(&rqstp->rq_arg_stream);
999+
len = xdr_stream_remaining(&rqstp->rq_arg_stream);
9921000
if (!proc->pc_decode(rqstp, &rqstp->rq_arg_stream))
9931001
goto out_decode_err;
9941002

@@ -1002,7 +1010,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
10021010
smp_store_release(&rqstp->rq_status_counter, rqstp->rq_status_counter | 1);
10031011

10041012
rp = NULL;
1005-
switch (nfsd_cache_lookup(rqstp, &rp)) {
1013+
switch (nfsd_cache_lookup(rqstp, start, len, &rp)) {
10061014
case RC_DOIT:
10071015
break;
10081016
case RC_REPLY:

0 commit comments

Comments
 (0)