Commit bb28378a authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'nfsd-6.7-1' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux

Pull nfsd fixes from Chuck Lever:

 - Fix several long-standing bugs in the duplicate reply cache

 - Fix a memory leak

* tag 'nfsd-6.7-1' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux:
  NFSD: Fix checksum mismatches in the duplicate reply cache
  NFSD: Fix "start of NFS reply" pointer passed to nfsd_cache_update()
  NFSD: Update nfsd_cache_append() to use xdr_stream
  nfsd: fix file memleak on client_opens_release
parents 33b63f15 bf51c52a
...@@ -84,8 +84,8 @@ int nfsd_net_reply_cache_init(struct nfsd_net *nn); ...@@ -84,8 +84,8 @@ int nfsd_net_reply_cache_init(struct nfsd_net *nn);
void nfsd_net_reply_cache_destroy(struct nfsd_net *nn); void nfsd_net_reply_cache_destroy(struct nfsd_net *nn);
int nfsd_reply_cache_init(struct nfsd_net *); int nfsd_reply_cache_init(struct nfsd_net *);
void nfsd_reply_cache_shutdown(struct nfsd_net *); void nfsd_reply_cache_shutdown(struct nfsd_net *);
int nfsd_cache_lookup(struct svc_rqst *rqstp, int nfsd_cache_lookup(struct svc_rqst *rqstp, unsigned int start,
struct nfsd_cacherep **cacherep); unsigned int len, struct nfsd_cacherep **cacherep);
void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp, void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp,
int cachetype, __be32 *statp); int cachetype, __be32 *statp);
int nfsd_reply_cache_stats_show(struct seq_file *m, void *v); int nfsd_reply_cache_stats_show(struct seq_file *m, void *v);
......
...@@ -2804,7 +2804,7 @@ static int client_opens_release(struct inode *inode, struct file *file) ...@@ -2804,7 +2804,7 @@ static int client_opens_release(struct inode *inode, struct file *file)
/* XXX: alternatively, we could get/drop in seq start/stop */ /* XXX: alternatively, we could get/drop in seq start/stop */
drop_client(clp); drop_client(clp);
return 0; return seq_release(inode, file);
} }
static const struct file_operations client_states_fops = { static const struct file_operations client_states_fops = {
......
...@@ -369,33 +369,52 @@ nfsd_reply_cache_scan(struct shrinker *shrink, struct shrink_control *sc) ...@@ -369,33 +369,52 @@ nfsd_reply_cache_scan(struct shrinker *shrink, struct shrink_control *sc)
return freed; return freed;
} }
/* /**
* Walk an xdr_buf and get a CRC for at most the first RC_CSUMLEN bytes * nfsd_cache_csum - Checksum incoming NFS Call arguments
* @buf: buffer containing a whole RPC Call message
* @start: starting byte of the NFS Call header
* @remaining: size of the NFS Call header, in bytes
*
* Compute a weak checksum of the leading bytes of an NFS procedure
* call header to help verify that a retransmitted Call matches an
* entry in the duplicate reply cache.
*
* To avoid assumptions about how the RPC message is laid out in
* @buf and what else it might contain (eg, a GSS MIC suffix), the
* caller passes us the exact location and length of the NFS Call
* header.
*
* Returns a 32-bit checksum value, as defined in RFC 793.
*/ */
static __wsum static __wsum nfsd_cache_csum(struct xdr_buf *buf, unsigned int start,
nfsd_cache_csum(struct svc_rqst *rqstp) unsigned int remaining)
{ {
unsigned int base, len;
struct xdr_buf subbuf;
__wsum csum = 0;
void *p;
int idx; int idx;
unsigned int base;
__wsum csum; if (remaining > RC_CSUMLEN)
struct xdr_buf *buf = &rqstp->rq_arg; remaining = RC_CSUMLEN;
const unsigned char *p = buf->head[0].iov_base; if (xdr_buf_subsegment(buf, &subbuf, start, remaining))
size_t csum_len = min_t(size_t, buf->head[0].iov_len + buf->page_len, return csum;
RC_CSUMLEN);
size_t len = min(buf->head[0].iov_len, csum_len);
/* rq_arg.head first */ /* rq_arg.head first */
csum = csum_partial(p, len, 0); if (subbuf.head[0].iov_len) {
csum_len -= len; len = min_t(unsigned int, subbuf.head[0].iov_len, remaining);
csum = csum_partial(subbuf.head[0].iov_base, len, csum);
remaining -= len;
}
/* Continue into page array */ /* Continue into page array */
idx = buf->page_base / PAGE_SIZE; idx = subbuf.page_base / PAGE_SIZE;
base = buf->page_base & ~PAGE_MASK; base = subbuf.page_base & ~PAGE_MASK;
while (csum_len) { while (remaining) {
p = page_address(buf->pages[idx]) + base; p = page_address(subbuf.pages[idx]) + base;
len = min_t(size_t, PAGE_SIZE - base, csum_len); len = min_t(unsigned int, PAGE_SIZE - base, remaining);
csum = csum_partial(p, len, csum); csum = csum_partial(p, len, csum);
csum_len -= len; remaining -= len;
base = 0; base = 0;
++idx; ++idx;
} }
...@@ -466,6 +485,8 @@ nfsd_cache_insert(struct nfsd_drc_bucket *b, struct nfsd_cacherep *key, ...@@ -466,6 +485,8 @@ nfsd_cache_insert(struct nfsd_drc_bucket *b, struct nfsd_cacherep *key,
/** /**
* nfsd_cache_lookup - Find an entry in the duplicate reply cache * nfsd_cache_lookup - Find an entry in the duplicate reply cache
* @rqstp: Incoming Call to find * @rqstp: Incoming Call to find
* @start: starting byte in @rqstp->rq_arg of the NFS Call header
* @len: size of the NFS Call header, in bytes
* @cacherep: OUT: DRC entry for this request * @cacherep: OUT: DRC entry for this request
* *
* Try to find an entry matching the current call in the cache. When none * Try to find an entry matching the current call in the cache. When none
...@@ -479,7 +500,8 @@ nfsd_cache_insert(struct nfsd_drc_bucket *b, struct nfsd_cacherep *key, ...@@ -479,7 +500,8 @@ nfsd_cache_insert(struct nfsd_drc_bucket *b, struct nfsd_cacherep *key,
* %RC_REPLY: Reply from cache * %RC_REPLY: Reply from cache
* %RC_DROPIT: Do not process the request further * %RC_DROPIT: Do not process the request further
*/ */
int nfsd_cache_lookup(struct svc_rqst *rqstp, struct nfsd_cacherep **cacherep) int nfsd_cache_lookup(struct svc_rqst *rqstp, unsigned int start,
unsigned int len, struct nfsd_cacherep **cacherep)
{ {
struct nfsd_net *nn; struct nfsd_net *nn;
struct nfsd_cacherep *rp, *found; struct nfsd_cacherep *rp, *found;
...@@ -495,7 +517,7 @@ int nfsd_cache_lookup(struct svc_rqst *rqstp, struct nfsd_cacherep **cacherep) ...@@ -495,7 +517,7 @@ int nfsd_cache_lookup(struct svc_rqst *rqstp, struct nfsd_cacherep **cacherep)
goto out; goto out;
} }
csum = nfsd_cache_csum(rqstp); csum = nfsd_cache_csum(&rqstp->rq_arg, start, len);
/* /*
* Since the common case is a cache miss followed by an insert, * Since the common case is a cache miss followed by an insert,
...@@ -641,24 +663,17 @@ void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp, ...@@ -641,24 +663,17 @@ void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp,
return; return;
} }
/*
* Copy cached reply to current reply buffer. Should always fit.
* FIXME as reply is in a page, we should just attach the page, and
* keep a refcount....
*/
static int static int
nfsd_cache_append(struct svc_rqst *rqstp, struct kvec *data) nfsd_cache_append(struct svc_rqst *rqstp, struct kvec *data)
{ {
struct kvec *vec = &rqstp->rq_res.head[0]; __be32 *p;
if (vec->iov_len + data->iov_len > PAGE_SIZE) { p = xdr_reserve_space(&rqstp->rq_res_stream, data->iov_len);
printk(KERN_WARNING "nfsd: cached reply too large (%zd).\n", if (unlikely(!p))
data->iov_len); return false;
return 0; memcpy(p, data->iov_base, data->iov_len);
} xdr_commit_encode(&rqstp->rq_res_stream);
memcpy((char*)vec->iov_base + vec->iov_len, data->iov_base, data->iov_len); return true;
vec->iov_len += data->iov_len;
return 1;
} }
/* /*
......
...@@ -981,6 +981,8 @@ int nfsd_dispatch(struct svc_rqst *rqstp) ...@@ -981,6 +981,8 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
const struct svc_procedure *proc = rqstp->rq_procinfo; const struct svc_procedure *proc = rqstp->rq_procinfo;
__be32 *statp = rqstp->rq_accept_statp; __be32 *statp = rqstp->rq_accept_statp;
struct nfsd_cacherep *rp; struct nfsd_cacherep *rp;
unsigned int start, len;
__be32 *nfs_reply;
/* /*
* Give the xdr decoder a chance to change this if it wants * Give the xdr decoder a chance to change this if it wants
...@@ -988,6 +990,13 @@ int nfsd_dispatch(struct svc_rqst *rqstp) ...@@ -988,6 +990,13 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
*/ */
rqstp->rq_cachetype = proc->pc_cachetype; rqstp->rq_cachetype = proc->pc_cachetype;
/*
* ->pc_decode advances the argument stream past the NFS
* Call header, so grab the header's starting location and
* size now for the call to nfsd_cache_lookup().
*/
start = xdr_stream_pos(&rqstp->rq_arg_stream);
len = xdr_stream_remaining(&rqstp->rq_arg_stream);
if (!proc->pc_decode(rqstp, &rqstp->rq_arg_stream)) if (!proc->pc_decode(rqstp, &rqstp->rq_arg_stream))
goto out_decode_err; goto out_decode_err;
...@@ -1001,7 +1010,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp) ...@@ -1001,7 +1010,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
smp_store_release(&rqstp->rq_status_counter, rqstp->rq_status_counter | 1); smp_store_release(&rqstp->rq_status_counter, rqstp->rq_status_counter | 1);
rp = NULL; rp = NULL;
switch (nfsd_cache_lookup(rqstp, &rp)) { switch (nfsd_cache_lookup(rqstp, start, len, &rp)) {
case RC_DOIT: case RC_DOIT:
break; break;
case RC_REPLY: case RC_REPLY:
...@@ -1010,6 +1019,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp) ...@@ -1010,6 +1019,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
goto out_dropit; goto out_dropit;
} }
nfs_reply = xdr_inline_decode(&rqstp->rq_res_stream, 0);
*statp = proc->pc_func(rqstp); *statp = proc->pc_func(rqstp);
if (test_bit(RQ_DROPME, &rqstp->rq_flags)) if (test_bit(RQ_DROPME, &rqstp->rq_flags))
goto out_update_drop; goto out_update_drop;
...@@ -1023,7 +1033,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp) ...@@ -1023,7 +1033,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
*/ */
smp_store_release(&rqstp->rq_status_counter, rqstp->rq_status_counter + 1); smp_store_release(&rqstp->rq_status_counter, rqstp->rq_status_counter + 1);
nfsd_cache_update(rqstp, rp, rqstp->rq_cachetype, statp + 1); nfsd_cache_update(rqstp, rp, rqstp->rq_cachetype, nfs_reply);
out_cached_reply: out_cached_reply:
return 1; return 1;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment