Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit babddc72 authored by Bryan Schumaker's avatar Bryan Schumaker Committed by Trond Myklebust
Browse files

NFS: decode_dirent should use an xdr_stream



Convert nfs*xdr.c to use an xdr stream in decode_dirent.  This will prevent a
kernel oops that has been occuring when reading a vmapped page.

Signed-off-by: default avatarBryan Schumaker <bjschuma@netapp.com>
Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent ba8e452a
Loading
Loading
Loading
Loading
+21 −8
Original line number Diff line number Diff line
@@ -171,7 +171,7 @@ struct nfs_cache_array {

#define MAX_READDIR_ARRAY ((PAGE_SIZE - sizeof(struct nfs_cache_array)) / sizeof(struct nfs_cache_array_entry))

typedef __be32 * (*decode_dirent_t)(__be32 *, struct nfs_entry *, int);
typedef __be32 * (*decode_dirent_t)(struct xdr_stream *, struct nfs_entry *, int);
typedef struct {
	struct file	*file;
	struct page	*page;
@@ -357,13 +357,11 @@ int nfs_readdir_xdr_filler(struct page *xdr_page, nfs_readdir_descriptor_t *desc

/* Fill in an entry based on the xdr code stored in desc->page */
static
int xdr_decode(nfs_readdir_descriptor_t *desc, struct nfs_entry *entry, __be32 **ptr)
int xdr_decode(nfs_readdir_descriptor_t *desc, struct nfs_entry *entry, struct xdr_stream *stream)
{
	__be32	*p = *ptr;
	p = desc->decode(p, entry, desc->plus);
	__be32 *p = desc->decode(stream, entry, desc->plus);
	if (IS_ERR(p))
		return PTR_ERR(p);
	*ptr = p;

	entry->fattr->time_start = desc->timestamp;
	entry->fattr->gencount = desc->gencount;
@@ -438,10 +436,23 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
/* Perform conversion from xdr to cache array */
static
void nfs_readdir_page_filler(nfs_readdir_descriptor_t *desc, struct nfs_entry *entry,
				struct page *xdr_page, struct page *page)
				struct page *xdr_page, struct page *page, unsigned int buflen)
{
	struct xdr_stream stream;
	struct xdr_buf buf;
	__be32 *ptr = kmap(xdr_page);
	while (xdr_decode(desc, entry, &ptr) == 0) {

	buf.head->iov_base = xdr_page;
	buf.head->iov_len = buflen;
	buf.tail->iov_len = 0;
	buf.page_base = 0;
	buf.page_len = 0;
	buf.buflen = buf.head->iov_len;
	buf.len = buf.head->iov_len;

	xdr_init_decode(&stream, &buf, ptr);

	while (xdr_decode(desc, entry, &stream) == 0) {
		if (nfs_readdir_add_to_array(entry, page) == -1)
			break;
		if (desc->plus == 1)
@@ -458,6 +469,7 @@ int nfs_readdir_xdr_to_array(nfs_readdir_descriptor_t *desc, struct page *page,
	struct file	*file = desc->file;
	struct nfs_cache_array *array;
	int status = 0;
	unsigned int array_size = 1;

	entry.prev_cookie = 0;
	entry.cookie = *desc->dir_cookie;
@@ -476,9 +488,10 @@ int nfs_readdir_xdr_to_array(nfs_readdir_descriptor_t *desc, struct page *page,
		goto out_release_array;
	do {
		status = nfs_readdir_xdr_filler(xdr_page, desc, &entry, file, inode);

		if (status < 0)
			break;
		nfs_readdir_page_filler(desc, &entry, xdr_page, page);
		nfs_readdir_page_filler(desc, &entry, xdr_page, page, array_size * PAGE_SIZE);
	} while (array->eof_index < 0 && array->size < MAX_READDIR_ARRAY);

	put_page(xdr_page);
+3 −3
Original line number Diff line number Diff line
@@ -181,15 +181,15 @@ extern void nfs_destroy_directcache(void);
/* nfs2xdr.c */
extern int nfs_stat_to_errno(int);
extern struct rpc_procinfo nfs_procedures[];
extern __be32 * nfs_decode_dirent(__be32 *, struct nfs_entry *, int);
extern __be32 *nfs_decode_dirent(struct xdr_stream *, struct nfs_entry *, int);

/* nfs3xdr.c */
extern struct rpc_procinfo nfs3_procedures[];
extern __be32 *nfs3_decode_dirent(__be32 *, struct nfs_entry *, int);
extern __be32 *nfs3_decode_dirent(struct xdr_stream *, struct nfs_entry *, int);

/* nfs4xdr.c */
#ifdef CONFIG_NFS_V4
extern __be32 *nfs4_decode_dirent(__be32 *p, struct nfs_entry *entry, int plus);
extern __be32 *nfs4_decode_dirent(struct xdr_stream *, struct nfs_entry *entry, int plus);
#endif
#ifdef CONFIG_NFS_V4_1
extern const u32 nfs41_maxread_overhead;
+35 −4
Original line number Diff line number Diff line
@@ -500,25 +500,56 @@ nfs_xdr_readdirres(struct rpc_rqst *req, __be32 *p, void *dummy)
	goto out;
}

static void print_overflow_msg(const char *func, const struct xdr_stream *xdr)
{
	dprintk("nfs: %s: prematurely hit end of receive buffer. "
		"Remaining buffer length is %tu words.\n",
		func, xdr->end - xdr->p);
}

__be32 *
nfs_decode_dirent(__be32 *p, struct nfs_entry *entry, int plus)
nfs_decode_dirent(struct xdr_stream *xdr, struct nfs_entry *entry, int plus)
{
	if (!*p++) {
		if (!*p)
	__be32 *p;
	p = xdr_inline_decode(xdr, 4);
	if (unlikely(!p))
		goto out_overflow;
	if (!ntohl(*p++)) {
		p = xdr_inline_decode(xdr, 4);
		if (unlikely(!p))
			goto out_overflow;
		if (!ntohl(*p++))
			return ERR_PTR(-EAGAIN);
		entry->eof = 1;
		return ERR_PTR(-EBADCOOKIE);
	}

	p = xdr_inline_decode(xdr, 8);
	if (unlikely(!p))
		goto out_overflow;

	entry->ino	  = ntohl(*p++);
	entry->len	  = ntohl(*p++);

	p = xdr_inline_decode(xdr, entry->len + 4);
	if (unlikely(!p))
		goto out_overflow;
	entry->name	  = (const char *) p;
	p		 += XDR_QUADLEN(entry->len);
	entry->prev_cookie	  = entry->cookie;
	entry->cookie	  = ntohl(*p++);

	p = xdr_inline_peek(xdr, 8);
	if (p != NULL)
		entry->eof = !p[0] && p[1];
	else
		entry->eof = 0;

	return p;

out_overflow:
	print_overflow_msg(__func__, xdr);
	return ERR_PTR(-EIO);
}

/*
+87 −6
Original line number Diff line number Diff line
@@ -100,6 +100,13 @@ static const umode_t nfs_type2fmt[] = {
	[NF3FIFO] = S_IFIFO,
};

static void print_overflow_msg(const char *func, const struct xdr_stream *xdr)
{
	dprintk("nfs: %s: prematurely hit end of receive buffer. "
		"Remaining buffer length is %tu words.\n",
		func, xdr->end - xdr->p);
}

/*
 * Common NFS XDR functions as inlines
 */
@@ -119,6 +126,29 @@ xdr_decode_fhandle(__be32 *p, struct nfs_fh *fh)
	return NULL;
}

static inline __be32 *
xdr_decode_fhandle_stream(struct xdr_stream *xdr, struct nfs_fh *fh)
{
	__be32 *p;
	p = xdr_inline_decode(xdr, 4);
	if (unlikely(!p))
		goto out_overflow;
	fh->size = ntohl(*p++);

	if (fh->size <= NFS3_FHSIZE) {
		p = xdr_inline_decode(xdr, fh->size);
		if (unlikely(!p))
			goto out_overflow;
		memcpy(fh->data, p, fh->size);
		return p + XDR_QUADLEN(fh->size);
	}
	return NULL;

out_overflow:
	print_overflow_msg(__func__, xdr);
	return ERR_PTR(-EIO);
}

/*
 * Encode/decode time.
 */
@@ -240,6 +270,26 @@ xdr_decode_post_op_attr(__be32 *p, struct nfs_fattr *fattr)
	return p;
}

static inline __be32 *
xdr_decode_post_op_attr_stream(struct xdr_stream *xdr, struct nfs_fattr *fattr)
{
	__be32 *p;

	p = xdr_inline_decode(xdr, 4);
	if (unlikely(!p))
		goto out_overflow;
	if (ntohl(*p++)) {
		p = xdr_inline_decode(xdr, 84);
		if (unlikely(!p))
			goto out_overflow;
		p = xdr_decode_fattr(p, fattr);
	}
	return p;
out_overflow:
	print_overflow_msg(__func__, xdr);
	return ERR_PTR(-EIO);
}

static inline __be32 *
xdr_decode_pre_op_attr(__be32 *p, struct nfs_fattr *fattr)
{
@@ -616,19 +666,33 @@ nfs3_xdr_readdirres(struct rpc_rqst *req, __be32 *p, struct nfs3_readdirres *res
}

__be32 *
nfs3_decode_dirent(__be32 *p, struct nfs_entry *entry, int plus)
nfs3_decode_dirent(struct xdr_stream *xdr, struct nfs_entry *entry, int plus)
{
	__be32 *p;
	struct nfs_entry old = *entry;

	if (!*p++) {
		if (!*p)
	p = xdr_inline_decode(xdr, 4);
	if (unlikely(!p))
		goto out_overflow;
	if (!ntohl(*p++)) {
		p = xdr_inline_decode(xdr, 4);
		if (unlikely(!p))
			goto out_overflow;
		if (!ntohl(*p++))
			return ERR_PTR(-EAGAIN);
		entry->eof = 1;
		return ERR_PTR(-EBADCOOKIE);
	}

	p = xdr_inline_decode(xdr, 12);
	if (unlikely(!p))
		goto out_overflow;
	p = xdr_decode_hyper(p, &entry->ino);
	entry->len  = ntohl(*p++);

	p = xdr_inline_decode(xdr, entry->len + 8);
	if (unlikely(!p))
		goto out_overflow;
	entry->name = (const char *) p;
	p += XDR_QUADLEN(entry->len);
	entry->prev_cookie = entry->cookie;
@@ -636,10 +700,17 @@ nfs3_decode_dirent(__be32 *p, struct nfs_entry *entry, int plus)

	if (plus) {
		entry->fattr->valid = 0;
		p = xdr_decode_post_op_attr(p, entry->fattr);
		p = xdr_decode_post_op_attr_stream(xdr, entry->fattr);
		if (IS_ERR(p))
			goto out_overflow_exit;
		/* In fact, a post_op_fh3: */
		p = xdr_inline_decode(xdr, 4);
		if (unlikely(!p))
			goto out_overflow;
		if (*p++) {
			p = xdr_decode_fhandle(p, entry->fh);
			p = xdr_decode_fhandle_stream(xdr, entry->fh);
			if (IS_ERR(p))
				goto out_overflow_exit;
			/* Ugh -- server reply was truncated */
			if (p == NULL) {
				dprintk("NFS: FH truncated\n");
@@ -650,8 +721,18 @@ nfs3_decode_dirent(__be32 *p, struct nfs_entry *entry, int plus)
			memset((u8*)(entry->fh), 0, sizeof(*entry->fh));
	}

	p = xdr_inline_peek(xdr, 8);
	if (p != NULL)
		entry->eof = !p[0] && p[1];
	else
		entry->eof = 0;

	return p;

out_overflow:
	print_overflow_msg(__func__, xdr);
out_overflow_exit:
	return ERR_PTR(-EIO);
}

/*
+1 −1
Original line number Diff line number Diff line
@@ -331,7 +331,7 @@ extern void nfs_free_seqid(struct nfs_seqid *seqid);
extern const nfs4_stateid zero_stateid;

/* nfs4xdr.c */
extern __be32 *nfs4_decode_dirent(__be32 *p, struct nfs_entry *entry, int plus);
extern __be32 *nfs4_decode_dirent(struct xdr_stream *, struct nfs_entry *entry, int plus);
extern struct rpc_procinfo nfs4_procedures[];

struct nfs4_mount_data;
Loading