diff options
author | Stuart Swales <stuart.swales.croftnuisk@gmail.com> | 2011-03-22 19:35:04 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-03-22 20:44:17 -0400 |
commit | 2f09719af705db56032ae480a2d9c32c2a3fcbd3 (patch) | |
tree | 20fc7dc5c6955a943e01e78cfcdf77d1ab3e6027 /fs/adfs | |
parent | 12da58b0c89e27617aaedde7dcf99a8690875e91 (diff) |
adfs: fix E+/F+ dir size > 2048 crashing kernel
Kernel crashes in fs/adfs module when accessing directories with a large
number of objects on mounted Acorn ADFS E+/F+ format discs (or images) as
the existing code writes off the end of the fixed array of struct
buffer_head pointers.
Additionally, each directory access that didn't crash would leak a buffer
as nr_buffers was not adjusted correctly for E+/F+ discs (was always left
as one less than required).
The patch fixes this by allocating a dynamically-sized set of struct
buffer_head pointers if necessary for the E+/F+ case (many directories
still do in fact fit in 2048 bytes) and sets the correct nr_buffers so
that all buffers are released.
Addresses https://bugzilla.kernel.org/show_bug.cgi?id=26072
Tested by tar'ing the contents of my RISC PC's E+ format 20Gb HDD which
contains a number of large directories that previously crashed the kernel.
Signed-off-by: Stuart Swales <stuart.swales.croftnuisk@gmail.com>
Cc: Russell King <rmk@arm.linux.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/adfs')
-rw-r--r-- | fs/adfs/adfs.h | 4 | ||||
-rw-r--r-- | fs/adfs/dir_fplus.c | 101 |
2 files changed, 84 insertions, 21 deletions
diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 2ff622f6f547..58588ddb178c 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h | |||
@@ -79,6 +79,10 @@ struct adfs_dir { | |||
79 | 79 | ||
80 | int nr_buffers; | 80 | int nr_buffers; |
81 | struct buffer_head *bh[4]; | 81 | struct buffer_head *bh[4]; |
82 | |||
83 | /* big directories need allocated buffers */ | ||
84 | struct buffer_head **bh_fplus; | ||
85 | |||
82 | unsigned int pos; | 86 | unsigned int pos; |
83 | unsigned int parent_id; | 87 | unsigned int parent_id; |
84 | 88 | ||
diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c index 1796bb352d05..a7f41da8115b 100644 --- a/fs/adfs/dir_fplus.c +++ b/fs/adfs/dir_fplus.c | |||
@@ -8,6 +8,7 @@ | |||
8 | * published by the Free Software Foundation. | 8 | * published by the Free Software Foundation. |
9 | */ | 9 | */ |
10 | #include <linux/buffer_head.h> | 10 | #include <linux/buffer_head.h> |
11 | #include <linux/slab.h> | ||
11 | #include "adfs.h" | 12 | #include "adfs.h" |
12 | #include "dir_fplus.h" | 13 | #include "dir_fplus.h" |
13 | 14 | ||
@@ -22,30 +23,53 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct | |||
22 | 23 | ||
23 | dir->nr_buffers = 0; | 24 | dir->nr_buffers = 0; |
24 | 25 | ||
26 | /* start off using fixed bh set - only alloc for big dirs */ | ||
27 | dir->bh_fplus = &dir->bh[0]; | ||
28 | |||
25 | block = __adfs_block_map(sb, id, 0); | 29 | block = __adfs_block_map(sb, id, 0); |
26 | if (!block) { | 30 | if (!block) { |
27 | adfs_error(sb, "dir object %X has a hole at offset 0", id); | 31 | adfs_error(sb, "dir object %X has a hole at offset 0", id); |
28 | goto out; | 32 | goto out; |
29 | } | 33 | } |
30 | 34 | ||
31 | dir->bh[0] = sb_bread(sb, block); | 35 | dir->bh_fplus[0] = sb_bread(sb, block); |
32 | if (!dir->bh[0]) | 36 | if (!dir->bh_fplus[0]) |
33 | goto out; | 37 | goto out; |
34 | dir->nr_buffers += 1; | 38 | dir->nr_buffers += 1; |
35 | 39 | ||
36 | h = (struct adfs_bigdirheader *)dir->bh[0]->b_data; | 40 | h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data; |
37 | size = le32_to_cpu(h->bigdirsize); | 41 | size = le32_to_cpu(h->bigdirsize); |
38 | if (size != sz) { | 42 | if (size != sz) { |
39 | printk(KERN_WARNING "adfs: adfs_fplus_read: directory header size\n" | 43 | printk(KERN_WARNING "adfs: adfs_fplus_read:" |
40 | " does not match directory size\n"); | 44 | " directory header size %X\n" |
45 | " does not match directory size %X\n", | ||
46 | size, sz); | ||
41 | } | 47 | } |
42 | 48 | ||
43 | if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || | 49 | if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || |
44 | h->bigdirversion[2] != 0 || size & 2047 || | 50 | h->bigdirversion[2] != 0 || size & 2047 || |
45 | h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) | 51 | h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) { |
52 | printk(KERN_WARNING "adfs: dir object %X has" | ||
53 | " malformed dir header\n", id); | ||
46 | goto out; | 54 | goto out; |
55 | } | ||
47 | 56 | ||
48 | size >>= sb->s_blocksize_bits; | 57 | size >>= sb->s_blocksize_bits; |
58 | if (size > sizeof(dir->bh)/sizeof(dir->bh[0])) { | ||
59 | /* this directory is too big for fixed bh set, must allocate */ | ||
60 | struct buffer_head **bh_fplus = | ||
61 | kzalloc(size * sizeof(struct buffer_head *), | ||
62 | GFP_KERNEL); | ||
63 | if (!bh_fplus) { | ||
64 | adfs_error(sb, "not enough memory for" | ||
65 | " dir object %X (%d blocks)", id, size); | ||
66 | goto out; | ||
67 | } | ||
68 | dir->bh_fplus = bh_fplus; | ||
69 | /* copy over the pointer to the block that we've already read */ | ||
70 | dir->bh_fplus[0] = dir->bh[0]; | ||
71 | } | ||
72 | |||
49 | for (blk = 1; blk < size; blk++) { | 73 | for (blk = 1; blk < size; blk++) { |
50 | block = __adfs_block_map(sb, id, blk); | 74 | block = __adfs_block_map(sb, id, blk); |
51 | if (!block) { | 75 | if (!block) { |
@@ -53,25 +77,44 @@ adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct | |||
53 | goto out; | 77 | goto out; |
54 | } | 78 | } |
55 | 79 | ||
56 | dir->bh[blk] = sb_bread(sb, block); | 80 | dir->bh_fplus[blk] = sb_bread(sb, block); |
57 | if (!dir->bh[blk]) | 81 | if (!dir->bh_fplus[blk]) { |
82 | adfs_error(sb, "dir object %X failed read for" | ||
83 | " offset %d, mapped block %X", | ||
84 | id, blk, block); | ||
58 | goto out; | 85 | goto out; |
59 | dir->nr_buffers = blk; | 86 | } |
87 | |||
88 | dir->nr_buffers += 1; | ||
60 | } | 89 | } |
61 | 90 | ||
62 | t = (struct adfs_bigdirtail *)(dir->bh[size - 1]->b_data + (sb->s_blocksize - 8)); | 91 | t = (struct adfs_bigdirtail *) |
92 | (dir->bh_fplus[size - 1]->b_data + (sb->s_blocksize - 8)); | ||
63 | 93 | ||
64 | if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || | 94 | if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || |
65 | t->bigdirendmasseq != h->startmasseq || | 95 | t->bigdirendmasseq != h->startmasseq || |
66 | t->reserved[0] != 0 || t->reserved[1] != 0) | 96 | t->reserved[0] != 0 || t->reserved[1] != 0) { |
97 | printk(KERN_WARNING "adfs: dir object %X has " | ||
98 | "malformed dir end\n", id); | ||
67 | goto out; | 99 | goto out; |
100 | } | ||
68 | 101 | ||
69 | dir->parent_id = le32_to_cpu(h->bigdirparent); | 102 | dir->parent_id = le32_to_cpu(h->bigdirparent); |
70 | dir->sb = sb; | 103 | dir->sb = sb; |
71 | return 0; | 104 | return 0; |
105 | |||
72 | out: | 106 | out: |
73 | for (i = 0; i < dir->nr_buffers; i++) | 107 | if (dir->bh_fplus) { |
74 | brelse(dir->bh[i]); | 108 | for (i = 0; i < dir->nr_buffers; i++) |
109 | brelse(dir->bh_fplus[i]); | ||
110 | |||
111 | if (&dir->bh[0] != dir->bh_fplus) | ||
112 | kfree(dir->bh_fplus); | ||
113 | |||
114 | dir->bh_fplus = NULL; | ||
115 | } | ||
116 | |||
117 | dir->nr_buffers = 0; | ||
75 | dir->sb = NULL; | 118 | dir->sb = NULL; |
76 | return ret; | 119 | return ret; |
77 | } | 120 | } |
@@ -79,7 +122,8 @@ out: | |||
79 | static int | 122 | static int |
80 | adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) | 123 | adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) |
81 | { | 124 | { |
82 | struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data; | 125 | struct adfs_bigdirheader *h = |
126 | (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; | ||
83 | int ret = -ENOENT; | 127 | int ret = -ENOENT; |
84 | 128 | ||
85 | if (fpos <= le32_to_cpu(h->bigdirentries)) { | 129 | if (fpos <= le32_to_cpu(h->bigdirentries)) { |
@@ -102,21 +146,27 @@ dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len) | |||
102 | partial = sb->s_blocksize - offset; | 146 | partial = sb->s_blocksize - offset; |
103 | 147 | ||
104 | if (partial >= len) | 148 | if (partial >= len) |
105 | memcpy(to, dir->bh[buffer]->b_data + offset, len); | 149 | memcpy(to, dir->bh_fplus[buffer]->b_data + offset, len); |
106 | else { | 150 | else { |
107 | char *c = (char *)to; | 151 | char *c = (char *)to; |
108 | 152 | ||
109 | remainder = len - partial; | 153 | remainder = len - partial; |
110 | 154 | ||
111 | memcpy(c, dir->bh[buffer]->b_data + offset, partial); | 155 | memcpy(c, |
112 | memcpy(c + partial, dir->bh[buffer + 1]->b_data, remainder); | 156 | dir->bh_fplus[buffer]->b_data + offset, |
157 | partial); | ||
158 | |||
159 | memcpy(c + partial, | ||
160 | dir->bh_fplus[buffer + 1]->b_data, | ||
161 | remainder); | ||
113 | } | 162 | } |
114 | } | 163 | } |
115 | 164 | ||
116 | static int | 165 | static int |
117 | adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) | 166 | adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) |
118 | { | 167 | { |
119 | struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data; | 168 | struct adfs_bigdirheader *h = |
169 | (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; | ||
120 | struct adfs_bigdirentry bde; | 170 | struct adfs_bigdirentry bde; |
121 | unsigned int offset; | 171 | unsigned int offset; |
122 | int i, ret = -ENOENT; | 172 | int i, ret = -ENOENT; |
@@ -160,7 +210,7 @@ adfs_fplus_sync(struct adfs_dir *dir) | |||
160 | int i; | 210 | int i; |
161 | 211 | ||
162 | for (i = dir->nr_buffers - 1; i >= 0; i--) { | 212 | for (i = dir->nr_buffers - 1; i >= 0; i--) { |
163 | struct buffer_head *bh = dir->bh[i]; | 213 | struct buffer_head *bh = dir->bh_fplus[i]; |
164 | sync_dirty_buffer(bh); | 214 | sync_dirty_buffer(bh); |
165 | if (buffer_req(bh) && !buffer_uptodate(bh)) | 215 | if (buffer_req(bh) && !buffer_uptodate(bh)) |
166 | err = -EIO; | 216 | err = -EIO; |
@@ -174,8 +224,17 @@ adfs_fplus_free(struct adfs_dir *dir) | |||
174 | { | 224 | { |
175 | int i; | 225 | int i; |
176 | 226 | ||
177 | for (i = 0; i < dir->nr_buffers; i++) | 227 | if (dir->bh_fplus) { |
178 | brelse(dir->bh[i]); | 228 | for (i = 0; i < dir->nr_buffers; i++) |
229 | brelse(dir->bh_fplus[i]); | ||
230 | |||
231 | if (&dir->bh[0] != dir->bh_fplus) | ||
232 | kfree(dir->bh_fplus); | ||
233 | |||
234 | dir->bh_fplus = NULL; | ||
235 | } | ||
236 | |||
237 | dir->nr_buffers = 0; | ||
179 | dir->sb = NULL; | 238 | dir->sb = NULL; |
180 | } | 239 | } |
181 | 240 | ||