diff options
author | Darrick J. Wong <djwong@us.ibm.com> | 2012-04-29 18:39:10 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2012-04-29 18:39:10 -0400 |
commit | dbe89444042ab6540bc304343cfdcbc8b95d003d (patch) | |
tree | 436c6e039ffef3387fcfac0bb699f0b14b4c9ba6 /fs/ext4 | |
parent | 7ac5990d5a3e2e465162880819cc46c6427d3b6f (diff) |
ext4: Calculate and verify checksums for htree nodes
Calculate and verify the checksum for directory index tree (htree)
node blocks. The checksum is stored in the last 4 bytes of the htree
block and requires the dx_entry array to stop 1 dx_entry short of the
end of the block.
Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs/ext4')
-rw-r--r-- | fs/ext4/namei.c | 160 |
1 files changed, 156 insertions, 4 deletions
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 625125172d05..f8efedde7593 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c | |||
@@ -188,6 +188,121 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, | |||
188 | static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry, | 188 | static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry, |
189 | struct inode *inode); | 189 | struct inode *inode); |
190 | 190 | ||
191 | /* checksumming functions */ | ||
192 | static struct dx_countlimit *get_dx_countlimit(struct inode *inode, | ||
193 | struct ext4_dir_entry *dirent, | ||
194 | int *offset) | ||
195 | { | ||
196 | struct ext4_dir_entry *dp; | ||
197 | struct dx_root_info *root; | ||
198 | int count_offset; | ||
199 | |||
200 | if (le16_to_cpu(dirent->rec_len) == EXT4_BLOCK_SIZE(inode->i_sb)) | ||
201 | count_offset = 8; | ||
202 | else if (le16_to_cpu(dirent->rec_len) == 12) { | ||
203 | dp = (struct ext4_dir_entry *)(((void *)dirent) + 12); | ||
204 | if (le16_to_cpu(dp->rec_len) != | ||
205 | EXT4_BLOCK_SIZE(inode->i_sb) - 12) | ||
206 | return NULL; | ||
207 | root = (struct dx_root_info *)(((void *)dp + 12)); | ||
208 | if (root->reserved_zero || | ||
209 | root->info_length != sizeof(struct dx_root_info)) | ||
210 | return NULL; | ||
211 | count_offset = 32; | ||
212 | } else | ||
213 | return NULL; | ||
214 | |||
215 | if (offset) | ||
216 | *offset = count_offset; | ||
217 | return (struct dx_countlimit *)(((void *)dirent) + count_offset); | ||
218 | } | ||
219 | |||
220 | static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent, | ||
221 | int count_offset, int count, struct dx_tail *t) | ||
222 | { | ||
223 | struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); | ||
224 | struct ext4_inode_info *ei = EXT4_I(inode); | ||
225 | __u32 csum, old_csum; | ||
226 | int size; | ||
227 | |||
228 | size = count_offset + (count * sizeof(struct dx_entry)); | ||
229 | old_csum = t->dt_checksum; | ||
230 | t->dt_checksum = 0; | ||
231 | csum = ext4_chksum(sbi, ei->i_csum_seed, (__u8 *)dirent, size); | ||
232 | csum = ext4_chksum(sbi, csum, (__u8 *)t, sizeof(struct dx_tail)); | ||
233 | t->dt_checksum = old_csum; | ||
234 | |||
235 | return cpu_to_le32(csum); | ||
236 | } | ||
237 | |||
238 | static int ext4_dx_csum_verify(struct inode *inode, | ||
239 | struct ext4_dir_entry *dirent) | ||
240 | { | ||
241 | struct dx_countlimit *c; | ||
242 | struct dx_tail *t; | ||
243 | int count_offset, limit, count; | ||
244 | |||
245 | if (!EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb, | ||
246 | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) | ||
247 | return 1; | ||
248 | |||
249 | c = get_dx_countlimit(inode, dirent, &count_offset); | ||
250 | if (!c) { | ||
251 | EXT4_ERROR_INODE(inode, "dir seems corrupt? Run e2fsck -D."); | ||
252 | return 1; | ||
253 | } | ||
254 | limit = le16_to_cpu(c->limit); | ||
255 | count = le16_to_cpu(c->count); | ||
256 | if (count_offset + (limit * sizeof(struct dx_entry)) > | ||
257 | EXT4_BLOCK_SIZE(inode->i_sb) - sizeof(struct dx_tail)) { | ||
258 | EXT4_ERROR_INODE(inode, "metadata_csum set but no space for " | ||
259 | "tree checksum found. Run e2fsck -D."); | ||
260 | return 1; | ||
261 | } | ||
262 | t = (struct dx_tail *)(((struct dx_entry *)c) + limit); | ||
263 | |||
264 | if (t->dt_checksum != ext4_dx_csum(inode, dirent, count_offset, | ||
265 | count, t)) | ||
266 | return 0; | ||
267 | return 1; | ||
268 | } | ||
269 | |||
270 | static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent) | ||
271 | { | ||
272 | struct dx_countlimit *c; | ||
273 | struct dx_tail *t; | ||
274 | int count_offset, limit, count; | ||
275 | |||
276 | if (!EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb, | ||
277 | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) | ||
278 | return; | ||
279 | |||
280 | c = get_dx_countlimit(inode, dirent, &count_offset); | ||
281 | if (!c) { | ||
282 | EXT4_ERROR_INODE(inode, "dir seems corrupt? Run e2fsck -D."); | ||
283 | return; | ||
284 | } | ||
285 | limit = le16_to_cpu(c->limit); | ||
286 | count = le16_to_cpu(c->count); | ||
287 | if (count_offset + (limit * sizeof(struct dx_entry)) > | ||
288 | EXT4_BLOCK_SIZE(inode->i_sb) - sizeof(struct dx_tail)) { | ||
289 | EXT4_ERROR_INODE(inode, "metadata_csum set but no space for " | ||
290 | "tree checksum. Run e2fsck -D."); | ||
291 | return; | ||
292 | } | ||
293 | t = (struct dx_tail *)(((struct dx_entry *)c) + limit); | ||
294 | |||
295 | t->dt_checksum = ext4_dx_csum(inode, dirent, count_offset, count, t); | ||
296 | } | ||
297 | |||
298 | static inline int ext4_handle_dirty_dx_node(handle_t *handle, | ||
299 | struct inode *inode, | ||
300 | struct buffer_head *bh) | ||
301 | { | ||
302 | ext4_dx_csum_set(inode, (struct ext4_dir_entry *)bh->b_data); | ||
303 | return ext4_handle_dirty_metadata(handle, inode, bh); | ||
304 | } | ||
305 | |||
191 | /* | 306 | /* |
192 | * p is at least 6 bytes before the end of page | 307 | * p is at least 6 bytes before the end of page |
193 | */ | 308 | */ |
@@ -247,12 +362,20 @@ static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize) | |||
247 | { | 362 | { |
248 | unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(1) - | 363 | unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(1) - |
249 | EXT4_DIR_REC_LEN(2) - infosize; | 364 | EXT4_DIR_REC_LEN(2) - infosize; |
365 | |||
366 | if (EXT4_HAS_RO_COMPAT_FEATURE(dir->i_sb, | ||
367 | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) | ||
368 | entry_space -= sizeof(struct dx_tail); | ||
250 | return entry_space / sizeof(struct dx_entry); | 369 | return entry_space / sizeof(struct dx_entry); |
251 | } | 370 | } |
252 | 371 | ||
253 | static inline unsigned dx_node_limit(struct inode *dir) | 372 | static inline unsigned dx_node_limit(struct inode *dir) |
254 | { | 373 | { |
255 | unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(0); | 374 | unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(0); |
375 | |||
376 | if (EXT4_HAS_RO_COMPAT_FEATURE(dir->i_sb, | ||
377 | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) | ||
378 | entry_space -= sizeof(struct dx_tail); | ||
256 | return entry_space / sizeof(struct dx_entry); | 379 | return entry_space / sizeof(struct dx_entry); |
257 | } | 380 | } |
258 | 381 | ||
@@ -398,6 +521,15 @@ dx_probe(const struct qstr *d_name, struct inode *dir, | |||
398 | goto fail; | 521 | goto fail; |
399 | } | 522 | } |
400 | 523 | ||
524 | if (!buffer_verified(bh) && | ||
525 | !ext4_dx_csum_verify(dir, (struct ext4_dir_entry *)bh->b_data)) { | ||
526 | ext4_warning(dir->i_sb, "Root failed checksum"); | ||
527 | brelse(bh); | ||
528 | *err = ERR_BAD_DX_DIR; | ||
529 | goto fail; | ||
530 | } | ||
531 | set_buffer_verified(bh); | ||
532 | |||
401 | entries = (struct dx_entry *) (((char *)&root->info) + | 533 | entries = (struct dx_entry *) (((char *)&root->info) + |
402 | root->info.info_length); | 534 | root->info.info_length); |
403 | 535 | ||
@@ -458,6 +590,17 @@ dx_probe(const struct qstr *d_name, struct inode *dir, | |||
458 | if (!(bh = ext4_bread (NULL,dir, dx_get_block(at), 0, err))) | 590 | if (!(bh = ext4_bread (NULL,dir, dx_get_block(at), 0, err))) |
459 | goto fail2; | 591 | goto fail2; |
460 | at = entries = ((struct dx_node *) bh->b_data)->entries; | 592 | at = entries = ((struct dx_node *) bh->b_data)->entries; |
593 | |||
594 | if (!buffer_verified(bh) && | ||
595 | !ext4_dx_csum_verify(dir, | ||
596 | (struct ext4_dir_entry *)bh->b_data)) { | ||
597 | ext4_warning(dir->i_sb, "Node failed checksum"); | ||
598 | brelse(bh); | ||
599 | *err = ERR_BAD_DX_DIR; | ||
600 | goto fail; | ||
601 | } | ||
602 | set_buffer_verified(bh); | ||
603 | |||
461 | if (dx_get_limit(entries) != dx_node_limit (dir)) { | 604 | if (dx_get_limit(entries) != dx_node_limit (dir)) { |
462 | ext4_warning(dir->i_sb, | 605 | ext4_warning(dir->i_sb, |
463 | "dx entry: limit != node limit"); | 606 | "dx entry: limit != node limit"); |
@@ -557,6 +700,15 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash, | |||
557 | if (!(bh = ext4_bread(NULL, dir, dx_get_block(p->at), | 700 | if (!(bh = ext4_bread(NULL, dir, dx_get_block(p->at), |
558 | 0, &err))) | 701 | 0, &err))) |
559 | return err; /* Failure */ | 702 | return err; /* Failure */ |
703 | |||
704 | if (!buffer_verified(bh) && | ||
705 | !ext4_dx_csum_verify(dir, | ||
706 | (struct ext4_dir_entry *)bh->b_data)) { | ||
707 | ext4_warning(dir->i_sb, "Node failed checksum"); | ||
708 | return -EIO; | ||
709 | } | ||
710 | set_buffer_verified(bh); | ||
711 | |||
560 | p++; | 712 | p++; |
561 | brelse(p->bh); | 713 | brelse(p->bh); |
562 | p->bh = bh; | 714 | p->bh = bh; |
@@ -1232,7 +1384,7 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir, | |||
1232 | err = ext4_handle_dirty_metadata(handle, dir, bh2); | 1384 | err = ext4_handle_dirty_metadata(handle, dir, bh2); |
1233 | if (err) | 1385 | if (err) |
1234 | goto journal_error; | 1386 | goto journal_error; |
1235 | err = ext4_handle_dirty_metadata(handle, dir, frame->bh); | 1387 | err = ext4_handle_dirty_dx_node(handle, dir, frame->bh); |
1236 | if (err) | 1388 | if (err) |
1237 | goto journal_error; | 1389 | goto journal_error; |
1238 | brelse(bh2); | 1390 | brelse(bh2); |
@@ -1419,7 +1571,7 @@ static int make_indexed_dir(handle_t *handle, struct dentry *dentry, | |||
1419 | frame->bh = bh; | 1571 | frame->bh = bh; |
1420 | bh = bh2; | 1572 | bh = bh2; |
1421 | 1573 | ||
1422 | ext4_handle_dirty_metadata(handle, dir, frame->bh); | 1574 | ext4_handle_dirty_dx_node(handle, dir, frame->bh); |
1423 | ext4_handle_dirty_metadata(handle, dir, bh); | 1575 | ext4_handle_dirty_metadata(handle, dir, bh); |
1424 | 1576 | ||
1425 | de = do_split(handle,dir, &bh, frame, &hinfo, &retval); | 1577 | de = do_split(handle,dir, &bh, frame, &hinfo, &retval); |
@@ -1594,7 +1746,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry, | |||
1594 | dxtrace(dx_show_index("node", frames[1].entries)); | 1746 | dxtrace(dx_show_index("node", frames[1].entries)); |
1595 | dxtrace(dx_show_index("node", | 1747 | dxtrace(dx_show_index("node", |
1596 | ((struct dx_node *) bh2->b_data)->entries)); | 1748 | ((struct dx_node *) bh2->b_data)->entries)); |
1597 | err = ext4_handle_dirty_metadata(handle, dir, bh2); | 1749 | err = ext4_handle_dirty_dx_node(handle, dir, bh2); |
1598 | if (err) | 1750 | if (err) |
1599 | goto journal_error; | 1751 | goto journal_error; |
1600 | brelse (bh2); | 1752 | brelse (bh2); |
@@ -1620,7 +1772,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry, | |||
1620 | if (err) | 1772 | if (err) |
1621 | goto journal_error; | 1773 | goto journal_error; |
1622 | } | 1774 | } |
1623 | err = ext4_handle_dirty_metadata(handle, dir, frames[0].bh); | 1775 | err = ext4_handle_dirty_dx_node(handle, dir, frames[0].bh); |
1624 | if (err) { | 1776 | if (err) { |
1625 | ext4_std_error(inode->i_sb, err); | 1777 | ext4_std_error(inode->i_sb, err); |
1626 | goto cleanup; | 1778 | goto cleanup; |