Commit 8d5658c9 authored by Trond Myklebust's avatar Trond Myklebust

NFS: Fix a buffer overflow in the allocation of struct nfs_read/writedata

Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent c63c7b05
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/atomic.h> #include <asm/atomic.h>
#include "internal.h"
#include "iostat.h" #include "iostat.h"
#define NFSDBG_FACILITY NFSDBG_VFS #define NFSDBG_FACILITY NFSDBG_VFS
...@@ -271,7 +272,7 @@ static ssize_t nfs_direct_read_schedule(struct nfs_direct_req *dreq, unsigned lo ...@@ -271,7 +272,7 @@ static ssize_t nfs_direct_read_schedule(struct nfs_direct_req *dreq, unsigned lo
bytes = min(rsize,count); bytes = min(rsize,count);
result = -ENOMEM; result = -ENOMEM;
data = nfs_readdata_alloc(pgbase + bytes); data = nfs_readdata_alloc(nfs_page_array_len(pgbase, bytes));
if (unlikely(!data)) if (unlikely(!data))
break; break;
...@@ -602,7 +603,7 @@ static ssize_t nfs_direct_write_schedule(struct nfs_direct_req *dreq, unsigned l ...@@ -602,7 +603,7 @@ static ssize_t nfs_direct_write_schedule(struct nfs_direct_req *dreq, unsigned l
bytes = min(wsize,count); bytes = min(wsize,count);
result = -ENOMEM; result = -ENOMEM;
data = nfs_writedata_alloc(pgbase + bytes); data = nfs_writedata_alloc(nfs_page_array_len(pgbase, bytes));
if (unlikely(!data)) if (unlikely(!data))
break; break;
......
...@@ -231,3 +231,15 @@ unsigned int nfs_page_length(struct page *page) ...@@ -231,3 +231,15 @@ unsigned int nfs_page_length(struct page *page)
} }
return 0; return 0;
} }
/*
* Determine the number of pages in an array of length 'len' and
* with a base offset of 'base'
*/
static inline
unsigned int nfs_page_array_len(unsigned int base, size_t len)
{
return ((unsigned long)len + (unsigned long)base +
PAGE_SIZE - 1) >> PAGE_SHIFT;
}
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
#include <linux/nfs_fs.h> #include <linux/nfs_fs.h>
#include <linux/nfs_mount.h> #include <linux/nfs_mount.h>
#include "internal.h"
#define NFS_PARANOIA 1 #define NFS_PARANOIA 1
static struct kmem_cache *nfs_page_cachep; static struct kmem_cache *nfs_page_cachep;
...@@ -231,7 +233,7 @@ out: ...@@ -231,7 +233,7 @@ out:
*/ */
void nfs_pageio_init(struct nfs_pageio_descriptor *desc, void nfs_pageio_init(struct nfs_pageio_descriptor *desc,
struct inode *inode, struct inode *inode,
int (*doio)(struct inode *, struct list_head *, size_t, int), int (*doio)(struct inode *, struct list_head *, unsigned int, size_t, int),
unsigned int bsize, unsigned int bsize,
int io_flags) int io_flags)
{ {
...@@ -298,8 +300,10 @@ static int nfs_pageio_do_add_request(struct nfs_pageio_descriptor *desc, ...@@ -298,8 +300,10 @@ static int nfs_pageio_do_add_request(struct nfs_pageio_descriptor *desc,
* since nfs_flush_multi and nfs_pagein_multi assume you * since nfs_flush_multi and nfs_pagein_multi assume you
* can have only one struct nfs_page. * can have only one struct nfs_page.
*/ */
if (desc->pg_bsize < PAGE_SIZE)
return 0;
newlen += desc->pg_count; newlen += desc->pg_count;
if (desc->pg_base + newlen > desc->pg_bsize) if (newlen > desc->pg_bsize)
return 0; return 0;
prev = nfs_list_entry(desc->pg_list.prev); prev = nfs_list_entry(desc->pg_list.prev);
if (!nfs_can_coalesce_requests(prev, req)) if (!nfs_can_coalesce_requests(prev, req))
...@@ -320,6 +324,8 @@ static void nfs_pageio_doio(struct nfs_pageio_descriptor *desc) ...@@ -320,6 +324,8 @@ static void nfs_pageio_doio(struct nfs_pageio_descriptor *desc)
if (!list_empty(&desc->pg_list)) { if (!list_empty(&desc->pg_list)) {
int error = desc->pg_doio(desc->pg_inode, int error = desc->pg_doio(desc->pg_inode,
&desc->pg_list, &desc->pg_list,
nfs_page_array_len(desc->pg_base,
desc->pg_count),
desc->pg_count, desc->pg_count,
desc->pg_ioflags); desc->pg_ioflags);
if (error < 0) if (error < 0)
......
...@@ -27,8 +27,8 @@ ...@@ -27,8 +27,8 @@
#define NFSDBG_FACILITY NFSDBG_PAGECACHE #define NFSDBG_FACILITY NFSDBG_PAGECACHE
static int nfs_pagein_multi(struct inode *, struct list_head *, size_t, int); static int nfs_pagein_multi(struct inode *, struct list_head *, unsigned int, size_t, int);
static int nfs_pagein_one(struct inode *, struct list_head *, size_t, int); static int nfs_pagein_one(struct inode *, struct list_head *, unsigned int, size_t, int);
static const struct rpc_call_ops nfs_read_partial_ops; static const struct rpc_call_ops nfs_read_partial_ops;
static const struct rpc_call_ops nfs_read_full_ops; static const struct rpc_call_ops nfs_read_full_ops;
...@@ -37,9 +37,8 @@ static mempool_t *nfs_rdata_mempool; ...@@ -37,9 +37,8 @@ static mempool_t *nfs_rdata_mempool;
#define MIN_POOL_READ (32) #define MIN_POOL_READ (32)
struct nfs_read_data *nfs_readdata_alloc(size_t len) struct nfs_read_data *nfs_readdata_alloc(unsigned int pagecount)
{ {
unsigned int pagecount = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
struct nfs_read_data *p = mempool_alloc(nfs_rdata_mempool, GFP_NOFS); struct nfs_read_data *p = mempool_alloc(nfs_rdata_mempool, GFP_NOFS);
if (p) { if (p) {
...@@ -135,9 +134,9 @@ static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode, ...@@ -135,9 +134,9 @@ static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode,
nfs_list_add_request(new, &one_request); nfs_list_add_request(new, &one_request);
if (NFS_SERVER(inode)->rsize < PAGE_CACHE_SIZE) if (NFS_SERVER(inode)->rsize < PAGE_CACHE_SIZE)
nfs_pagein_multi(inode, &one_request, len, 0); nfs_pagein_multi(inode, &one_request, 1, len, 0);
else else
nfs_pagein_one(inode, &one_request, len, 0); nfs_pagein_one(inode, &one_request, 1, len, 0);
return 0; return 0;
} }
...@@ -234,7 +233,7 @@ static void nfs_execute_read(struct nfs_read_data *data) ...@@ -234,7 +233,7 @@ static void nfs_execute_read(struct nfs_read_data *data)
* won't see the new data until our attribute cache is updated. This is more * won't see the new data until our attribute cache is updated. This is more
* or less conventional NFS client behavior. * or less conventional NFS client behavior.
*/ */
static int nfs_pagein_multi(struct inode *inode, struct list_head *head, size_t count, int flags) static int nfs_pagein_multi(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int flags)
{ {
struct nfs_page *req = nfs_list_entry(head->next); struct nfs_page *req = nfs_list_entry(head->next);
struct page *page = req->wb_page; struct page *page = req->wb_page;
...@@ -250,7 +249,7 @@ static int nfs_pagein_multi(struct inode *inode, struct list_head *head, size_t ...@@ -250,7 +249,7 @@ static int nfs_pagein_multi(struct inode *inode, struct list_head *head, size_t
do { do {
size_t len = min(nbytes,rsize); size_t len = min(nbytes,rsize);
data = nfs_readdata_alloc(len); data = nfs_readdata_alloc(1);
if (!data) if (!data)
goto out_bad; goto out_bad;
INIT_LIST_HEAD(&data->pages); INIT_LIST_HEAD(&data->pages);
...@@ -291,13 +290,13 @@ out_bad: ...@@ -291,13 +290,13 @@ out_bad:
return -ENOMEM; return -ENOMEM;
} }
static int nfs_pagein_one(struct inode *inode, struct list_head *head, size_t count, int flags) static int nfs_pagein_one(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int flags)
{ {
struct nfs_page *req; struct nfs_page *req;
struct page **pages; struct page **pages;
struct nfs_read_data *data; struct nfs_read_data *data;
data = nfs_readdata_alloc(count); data = nfs_readdata_alloc(npages);
if (!data) if (!data)
goto out_bad; goto out_bad;
......
...@@ -72,9 +72,8 @@ void nfs_commit_free(struct nfs_write_data *wdata) ...@@ -72,9 +72,8 @@ void nfs_commit_free(struct nfs_write_data *wdata)
call_rcu_bh(&wdata->task.u.tk_rcu, nfs_commit_rcu_free); call_rcu_bh(&wdata->task.u.tk_rcu, nfs_commit_rcu_free);
} }
struct nfs_write_data *nfs_writedata_alloc(size_t len) struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount)
{ {
unsigned int pagecount = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, GFP_NOFS); struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, GFP_NOFS);
if (p) { if (p) {
...@@ -832,7 +831,7 @@ static void nfs_execute_write(struct nfs_write_data *data) ...@@ -832,7 +831,7 @@ static void nfs_execute_write(struct nfs_write_data *data)
* Generate multiple small requests to write out a single * Generate multiple small requests to write out a single
* contiguous dirty area on one page. * contiguous dirty area on one page.
*/ */
static int nfs_flush_multi(struct inode *inode, struct list_head *head, size_t count, int how) static int nfs_flush_multi(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int how)
{ {
struct nfs_page *req = nfs_list_entry(head->next); struct nfs_page *req = nfs_list_entry(head->next);
struct page *page = req->wb_page; struct page *page = req->wb_page;
...@@ -848,7 +847,7 @@ static int nfs_flush_multi(struct inode *inode, struct list_head *head, size_t c ...@@ -848,7 +847,7 @@ static int nfs_flush_multi(struct inode *inode, struct list_head *head, size_t c
do { do {
size_t len = min(nbytes, wsize); size_t len = min(nbytes, wsize);
data = nfs_writedata_alloc(len); data = nfs_writedata_alloc(1);
if (!data) if (!data)
goto out_bad; goto out_bad;
list_add(&data->pages, &list); list_add(&data->pages, &list);
...@@ -897,13 +896,13 @@ out_bad: ...@@ -897,13 +896,13 @@ out_bad:
* This is the case if nfs_updatepage detects a conflicting request * This is the case if nfs_updatepage detects a conflicting request
* that has been written but not committed. * that has been written but not committed.
*/ */
static int nfs_flush_one(struct inode *inode, struct list_head *head, size_t count, int how) static int nfs_flush_one(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int how)
{ {
struct nfs_page *req; struct nfs_page *req;
struct page **pages; struct page **pages;
struct nfs_write_data *data; struct nfs_write_data *data;
data = nfs_writedata_alloc(count); data = nfs_writedata_alloc(npages);
if (!data) if (!data)
goto out_bad; goto out_bad;
......
...@@ -455,7 +455,7 @@ nfs_have_writebacks(struct inode *inode) ...@@ -455,7 +455,7 @@ nfs_have_writebacks(struct inode *inode)
/* /*
* Allocate nfs_write_data structures * Allocate nfs_write_data structures
*/ */
extern struct nfs_write_data *nfs_writedata_alloc(size_t len); extern struct nfs_write_data *nfs_writedata_alloc(unsigned int npages);
/* /*
* linux/fs/nfs/read.c * linux/fs/nfs/read.c
...@@ -469,7 +469,7 @@ extern void nfs_readdata_release(void *data); ...@@ -469,7 +469,7 @@ extern void nfs_readdata_release(void *data);
/* /*
* Allocate nfs_read_data structures * Allocate nfs_read_data structures
*/ */
extern struct nfs_read_data *nfs_readdata_alloc(size_t len); extern struct nfs_read_data *nfs_readdata_alloc(unsigned int npages);
/* /*
* linux/fs/nfs3proc.c * linux/fs/nfs3proc.c
......
...@@ -55,7 +55,7 @@ struct nfs_pageio_descriptor { ...@@ -55,7 +55,7 @@ struct nfs_pageio_descriptor {
unsigned int pg_base; unsigned int pg_base;
struct inode *pg_inode; struct inode *pg_inode;
int (*pg_doio)(struct inode *, struct list_head *, size_t, int); int (*pg_doio)(struct inode *, struct list_head *, unsigned int, size_t, int);
int pg_ioflags; int pg_ioflags;
int pg_error; int pg_error;
}; };
...@@ -75,7 +75,7 @@ extern int nfs_scan_list(struct nfs_inode *nfsi, struct list_head *head, struct ...@@ -75,7 +75,7 @@ extern int nfs_scan_list(struct nfs_inode *nfsi, struct list_head *head, struct
unsigned long idx_start, unsigned int npages); unsigned long idx_start, unsigned int npages);
extern void nfs_pageio_init(struct nfs_pageio_descriptor *desc, extern void nfs_pageio_init(struct nfs_pageio_descriptor *desc,
struct inode *inode, struct inode *inode,
int (*doio)(struct inode *, struct list_head *, size_t, int), int (*doio)(struct inode *, struct list_head *, unsigned int, size_t, int),
size_t bsize, size_t bsize,
int how); int how);
extern int nfs_pageio_add_request(struct nfs_pageio_descriptor *, extern int nfs_pageio_add_request(struct nfs_pageio_descriptor *,
......
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