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 | ||
