forked from mirrors/linux
		
	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>
This commit is contained in:
		
							parent
							
								
									7ac5990d5a
								
							
						
					
					
						commit
						dbe8944404
					
				
					 1 changed files with 156 additions and 4 deletions
				
			
		
							
								
								
									
										160
									
								
								fs/ext4/namei.c
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								fs/ext4/namei.c
									
									
									
									
									
								
							| 
						 | 
					@ -188,6 +188,121 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
 | 
				
			||||||
static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
 | 
					static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
 | 
				
			||||||
			     struct inode *inode);
 | 
								     struct inode *inode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* checksumming functions */
 | 
				
			||||||
 | 
					static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
 | 
				
			||||||
 | 
										       struct ext4_dir_entry *dirent,
 | 
				
			||||||
 | 
										       int *offset)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct ext4_dir_entry *dp;
 | 
				
			||||||
 | 
						struct dx_root_info *root;
 | 
				
			||||||
 | 
						int count_offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (le16_to_cpu(dirent->rec_len) == EXT4_BLOCK_SIZE(inode->i_sb))
 | 
				
			||||||
 | 
							count_offset = 8;
 | 
				
			||||||
 | 
						else if (le16_to_cpu(dirent->rec_len) == 12) {
 | 
				
			||||||
 | 
							dp = (struct ext4_dir_entry *)(((void *)dirent) + 12);
 | 
				
			||||||
 | 
							if (le16_to_cpu(dp->rec_len) !=
 | 
				
			||||||
 | 
							    EXT4_BLOCK_SIZE(inode->i_sb) - 12)
 | 
				
			||||||
 | 
								return NULL;
 | 
				
			||||||
 | 
							root = (struct dx_root_info *)(((void *)dp + 12));
 | 
				
			||||||
 | 
							if (root->reserved_zero ||
 | 
				
			||||||
 | 
							    root->info_length != sizeof(struct dx_root_info))
 | 
				
			||||||
 | 
								return NULL;
 | 
				
			||||||
 | 
							count_offset = 32;
 | 
				
			||||||
 | 
						} else
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (offset)
 | 
				
			||||||
 | 
							*offset = count_offset;
 | 
				
			||||||
 | 
						return (struct dx_countlimit *)(((void *)dirent) + count_offset);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent,
 | 
				
			||||||
 | 
								   int count_offset, int count, struct dx_tail *t)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
 | 
				
			||||||
 | 
						struct ext4_inode_info *ei = EXT4_I(inode);
 | 
				
			||||||
 | 
						__u32 csum, old_csum;
 | 
				
			||||||
 | 
						int size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size = count_offset + (count * sizeof(struct dx_entry));
 | 
				
			||||||
 | 
						old_csum = t->dt_checksum;
 | 
				
			||||||
 | 
						t->dt_checksum = 0;
 | 
				
			||||||
 | 
						csum = ext4_chksum(sbi, ei->i_csum_seed, (__u8 *)dirent, size);
 | 
				
			||||||
 | 
						csum = ext4_chksum(sbi, csum, (__u8 *)t, sizeof(struct dx_tail));
 | 
				
			||||||
 | 
						t->dt_checksum = old_csum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return cpu_to_le32(csum);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ext4_dx_csum_verify(struct inode *inode,
 | 
				
			||||||
 | 
								       struct ext4_dir_entry *dirent)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct dx_countlimit *c;
 | 
				
			||||||
 | 
						struct dx_tail *t;
 | 
				
			||||||
 | 
						int count_offset, limit, count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb,
 | 
				
			||||||
 | 
										EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
 | 
				
			||||||
 | 
							return 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c = get_dx_countlimit(inode, dirent, &count_offset);
 | 
				
			||||||
 | 
						if (!c) {
 | 
				
			||||||
 | 
							EXT4_ERROR_INODE(inode, "dir seems corrupt?  Run e2fsck -D.");
 | 
				
			||||||
 | 
							return 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						limit = le16_to_cpu(c->limit);
 | 
				
			||||||
 | 
						count = le16_to_cpu(c->count);
 | 
				
			||||||
 | 
						if (count_offset + (limit * sizeof(struct dx_entry)) >
 | 
				
			||||||
 | 
						    EXT4_BLOCK_SIZE(inode->i_sb) - sizeof(struct dx_tail)) {
 | 
				
			||||||
 | 
							EXT4_ERROR_INODE(inode, "metadata_csum set but no space for "
 | 
				
			||||||
 | 
									 "tree checksum found.  Run e2fsck -D.");
 | 
				
			||||||
 | 
							return 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t = (struct dx_tail *)(((struct dx_entry *)c) + limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (t->dt_checksum != ext4_dx_csum(inode, dirent, count_offset,
 | 
				
			||||||
 | 
										    count, t))
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						return 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct dx_countlimit *c;
 | 
				
			||||||
 | 
						struct dx_tail *t;
 | 
				
			||||||
 | 
						int count_offset, limit, count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb,
 | 
				
			||||||
 | 
										EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c = get_dx_countlimit(inode, dirent, &count_offset);
 | 
				
			||||||
 | 
						if (!c) {
 | 
				
			||||||
 | 
							EXT4_ERROR_INODE(inode, "dir seems corrupt?  Run e2fsck -D.");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						limit = le16_to_cpu(c->limit);
 | 
				
			||||||
 | 
						count = le16_to_cpu(c->count);
 | 
				
			||||||
 | 
						if (count_offset + (limit * sizeof(struct dx_entry)) >
 | 
				
			||||||
 | 
						    EXT4_BLOCK_SIZE(inode->i_sb) - sizeof(struct dx_tail)) {
 | 
				
			||||||
 | 
							EXT4_ERROR_INODE(inode, "metadata_csum set but no space for "
 | 
				
			||||||
 | 
									 "tree checksum.  Run e2fsck -D.");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t = (struct dx_tail *)(((struct dx_entry *)c) + limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t->dt_checksum = ext4_dx_csum(inode, dirent, count_offset, count, t);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline int ext4_handle_dirty_dx_node(handle_t *handle,
 | 
				
			||||||
 | 
										    struct inode *inode,
 | 
				
			||||||
 | 
										    struct buffer_head *bh)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						ext4_dx_csum_set(inode, (struct ext4_dir_entry *)bh->b_data);
 | 
				
			||||||
 | 
						return ext4_handle_dirty_metadata(handle, inode, bh);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * p is at least 6 bytes before the end of page
 | 
					 * p is at least 6 bytes before the end of page
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -247,12 +362,20 @@ static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(1) -
 | 
						unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(1) -
 | 
				
			||||||
		EXT4_DIR_REC_LEN(2) - infosize;
 | 
							EXT4_DIR_REC_LEN(2) - infosize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (EXT4_HAS_RO_COMPAT_FEATURE(dir->i_sb,
 | 
				
			||||||
 | 
									       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
 | 
				
			||||||
 | 
							entry_space -= sizeof(struct dx_tail);
 | 
				
			||||||
	return entry_space / sizeof(struct dx_entry);
 | 
						return entry_space / sizeof(struct dx_entry);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline unsigned dx_node_limit(struct inode *dir)
 | 
					static inline unsigned dx_node_limit(struct inode *dir)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(0);
 | 
						unsigned entry_space = dir->i_sb->s_blocksize - EXT4_DIR_REC_LEN(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (EXT4_HAS_RO_COMPAT_FEATURE(dir->i_sb,
 | 
				
			||||||
 | 
									       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
 | 
				
			||||||
 | 
							entry_space -= sizeof(struct dx_tail);
 | 
				
			||||||
	return entry_space / sizeof(struct dx_entry);
 | 
						return entry_space / sizeof(struct dx_entry);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -398,6 +521,15 @@ dx_probe(const struct qstr *d_name, struct inode *dir,
 | 
				
			||||||
		goto fail;
 | 
							goto fail;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!buffer_verified(bh) &&
 | 
				
			||||||
 | 
						    !ext4_dx_csum_verify(dir, (struct ext4_dir_entry *)bh->b_data)) {
 | 
				
			||||||
 | 
							ext4_warning(dir->i_sb, "Root failed checksum");
 | 
				
			||||||
 | 
							brelse(bh);
 | 
				
			||||||
 | 
							*err = ERR_BAD_DX_DIR;
 | 
				
			||||||
 | 
							goto fail;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						set_buffer_verified(bh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	entries = (struct dx_entry *) (((char *)&root->info) +
 | 
						entries = (struct dx_entry *) (((char *)&root->info) +
 | 
				
			||||||
				       root->info.info_length);
 | 
									       root->info.info_length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -458,6 +590,17 @@ dx_probe(const struct qstr *d_name, struct inode *dir,
 | 
				
			||||||
		if (!(bh = ext4_bread (NULL,dir, dx_get_block(at), 0, err)))
 | 
							if (!(bh = ext4_bread (NULL,dir, dx_get_block(at), 0, err)))
 | 
				
			||||||
			goto fail2;
 | 
								goto fail2;
 | 
				
			||||||
		at = entries = ((struct dx_node *) bh->b_data)->entries;
 | 
							at = entries = ((struct dx_node *) bh->b_data)->entries;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!buffer_verified(bh) &&
 | 
				
			||||||
 | 
							    !ext4_dx_csum_verify(dir,
 | 
				
			||||||
 | 
										 (struct ext4_dir_entry *)bh->b_data)) {
 | 
				
			||||||
 | 
								ext4_warning(dir->i_sb, "Node failed checksum");
 | 
				
			||||||
 | 
								brelse(bh);
 | 
				
			||||||
 | 
								*err = ERR_BAD_DX_DIR;
 | 
				
			||||||
 | 
								goto fail;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set_buffer_verified(bh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (dx_get_limit(entries) != dx_node_limit (dir)) {
 | 
							if (dx_get_limit(entries) != dx_node_limit (dir)) {
 | 
				
			||||||
			ext4_warning(dir->i_sb,
 | 
								ext4_warning(dir->i_sb,
 | 
				
			||||||
				     "dx entry: limit != node limit");
 | 
									     "dx entry: limit != node limit");
 | 
				
			||||||
| 
						 | 
					@ -557,6 +700,15 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash,
 | 
				
			||||||
		if (!(bh = ext4_bread(NULL, dir, dx_get_block(p->at),
 | 
							if (!(bh = ext4_bread(NULL, dir, dx_get_block(p->at),
 | 
				
			||||||
				      0, &err)))
 | 
									      0, &err)))
 | 
				
			||||||
			return err; /* Failure */
 | 
								return err; /* Failure */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!buffer_verified(bh) &&
 | 
				
			||||||
 | 
							    !ext4_dx_csum_verify(dir,
 | 
				
			||||||
 | 
										 (struct ext4_dir_entry *)bh->b_data)) {
 | 
				
			||||||
 | 
								ext4_warning(dir->i_sb, "Node failed checksum");
 | 
				
			||||||
 | 
								return -EIO;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set_buffer_verified(bh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		p++;
 | 
							p++;
 | 
				
			||||||
		brelse(p->bh);
 | 
							brelse(p->bh);
 | 
				
			||||||
		p->bh = bh;
 | 
							p->bh = bh;
 | 
				
			||||||
| 
						 | 
					@ -1232,7 +1384,7 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
 | 
				
			||||||
	err = ext4_handle_dirty_metadata(handle, dir, bh2);
 | 
						err = ext4_handle_dirty_metadata(handle, dir, bh2);
 | 
				
			||||||
	if (err)
 | 
						if (err)
 | 
				
			||||||
		goto journal_error;
 | 
							goto journal_error;
 | 
				
			||||||
	err = ext4_handle_dirty_metadata(handle, dir, frame->bh);
 | 
						err = ext4_handle_dirty_dx_node(handle, dir, frame->bh);
 | 
				
			||||||
	if (err)
 | 
						if (err)
 | 
				
			||||||
		goto journal_error;
 | 
							goto journal_error;
 | 
				
			||||||
	brelse(bh2);
 | 
						brelse(bh2);
 | 
				
			||||||
| 
						 | 
					@ -1419,7 +1571,7 @@ static int make_indexed_dir(handle_t *handle, struct dentry *dentry,
 | 
				
			||||||
	frame->bh = bh;
 | 
						frame->bh = bh;
 | 
				
			||||||
	bh = bh2;
 | 
						bh = bh2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ext4_handle_dirty_metadata(handle, dir, frame->bh);
 | 
						ext4_handle_dirty_dx_node(handle, dir, frame->bh);
 | 
				
			||||||
	ext4_handle_dirty_metadata(handle, dir, bh);
 | 
						ext4_handle_dirty_metadata(handle, dir, bh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	de = do_split(handle,dir, &bh, frame, &hinfo, &retval);
 | 
						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,
 | 
				
			||||||
			dxtrace(dx_show_index("node", frames[1].entries));
 | 
								dxtrace(dx_show_index("node", frames[1].entries));
 | 
				
			||||||
			dxtrace(dx_show_index("node",
 | 
								dxtrace(dx_show_index("node",
 | 
				
			||||||
			       ((struct dx_node *) bh2->b_data)->entries));
 | 
								       ((struct dx_node *) bh2->b_data)->entries));
 | 
				
			||||||
			err = ext4_handle_dirty_metadata(handle, dir, bh2);
 | 
								err = ext4_handle_dirty_dx_node(handle, dir, bh2);
 | 
				
			||||||
			if (err)
 | 
								if (err)
 | 
				
			||||||
				goto journal_error;
 | 
									goto journal_error;
 | 
				
			||||||
			brelse (bh2);
 | 
								brelse (bh2);
 | 
				
			||||||
| 
						 | 
					@ -1620,7 +1772,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
 | 
				
			||||||
			if (err)
 | 
								if (err)
 | 
				
			||||||
				goto journal_error;
 | 
									goto journal_error;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		err = ext4_handle_dirty_metadata(handle, dir, frames[0].bh);
 | 
							err = ext4_handle_dirty_dx_node(handle, dir, frames[0].bh);
 | 
				
			||||||
		if (err) {
 | 
							if (err) {
 | 
				
			||||||
			ext4_std_error(inode->i_sb, err);
 | 
								ext4_std_error(inode->i_sb, err);
 | 
				
			||||||
			goto cleanup;
 | 
								goto cleanup;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue