diff options
| author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2007-04-10 09:26:35 -0400 |
|---|---|---|
| committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2007-05-01 01:17:07 -0400 |
| commit | 8d5658c949e6d89edc579a1f112aeee3bc232a8e (patch) | |
| tree | f206d3f6809eeb0ca23c1999cf79aa294968b113 /fs/nfs | |
| parent | c63c7b051395368573779c8309aa5c990dcf2f96 (diff) | |
NFS: Fix a buffer overflow in the allocation of struct nfs_read/writedata
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs')
| -rw-r--r-- | fs/nfs/direct.c | 5 | ||||
| -rw-r--r-- | fs/nfs/internal.h | 12 | ||||
| -rw-r--r-- | fs/nfs/pagelist.c | 10 | ||||
| -rw-r--r-- | fs/nfs/read.c | 19 | ||||
| -rw-r--r-- | fs/nfs/write.c | 11 |
5 files changed, 37 insertions, 20 deletions
diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c index 2877744cb606..889de60f8a84 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c | |||
| @@ -54,6 +54,7 @@ | |||
| 54 | #include <asm/uaccess.h> | 54 | #include <asm/uaccess.h> |
| 55 | #include <asm/atomic.h> | 55 | #include <asm/atomic.h> |
| 56 | 56 | ||
| 57 | #include "internal.h" | ||
| 57 | #include "iostat.h" | 58 | #include "iostat.h" |
| 58 | 59 | ||
| 59 | #define NFSDBG_FACILITY NFSDBG_VFS | 60 | #define NFSDBG_FACILITY NFSDBG_VFS |
| @@ -271,7 +272,7 @@ static ssize_t nfs_direct_read_schedule(struct nfs_direct_req *dreq, unsigned lo | |||
| 271 | bytes = min(rsize,count); | 272 | bytes = min(rsize,count); |
| 272 | 273 | ||
| 273 | result = -ENOMEM; | 274 | result = -ENOMEM; |
| 274 | data = nfs_readdata_alloc(pgbase + bytes); | 275 | data = nfs_readdata_alloc(nfs_page_array_len(pgbase, bytes)); |
| 275 | if (unlikely(!data)) | 276 | if (unlikely(!data)) |
| 276 | break; | 277 | break; |
| 277 | 278 | ||
| @@ -602,7 +603,7 @@ static ssize_t nfs_direct_write_schedule(struct nfs_direct_req *dreq, unsigned l | |||
| 602 | bytes = min(wsize,count); | 603 | bytes = min(wsize,count); |
| 603 | 604 | ||
| 604 | result = -ENOMEM; | 605 | result = -ENOMEM; |
| 605 | data = nfs_writedata_alloc(pgbase + bytes); | 606 | data = nfs_writedata_alloc(nfs_page_array_len(pgbase, bytes)); |
| 606 | if (unlikely(!data)) | 607 | if (unlikely(!data)) |
| 607 | break; | 608 | break; |
| 608 | 609 | ||
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 6610f2b02077..ad2b40db1e65 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h | |||
| @@ -231,3 +231,15 @@ unsigned int nfs_page_length(struct page *page) | |||
| 231 | } | 231 | } |
| 232 | return 0; | 232 | return 0; |
| 233 | } | 233 | } |
| 234 | |||
| 235 | /* | ||
| 236 | * Determine the number of pages in an array of length 'len' and | ||
| 237 | * with a base offset of 'base' | ||
| 238 | */ | ||
| 239 | static inline | ||
| 240 | unsigned int nfs_page_array_len(unsigned int base, size_t len) | ||
| 241 | { | ||
| 242 | return ((unsigned long)len + (unsigned long)base + | ||
| 243 | PAGE_SIZE - 1) >> PAGE_SHIFT; | ||
| 244 | } | ||
| 245 | |||
diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c index ea1a85df9ab1..096efd73eb4c 100644 --- a/fs/nfs/pagelist.c +++ b/fs/nfs/pagelist.c | |||
| @@ -18,6 +18,8 @@ | |||
| 18 | #include <linux/nfs_fs.h> | 18 | #include <linux/nfs_fs.h> |
| 19 | #include <linux/nfs_mount.h> | 19 | #include <linux/nfs_mount.h> |
| 20 | 20 | ||
| 21 | #include "internal.h" | ||
| 22 | |||
| 21 | #define NFS_PARANOIA 1 | 23 | #define NFS_PARANOIA 1 |
| 22 | 24 | ||
| 23 | static struct kmem_cache *nfs_page_cachep; | 25 | static struct kmem_cache *nfs_page_cachep; |
| @@ -231,7 +233,7 @@ out: | |||
| 231 | */ | 233 | */ |
| 232 | void nfs_pageio_init(struct nfs_pageio_descriptor *desc, | 234 | void nfs_pageio_init(struct nfs_pageio_descriptor *desc, |
| 233 | struct inode *inode, | 235 | struct inode *inode, |
| 234 | int (*doio)(struct inode *, struct list_head *, size_t, int), | 236 | int (*doio)(struct inode *, struct list_head *, unsigned int, size_t, int), |
| 235 | unsigned int bsize, | 237 | unsigned int bsize, |
| 236 | int io_flags) | 238 | int io_flags) |
| 237 | { | 239 | { |
| @@ -298,8 +300,10 @@ static int nfs_pageio_do_add_request(struct nfs_pageio_descriptor *desc, | |||
| 298 | * since nfs_flush_multi and nfs_pagein_multi assume you | 300 | * since nfs_flush_multi and nfs_pagein_multi assume you |
| 299 | * can have only one struct nfs_page. | 301 | * can have only one struct nfs_page. |
| 300 | */ | 302 | */ |
| 303 | if (desc->pg_bsize < PAGE_SIZE) | ||
| 304 | return 0; | ||
| 301 | newlen += desc->pg_count; | 305 | newlen += desc->pg_count; |
| 302 | if (desc->pg_base + newlen > desc->pg_bsize) | 306 | if (newlen > desc->pg_bsize) |
| 303 | return 0; | 307 | return 0; |
| 304 | prev = nfs_list_entry(desc->pg_list.prev); | 308 | prev = nfs_list_entry(desc->pg_list.prev); |
| 305 | if (!nfs_can_coalesce_requests(prev, req)) | 309 | if (!nfs_can_coalesce_requests(prev, req)) |
| @@ -320,6 +324,8 @@ static void nfs_pageio_doio(struct nfs_pageio_descriptor *desc) | |||
| 320 | if (!list_empty(&desc->pg_list)) { | 324 | if (!list_empty(&desc->pg_list)) { |
| 321 | int error = desc->pg_doio(desc->pg_inode, | 325 | int error = desc->pg_doio(desc->pg_inode, |
| 322 | &desc->pg_list, | 326 | &desc->pg_list, |
| 327 | nfs_page_array_len(desc->pg_base, | ||
| 328 | desc->pg_count), | ||
| 323 | desc->pg_count, | 329 | desc->pg_count, |
| 324 | desc->pg_ioflags); | 330 | desc->pg_ioflags); |
| 325 | if (error < 0) | 331 | if (error < 0) |
diff --git a/fs/nfs/read.c b/fs/nfs/read.c index f0016062340d..9a55807b2a70 100644 --- a/fs/nfs/read.c +++ b/fs/nfs/read.c | |||
| @@ -27,8 +27,8 @@ | |||
| 27 | 27 | ||
| 28 | #define NFSDBG_FACILITY NFSDBG_PAGECACHE | 28 | #define NFSDBG_FACILITY NFSDBG_PAGECACHE |
| 29 | 29 | ||
| 30 | static int nfs_pagein_multi(struct inode *, struct list_head *, size_t, int); | 30 | static int nfs_pagein_multi(struct inode *, struct list_head *, unsigned int, size_t, int); |
| 31 | static int nfs_pagein_one(struct inode *, struct list_head *, size_t, int); | 31 | static int nfs_pagein_one(struct inode *, struct list_head *, unsigned int, size_t, int); |
| 32 | static const struct rpc_call_ops nfs_read_partial_ops; | 32 | static const struct rpc_call_ops nfs_read_partial_ops; |
| 33 | static const struct rpc_call_ops nfs_read_full_ops; | 33 | static const struct rpc_call_ops nfs_read_full_ops; |
| 34 | 34 | ||
| @@ -37,9 +37,8 @@ static mempool_t *nfs_rdata_mempool; | |||
| 37 | 37 | ||
| 38 | #define MIN_POOL_READ (32) | 38 | #define MIN_POOL_READ (32) |
| 39 | 39 | ||
| 40 | struct nfs_read_data *nfs_readdata_alloc(size_t len) | 40 | struct nfs_read_data *nfs_readdata_alloc(unsigned int pagecount) |
| 41 | { | 41 | { |
| 42 | unsigned int pagecount = (len + PAGE_SIZE - 1) >> PAGE_SHIFT; | ||
| 43 | struct nfs_read_data *p = mempool_alloc(nfs_rdata_mempool, GFP_NOFS); | 42 | struct nfs_read_data *p = mempool_alloc(nfs_rdata_mempool, GFP_NOFS); |
| 44 | 43 | ||
| 45 | if (p) { | 44 | if (p) { |
| @@ -135,9 +134,9 @@ static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode, | |||
| 135 | 134 | ||
| 136 | nfs_list_add_request(new, &one_request); | 135 | nfs_list_add_request(new, &one_request); |
| 137 | if (NFS_SERVER(inode)->rsize < PAGE_CACHE_SIZE) | 136 | if (NFS_SERVER(inode)->rsize < PAGE_CACHE_SIZE) |
| 138 | nfs_pagein_multi(inode, &one_request, len, 0); | 137 | nfs_pagein_multi(inode, &one_request, 1, len, 0); |
| 139 | else | 138 | else |
| 140 | nfs_pagein_one(inode, &one_request, len, 0); | 139 | nfs_pagein_one(inode, &one_request, 1, len, 0); |
| 141 | return 0; | 140 | return 0; |
| 142 | } | 141 | } |
| 143 | 142 | ||
| @@ -234,7 +233,7 @@ static void nfs_execute_read(struct nfs_read_data *data) | |||
| 234 | * won't see the new data until our attribute cache is updated. This is more | 233 | * won't see the new data until our attribute cache is updated. This is more |
| 235 | * or less conventional NFS client behavior. | 234 | * or less conventional NFS client behavior. |
| 236 | */ | 235 | */ |
| 237 | static int nfs_pagein_multi(struct inode *inode, struct list_head *head, size_t count, int flags) | 236 | static int nfs_pagein_multi(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int flags) |
| 238 | { | 237 | { |
| 239 | struct nfs_page *req = nfs_list_entry(head->next); | 238 | struct nfs_page *req = nfs_list_entry(head->next); |
| 240 | struct page *page = req->wb_page; | 239 | 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 | do { | 249 | do { |
| 251 | size_t len = min(nbytes,rsize); | 250 | size_t len = min(nbytes,rsize); |
| 252 | 251 | ||
| 253 | data = nfs_readdata_alloc(len); | 252 | data = nfs_readdata_alloc(1); |
| 254 | if (!data) | 253 | if (!data) |
| 255 | goto out_bad; | 254 | goto out_bad; |
| 256 | INIT_LIST_HEAD(&data->pages); | 255 | INIT_LIST_HEAD(&data->pages); |
| @@ -291,13 +290,13 @@ out_bad: | |||
| 291 | return -ENOMEM; | 290 | return -ENOMEM; |
| 292 | } | 291 | } |
| 293 | 292 | ||
| 294 | static int nfs_pagein_one(struct inode *inode, struct list_head *head, size_t count, int flags) | 293 | static int nfs_pagein_one(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int flags) |
| 295 | { | 294 | { |
| 296 | struct nfs_page *req; | 295 | struct nfs_page *req; |
| 297 | struct page **pages; | 296 | struct page **pages; |
| 298 | struct nfs_read_data *data; | 297 | struct nfs_read_data *data; |
| 299 | 298 | ||
| 300 | data = nfs_readdata_alloc(count); | 299 | data = nfs_readdata_alloc(npages); |
| 301 | if (!data) | 300 | if (!data) |
| 302 | goto out_bad; | 301 | goto out_bad; |
| 303 | 302 | ||
diff --git a/fs/nfs/write.c b/fs/nfs/write.c index 6ce2d94e7b3f..0a8bbc399689 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c | |||
| @@ -72,9 +72,8 @@ void nfs_commit_free(struct nfs_write_data *wdata) | |||
| 72 | call_rcu_bh(&wdata->task.u.tk_rcu, nfs_commit_rcu_free); | 72 | call_rcu_bh(&wdata->task.u.tk_rcu, nfs_commit_rcu_free); |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | struct nfs_write_data *nfs_writedata_alloc(size_t len) | 75 | struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount) |
| 76 | { | 76 | { |
| 77 | unsigned int pagecount = (len + PAGE_SIZE - 1) >> PAGE_SHIFT; | ||
| 78 | struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, GFP_NOFS); | 77 | struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, GFP_NOFS); |
| 79 | 78 | ||
| 80 | if (p) { | 79 | if (p) { |
| @@ -832,7 +831,7 @@ static void nfs_execute_write(struct nfs_write_data *data) | |||
| 832 | * Generate multiple small requests to write out a single | 831 | * Generate multiple small requests to write out a single |
| 833 | * contiguous dirty area on one page. | 832 | * contiguous dirty area on one page. |
| 834 | */ | 833 | */ |
| 835 | static int nfs_flush_multi(struct inode *inode, struct list_head *head, size_t count, int how) | 834 | static int nfs_flush_multi(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int how) |
| 836 | { | 835 | { |
| 837 | struct nfs_page *req = nfs_list_entry(head->next); | 836 | struct nfs_page *req = nfs_list_entry(head->next); |
| 838 | struct page *page = req->wb_page; | 837 | 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 | do { | 847 | do { |
| 849 | size_t len = min(nbytes, wsize); | 848 | size_t len = min(nbytes, wsize); |
| 850 | 849 | ||
| 851 | data = nfs_writedata_alloc(len); | 850 | data = nfs_writedata_alloc(1); |
| 852 | if (!data) | 851 | if (!data) |
| 853 | goto out_bad; | 852 | goto out_bad; |
| 854 | list_add(&data->pages, &list); | 853 | list_add(&data->pages, &list); |
| @@ -897,13 +896,13 @@ out_bad: | |||
| 897 | * This is the case if nfs_updatepage detects a conflicting request | 896 | * This is the case if nfs_updatepage detects a conflicting request |
| 898 | * that has been written but not committed. | 897 | * that has been written but not committed. |
| 899 | */ | 898 | */ |
| 900 | static int nfs_flush_one(struct inode *inode, struct list_head *head, size_t count, int how) | 899 | static int nfs_flush_one(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int how) |
| 901 | { | 900 | { |
| 902 | struct nfs_page *req; | 901 | struct nfs_page *req; |
| 903 | struct page **pages; | 902 | struct page **pages; |
| 904 | struct nfs_write_data *data; | 903 | struct nfs_write_data *data; |
| 905 | 904 | ||
| 906 | data = nfs_writedata_alloc(count); | 905 | data = nfs_writedata_alloc(npages); |
| 907 | if (!data) | 906 | if (!data) |
| 908 | goto out_bad; | 907 | goto out_bad; |
| 909 | 908 | ||
