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 | |
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>
-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 | ||||
-rw-r--r-- | include/linux/nfs_fs.h | 4 | ||||
-rw-r--r-- | include/linux/nfs_page.h | 4 |
7 files changed, 41 insertions, 24 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 | ||
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index e9ae0c6e2c62..0543439a97af 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h | |||
@@ -455,7 +455,7 @@ nfs_have_writebacks(struct inode *inode) | |||
455 | /* | 455 | /* |
456 | * Allocate nfs_write_data structures | 456 | * Allocate nfs_write_data structures |
457 | */ | 457 | */ |
458 | extern struct nfs_write_data *nfs_writedata_alloc(size_t len); | 458 | extern struct nfs_write_data *nfs_writedata_alloc(unsigned int npages); |
459 | 459 | ||
460 | /* | 460 | /* |
461 | * linux/fs/nfs/read.c | 461 | * linux/fs/nfs/read.c |
@@ -469,7 +469,7 @@ extern void nfs_readdata_release(void *data); | |||
469 | /* | 469 | /* |
470 | * Allocate nfs_read_data structures | 470 | * Allocate nfs_read_data structures |
471 | */ | 471 | */ |
472 | extern struct nfs_read_data *nfs_readdata_alloc(size_t len); | 472 | extern struct nfs_read_data *nfs_readdata_alloc(unsigned int npages); |
473 | 473 | ||
474 | /* | 474 | /* |
475 | * linux/fs/nfs3proc.c | 475 | * linux/fs/nfs3proc.c |
diff --git a/include/linux/nfs_page.h b/include/linux/nfs_page.h index e556e57ef7ad..8e9e7bceda48 100644 --- a/include/linux/nfs_page.h +++ b/include/linux/nfs_page.h | |||
@@ -55,7 +55,7 @@ struct nfs_pageio_descriptor { | |||
55 | unsigned int pg_base; | 55 | unsigned int pg_base; |
56 | 56 | ||
57 | struct inode *pg_inode; | 57 | struct inode *pg_inode; |
58 | int (*pg_doio)(struct inode *, struct list_head *, size_t, int); | 58 | int (*pg_doio)(struct inode *, struct list_head *, unsigned int, size_t, int); |
59 | int pg_ioflags; | 59 | int pg_ioflags; |
60 | int pg_error; | 60 | int pg_error; |
61 | }; | 61 | }; |
@@ -75,7 +75,7 @@ extern int nfs_scan_list(struct nfs_inode *nfsi, struct list_head *head, struct | |||
75 | unsigned long idx_start, unsigned int npages); | 75 | unsigned long idx_start, unsigned int npages); |
76 | extern void nfs_pageio_init(struct nfs_pageio_descriptor *desc, | 76 | extern void nfs_pageio_init(struct nfs_pageio_descriptor *desc, |
77 | struct inode *inode, | 77 | struct inode *inode, |
78 | int (*doio)(struct inode *, struct list_head *, size_t, int), | 78 | int (*doio)(struct inode *, struct list_head *, unsigned int, size_t, int), |
79 | size_t bsize, | 79 | size_t bsize, |
80 | int how); | 80 | int how); |
81 | extern int nfs_pageio_add_request(struct nfs_pageio_descriptor *, | 81 | extern int nfs_pageio_add_request(struct nfs_pageio_descriptor *, |