diff options
author | Krzysztof Błaszkowski <kb@sysmikro.com.pl> | 2016-06-12 13:26:45 -0400 |
---|---|---|
committer | Christoph Hellwig <hch@lst.de> | 2016-06-12 13:35:17 -0400 |
commit | 12495ea3ac47ec5b53611e0d68ba3222a09784d1 (patch) | |
tree | b767e6e4c507d2b3c94bab0e7f97382ae2bf61b9 | |
parent | f2fe2fa1fbad72e469f49da3716f176a9b53fb75 (diff) |
freevxfs: refactor readdir and lookup code
This change fixes also a buffer overflow which was caused by
accessing address space beyond mapped page
Signed-off-by: Krzysztof Błaszkowski <kb@sysmikro.com.pl>
Signed-off-by: Christoph Hellwig <hch@lst.de>
-rw-r--r-- | fs/freevxfs/vxfs_lookup.c | 233 |
1 files changed, 108 insertions, 125 deletions
diff --git a/fs/freevxfs/vxfs_lookup.c b/fs/freevxfs/vxfs_lookup.c index f042cafbb85f..ce4785fd81c6 100644 --- a/fs/freevxfs/vxfs_lookup.c +++ b/fs/freevxfs/vxfs_lookup.c | |||
@@ -62,35 +62,6 @@ const struct file_operations vxfs_dir_operations = { | |||
62 | .iterate_shared = vxfs_readdir, | 62 | .iterate_shared = vxfs_readdir, |
63 | }; | 63 | }; |
64 | 64 | ||
65 | static inline u_long | ||
66 | dir_blocks(struct inode *ip) | ||
67 | { | ||
68 | u_long bsize = ip->i_sb->s_blocksize; | ||
69 | return (ip->i_size + bsize - 1) & ~(bsize - 1); | ||
70 | } | ||
71 | |||
72 | /* | ||
73 | * NOTE! unlike strncmp, vxfs_match returns 1 for success, 0 for failure. | ||
74 | * | ||
75 | * len <= VXFS_NAMELEN and de != NULL are guaranteed by caller. | ||
76 | */ | ||
77 | static inline int | ||
78 | vxfs_match(struct vxfs_sb_info *sbi, int len, const char *const name, | ||
79 | struct vxfs_direct *de) | ||
80 | { | ||
81 | if (len != fs16_to_cpu(sbi, de->d_namelen)) | ||
82 | return 0; | ||
83 | if (!de->d_ino) | ||
84 | return 0; | ||
85 | return !memcmp(name, de->d_name, len); | ||
86 | } | ||
87 | |||
88 | static inline struct vxfs_direct * | ||
89 | vxfs_next_entry(struct vxfs_sb_info *sbi, struct vxfs_direct *de) | ||
90 | { | ||
91 | return ((struct vxfs_direct *) | ||
92 | ((char *)de + fs16_to_cpu(sbi, de->d_reclen))); | ||
93 | } | ||
94 | 65 | ||
95 | /** | 66 | /** |
96 | * vxfs_find_entry - find a mathing directory entry for a dentry | 67 | * vxfs_find_entry - find a mathing directory entry for a dentry |
@@ -109,53 +80,64 @@ vxfs_next_entry(struct vxfs_sb_info *sbi, struct vxfs_direct *de) | |||
109 | static struct vxfs_direct * | 80 | static struct vxfs_direct * |
110 | vxfs_find_entry(struct inode *ip, struct dentry *dp, struct page **ppp) | 81 | vxfs_find_entry(struct inode *ip, struct dentry *dp, struct page **ppp) |
111 | { | 82 | { |
112 | struct vxfs_sb_info *sbi = VXFS_SBI(ip->i_sb); | 83 | u_long bsize = ip->i_sb->s_blocksize; |
113 | u_long npages, page, nblocks, pblocks, block; | 84 | const char *name = dp->d_name.name; |
114 | u_long bsize = ip->i_sb->s_blocksize; | 85 | int namelen = dp->d_name.len; |
115 | const char *name = dp->d_name.name; | 86 | loff_t limit = VXFS_DIRROUND(ip->i_size); |
116 | int namelen = dp->d_name.len; | 87 | struct vxfs_direct *de_exit = NULL; |
117 | 88 | loff_t pos = 0; | |
118 | npages = dir_pages(ip); | 89 | struct vxfs_sb_info *sbi = VXFS_SBI(ip->i_sb); |
119 | nblocks = dir_blocks(ip); | 90 | |
120 | pblocks = VXFS_BLOCK_PER_PAGE(ip->i_sb); | 91 | while (pos < limit) { |
121 | 92 | struct page *pp; | |
122 | for (page = 0; page < npages; page++) { | 93 | char *kaddr; |
123 | caddr_t kaddr; | 94 | int pg_ofs = pos & ~PAGE_MASK; |
124 | struct page *pp; | ||
125 | 95 | ||
126 | pp = vxfs_get_page(ip->i_mapping, page); | 96 | pp = vxfs_get_page(ip->i_mapping, pos >> PAGE_SHIFT); |
127 | if (IS_ERR(pp)) | 97 | if (IS_ERR(pp)) |
128 | continue; | 98 | return NULL; |
129 | kaddr = (caddr_t)page_address(pp); | 99 | kaddr = (char *)page_address(pp); |
130 | 100 | ||
131 | for (block = 0; block <= nblocks && block <= pblocks; block++) { | 101 | while (pg_ofs < PAGE_SIZE && pos < limit) { |
132 | caddr_t baddr, limit; | 102 | struct vxfs_direct *de; |
133 | struct vxfs_dirblk *dbp; | 103 | |
134 | struct vxfs_direct *de; | 104 | if ((pos & (bsize - 1)) < 4) { |
135 | 105 | struct vxfs_dirblk *dbp = | |
136 | baddr = kaddr + (block * bsize); | 106 | (struct vxfs_dirblk *) |
137 | limit = baddr + bsize - VXFS_DIRLEN(1); | 107 | (kaddr + (pos & ~PAGE_MASK)); |
138 | 108 | int overhead = VXFS_DIRBLKOV(sbi, dbp); | |
139 | dbp = (struct vxfs_dirblk *)baddr; | 109 | |
140 | de = (struct vxfs_direct *) | 110 | pos += overhead; |
141 | (baddr + VXFS_DIRBLKOV(sbi, dbp)); | 111 | pg_ofs += overhead; |
142 | 112 | } | |
143 | for (; (caddr_t)de <= limit; | 113 | de = (struct vxfs_direct *)(kaddr + pg_ofs); |
144 | de = vxfs_next_entry(sbi, de)) { | 114 | |
145 | if (!de->d_reclen) | 115 | if (!de->d_reclen) { |
146 | break; | 116 | pos += bsize - 1; |
147 | if (!de->d_ino) | 117 | pos &= ~(bsize - 1); |
148 | continue; | 118 | break; |
149 | if (vxfs_match(sbi, namelen, name, de)) { | 119 | } |
150 | *ppp = pp; | 120 | |
151 | return (de); | 121 | pg_ofs += fs16_to_cpu(sbi, de->d_reclen); |
152 | } | 122 | pos += fs16_to_cpu(sbi, de->d_reclen); |
123 | if (!de->d_ino) | ||
124 | continue; | ||
125 | |||
126 | if (namelen != fs16_to_cpu(sbi, de->d_namelen)) | ||
127 | continue; | ||
128 | if (!memcmp(name, de->d_name, namelen)) { | ||
129 | *ppp = pp; | ||
130 | de_exit = de; | ||
131 | break; | ||
153 | } | 132 | } |
154 | } | 133 | } |
155 | vxfs_put_page(pp); | 134 | if (!de_exit) |
135 | vxfs_put_page(pp); | ||
136 | else | ||
137 | break; | ||
156 | } | 138 | } |
157 | 139 | ||
158 | return NULL; | 140 | return de_exit; |
159 | } | 141 | } |
160 | 142 | ||
161 | /** | 143 | /** |
@@ -238,80 +220,81 @@ vxfs_readdir(struct file *fp, struct dir_context *ctx) | |||
238 | { | 220 | { |
239 | struct inode *ip = file_inode(fp); | 221 | struct inode *ip = file_inode(fp); |
240 | struct super_block *sbp = ip->i_sb; | 222 | struct super_block *sbp = ip->i_sb; |
241 | struct vxfs_sb_info *sbi = VXFS_SBI(sbp); | ||
242 | u_long bsize = sbp->s_blocksize; | 223 | u_long bsize = sbp->s_blocksize; |
243 | u_long page, npages, block, pblocks, nblocks, offset; | 224 | loff_t pos, limit; |
244 | loff_t pos; | 225 | struct vxfs_sb_info *sbi = VXFS_SBI(sbp); |
245 | |||
246 | 226 | ||
247 | if (ctx->pos == 0) { | 227 | if (ctx->pos == 0) { |
248 | if (!dir_emit_dot(fp, ctx)) | 228 | if (!dir_emit_dot(fp, ctx)) |
249 | return 0; | 229 | goto out; |
250 | ctx->pos = 1; | 230 | ctx->pos++; |
251 | } | 231 | } |
252 | if (ctx->pos == 1) { | 232 | if (ctx->pos == 1) { |
253 | if (!dir_emit(ctx, "..", 2, VXFS_INO(ip)->vii_dotdot, DT_DIR)) | 233 | if (!dir_emit(ctx, "..", 2, VXFS_INO(ip)->vii_dotdot, DT_DIR)) |
254 | return 0; | 234 | goto out; |
255 | ctx->pos = 2; | 235 | ctx->pos++; |
256 | } | 236 | } |
257 | pos = ctx->pos - 2; | ||
258 | |||
259 | if (pos > VXFS_DIRROUND(ip->i_size)) | ||
260 | return 0; | ||
261 | 237 | ||
262 | npages = dir_pages(ip); | 238 | limit = VXFS_DIRROUND(ip->i_size); |
263 | nblocks = dir_blocks(ip); | 239 | if (ctx->pos > limit) |
264 | pblocks = VXFS_BLOCK_PER_PAGE(sbp); | 240 | goto out; |
265 | 241 | ||
266 | page = pos >> PAGE_SHIFT; | 242 | pos = ctx->pos & ~3L; |
267 | offset = pos & ~PAGE_MASK; | ||
268 | block = (u_long)(pos >> sbp->s_blocksize_bits) % pblocks; | ||
269 | 243 | ||
270 | for (; page < npages; page++, block = 0) { | 244 | while (pos < limit) { |
271 | char *kaddr; | 245 | struct page *pp; |
272 | struct page *pp; | 246 | char *kaddr; |
247 | int pg_ofs = pos & ~PAGE_MASK; | ||
248 | int rc = 0; | ||
273 | 249 | ||
274 | pp = vxfs_get_page(ip->i_mapping, page); | 250 | pp = vxfs_get_page(ip->i_mapping, pos >> PAGE_SHIFT); |
275 | if (IS_ERR(pp)) | 251 | if (IS_ERR(pp)) |
276 | continue; | 252 | return -ENOMEM; |
253 | |||
277 | kaddr = (char *)page_address(pp); | 254 | kaddr = (char *)page_address(pp); |
278 | 255 | ||
279 | for (; block <= nblocks && block <= pblocks; block++) { | 256 | while (pg_ofs < PAGE_SIZE && pos < limit) { |
280 | char *baddr, *limit; | 257 | struct vxfs_direct *de; |
281 | struct vxfs_dirblk *dbp; | ||
282 | struct vxfs_direct *de; | ||
283 | 258 | ||
284 | baddr = kaddr + (block * bsize); | 259 | if ((pos & (bsize - 1)) < 4) { |
285 | limit = baddr + bsize - VXFS_DIRLEN(1); | 260 | struct vxfs_dirblk *dbp = |
286 | 261 | (struct vxfs_dirblk *) | |
287 | dbp = (struct vxfs_dirblk *)baddr; | 262 | (kaddr + (pos & ~PAGE_MASK)); |
288 | de = (struct vxfs_direct *) | 263 | int overhead = VXFS_DIRBLKOV(sbi, dbp); |
289 | (offset ? | 264 | |
290 | (kaddr + offset) : | 265 | pos += overhead; |
291 | (baddr + VXFS_DIRBLKOV(sbi, dbp))); | 266 | pg_ofs += overhead; |
292 | 267 | } | |
293 | for (; (char *)de <= limit; | 268 | de = (struct vxfs_direct *)(kaddr + pg_ofs); |
294 | de = vxfs_next_entry(sbi, de)) { | 269 | |
295 | if (!de->d_reclen) | 270 | if (!de->d_reclen) { |
296 | break; | 271 | pos += bsize - 1; |
297 | if (!de->d_ino) | 272 | pos &= ~(bsize - 1); |
298 | continue; | 273 | break; |
299 | 274 | } | |
300 | offset = (char *)de - kaddr; | 275 | |
301 | ctx->pos = ((page << PAGE_SHIFT) | offset) + 2; | 276 | pg_ofs += fs16_to_cpu(sbi, de->d_reclen); |
302 | if (!dir_emit(ctx, de->d_name, | 277 | pos += fs16_to_cpu(sbi, de->d_reclen); |
303 | fs16_to_cpu(sbi, de->d_namelen), | 278 | if (!de->d_ino) |
304 | fs32_to_cpu(sbi, de->d_ino), | 279 | continue; |
305 | DT_UNKNOWN)) { | 280 | |
306 | vxfs_put_page(pp); | 281 | rc = dir_emit(ctx, de->d_name, |
307 | return 0; | 282 | fs16_to_cpu(sbi, de->d_namelen), |
308 | } | 283 | fs32_to_cpu(sbi, de->d_ino), |
284 | DT_UNKNOWN); | ||
285 | if (!rc) { | ||
286 | /* the dir entry was not read, fix pos. */ | ||
287 | pos -= fs16_to_cpu(sbi, de->d_reclen); | ||
288 | break; | ||
309 | } | 289 | } |
310 | offset = 0; | ||
311 | } | 290 | } |
312 | vxfs_put_page(pp); | 291 | vxfs_put_page(pp); |
313 | offset = 0; | 292 | if (!rc) |
293 | break; | ||
314 | } | 294 | } |
315 | ctx->pos = ((page << PAGE_SHIFT) | offset) + 2; | 295 | |
296 | ctx->pos = pos | 2; | ||
297 | |||
298 | out: | ||
316 | return 0; | 299 | return 0; |
317 | } | 300 | } |