Commit bb28378a authored by Linus Torvalds's avatar Linus Torvalds
Browse files
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
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -84,8 +84,8 @@ int nfsd_net_reply_cache_init(struct nfsd_net *nn);
void	nfsd_net_reply_cache_destroy(struct nfsd_net *nn);
int	nfsd_reply_cache_init(struct nfsd_net *);
void	nfsd_reply_cache_shutdown(struct nfsd_net *);
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);
void	nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp,
			  int cachetype, __be32 *statp);
int	nfsd_reply_cache_stats_show(struct seq_file *m, void *v);
+1 −1
Original line number Diff line number Diff line
@@ -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 */
	drop_client(clp);
	return 0;
	return seq_release(inode, file);
}

static const struct file_operations client_states_fops = {
+51 −36
Original line number Diff line number Diff line
@@ -369,33 +369,52 @@ nfsd_reply_cache_scan(struct shrinker *shrink, struct shrink_control *sc)
	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
nfsd_cache_csum(struct svc_rqst *rqstp)
static __wsum nfsd_cache_csum(struct xdr_buf *buf, unsigned int start,
			      unsigned int remaining)
{
	unsigned int base, len;
	struct xdr_buf subbuf;
	__wsum csum = 0;
	void *p;
	int idx;
	unsigned int base;
	__wsum csum;
	struct xdr_buf *buf = &rqstp->rq_arg;
	const unsigned char *p = buf->head[0].iov_base;
	size_t csum_len = min_t(size_t, buf->head[0].iov_len + buf->page_len,
				RC_CSUMLEN);
	size_t len = min(buf->head[0].iov_len, csum_len);

	if (remaining > RC_CSUMLEN)
		remaining = RC_CSUMLEN;
	if (xdr_buf_subsegment(buf, &subbuf, start, remaining))
		return csum;

	/* rq_arg.head first */
	csum = csum_partial(p, len, 0);
	csum_len -= len;
	if (subbuf.head[0].iov_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 */
	idx = buf->page_base / PAGE_SIZE;
	base = buf->page_base & ~PAGE_MASK;
	while (csum_len) {
		p = page_address(buf->pages[idx]) + base;
		len = min_t(size_t, PAGE_SIZE - base, csum_len);
	idx = subbuf.page_base / PAGE_SIZE;
	base = subbuf.page_base & ~PAGE_MASK;
	while (remaining) {
		p = page_address(subbuf.pages[idx]) + base;
		len = min_t(unsigned int, PAGE_SIZE - base, remaining);
		csum = csum_partial(p, len, csum);
		csum_len -= len;
		remaining -= len;
		base = 0;
		++idx;
	}
@@ -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
 * @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
 *
 * 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,
 *   %RC_REPLY: Reply from cache
 *   %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_cacherep	*rp, *found;
@@ -495,7 +517,7 @@ int nfsd_cache_lookup(struct svc_rqst *rqstp, struct nfsd_cacherep **cacherep)
		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,
@@ -641,24 +663,17 @@ void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp,
	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
nfsd_cache_append(struct svc_rqst *rqstp, struct kvec *data)
{
	struct kvec	*vec = &rqstp->rq_res.head[0];

	if (vec->iov_len + data->iov_len > PAGE_SIZE) {
		printk(KERN_WARNING "nfsd: cached reply too large (%zd).\n",
				data->iov_len);
		return 0;
	}
	memcpy((char*)vec->iov_base + vec->iov_len, data->iov_base, data->iov_len);
	vec->iov_len += data->iov_len;
	return 1;
	__be32 *p;

	p = xdr_reserve_space(&rqstp->rq_res_stream, data->iov_len);
	if (unlikely(!p))
		return false;
	memcpy(p, data->iov_base, data->iov_len);
	xdr_commit_encode(&rqstp->rq_res_stream);
	return true;
}

/*
+12 −2
Original line number Diff line number Diff line
@@ -981,6 +981,8 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
	const struct svc_procedure *proc = rqstp->rq_procinfo;
	__be32 *statp = rqstp->rq_accept_statp;
	struct nfsd_cacherep *rp;
	unsigned int start, len;
	__be32 *nfs_reply;

	/*
	 * Give the xdr decoder a chance to change this if it wants
@@ -988,6 +990,13 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
	 */
	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))
		goto out_decode_err;

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

	rp = NULL;
	switch (nfsd_cache_lookup(rqstp, &rp)) {
	switch (nfsd_cache_lookup(rqstp, start, len, &rp)) {
	case RC_DOIT:
		break;
	case RC_REPLY:
@@ -1010,6 +1019,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
		goto out_dropit;
	}

	nfs_reply = xdr_inline_decode(&rqstp->rq_res_stream, 0);
	*statp = proc->pc_func(rqstp);
	if (test_bit(RQ_DROPME, &rqstp->rq_flags))
		goto out_update_drop;
@@ -1023,7 +1033,7 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
	 */
	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:
	return 1;