Skip to content

Commit

Permalink
ext4: Calculate and verify checksums for htree nodes
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Darrick J. Wong authored and Theodore Ts'o committed Apr 29, 2012
1 parent 7ac5990 commit dbe8944
Showing 1 changed file with 156 additions and 4 deletions.
160 changes: 156 additions & 4 deletions fs/ext4/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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
*/
Expand Down Expand Up @@ -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) -
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);
}

static inline unsigned dx_node_limit(struct inode *dir)
{
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);
}

Expand Down Expand Up @@ -398,6 +521,15 @@ dx_probe(const struct qstr *d_name, struct inode *dir,
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) +
root->info.info_length);

Expand Down Expand Up @@ -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)))
goto fail2;
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)) {
ext4_warning(dir->i_sb,
"dx entry: limit != node limit");
Expand Down Expand Up @@ -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),
0, &err)))
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++;
brelse(p->bh);
p->bh = bh;
Expand Down Expand Up @@ -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);
if (err)
goto journal_error;
err = ext4_handle_dirty_metadata(handle, dir, frame->bh);
err = ext4_handle_dirty_dx_node(handle, dir, frame->bh);
if (err)
goto journal_error;
brelse(bh2);
Expand Down Expand Up @@ -1419,7 +1571,7 @@ static int make_indexed_dir(handle_t *handle, struct dentry *dentry,
frame->bh = bh;
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);

de = do_split(handle,dir, &bh, frame, &hinfo, &retval);
Expand Down Expand Up @@ -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",
((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)
goto journal_error;
brelse (bh2);
Expand All @@ -1620,7 +1772,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
if (err)
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) {
ext4_std_error(inode->i_sb, err);
goto cleanup;
Expand Down

0 comments on commit dbe8944

Please sign in to comment.