Skip to content

Commit

Permalink
hfsplus: handle more on-disk corruptions without oopsing
Browse files Browse the repository at this point in the history
hfs seems prone to bad things when it encounters on disk corruption.  Many
values are read from disk, and used as lengths to memcpy, as an example.
This patch fixes up several of these problematic cases.

o sanity check the on-disk maximum key lengths on mount
  (these are set to a defined value at mkfs time and shouldn't differ)
o check on-disk node keylens against the maximum key length for each tree
o fix hfs_btree_open so that going out via free_tree: doesn't wind
  up in hfs_releasepage, which wants to follow the very pointer
  we were trying to set up:
	HFS_SB(sb)->cat_tree = hfs_btree_open()
    .
  failure gets to hfs_releasepage and tries to follow HFS_SB(sb)->cat_tree

Tested with the fsfuzzer; it survives more than it used to.

[hch: ported of commit cf05946 from hfs]
[hch: added the fixes from 5581d018ed3493d226e7a4d645d9c8a5af6c36b]

Signed-off-by: Eric Sandeen <[email protected]>
Signed-off-by: Christoph Hellwig <[email protected]>
  • Loading branch information
Eric Sandeen authored and Christoph Hellwig committed Oct 14, 2010
1 parent b6b4142 commit 9250f92
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 8 deletions.
13 changes: 13 additions & 0 deletions fs/hfsplus/bfind.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd)
rec = (e + b) / 2;
len = hfs_brec_lenoff(bnode, rec, &off);
keylen = hfs_brec_keylen(bnode, rec);
if (keylen == 0) {
res = -EINVAL;
goto fail;
}
hfs_bnode_read(bnode, fd->key, off, keylen);
cmpval = bnode->tree->keycmp(fd->key, fd->search_key);
if (!cmpval) {
Expand All @@ -67,6 +71,10 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd)
if (rec != e && e >= 0) {
len = hfs_brec_lenoff(bnode, e, &off);
keylen = hfs_brec_keylen(bnode, e);
if (keylen == 0) {
res = -EINVAL;
goto fail;
}
hfs_bnode_read(bnode, fd->key, off, keylen);
}
done:
Expand All @@ -75,6 +83,7 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd)
fd->keylength = keylen;
fd->entryoffset = off + keylen;
fd->entrylength = len - keylen;
fail:
return res;
}

Expand Down Expand Up @@ -198,6 +207,10 @@ int hfs_brec_goto(struct hfs_find_data *fd, int cnt)

len = hfs_brec_lenoff(bnode, fd->record, &off);
keylen = hfs_brec_keylen(bnode, fd->record);
if (keylen == 0) {
res = -EINVAL;
goto out;
}
fd->keyoffset = off;
fd->keylength = keylen;
fd->entryoffset = off + keylen;
Expand Down
15 changes: 13 additions & 2 deletions fs/hfsplus/brec.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,21 @@ u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec)
recoff = hfs_bnode_read_u16(node, node->tree->node_size - (rec + 1) * 2);
if (!recoff)
return 0;
if (node->tree->attributes & HFS_TREE_BIGKEYS)
if (node->tree->attributes & HFS_TREE_BIGKEYS) {
retval = hfs_bnode_read_u16(node, recoff) + 2;
else
if (retval > node->tree->max_key_len + 2) {
printk(KERN_ERR "hfs: keylen %d too large\n",
retval);
retval = 0;
}
} else {
retval = (hfs_bnode_read_u8(node, recoff) | 1) + 1;
if (retval > node->tree->max_key_len + 1) {
printk(KERN_ERR "hfs: keylen %d too large\n",
retval);
retval = 0;
}
}
}
return retval;
}
Expand Down
25 changes: 20 additions & 5 deletions fs/hfsplus/btree.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,32 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)
tree->max_key_len = be16_to_cpu(head->max_key_len);
tree->depth = be16_to_cpu(head->depth);

/* Set the correct compare function */
if (id == HFSPLUS_EXT_CNID) {
/* Verify the tree and set the correct compare function */
switch (id) {
case HFSPLUS_EXT_CNID:
if (tree->max_key_len != HFSPLUS_EXT_KEYLEN - sizeof(u16)) {
printk(KERN_ERR "hfs: invalid extent max_key_len %d\n",
tree->max_key_len);
goto fail_page;
}
tree->keycmp = hfsplus_ext_cmp_key;
} else if (id == HFSPLUS_CAT_CNID) {
break;
case HFSPLUS_CAT_CNID:
if (tree->max_key_len != HFSPLUS_CAT_KEYLEN - sizeof(u16)) {
printk(KERN_ERR "hfs: invalid catalog max_key_len %d\n",
tree->max_key_len);
goto fail_page;
}

if (test_bit(HFSPLUS_SB_HFSX, &HFSPLUS_SB(sb)->flags) &&
(head->key_type == HFSPLUS_KEY_BINARY))
tree->keycmp = hfsplus_cat_bin_cmp_key;
else {
tree->keycmp = hfsplus_cat_case_cmp_key;
set_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
}
} else {
break;
default:
printk(KERN_ERR "hfs: unknown B*Tree requested\n");
goto fail_page;
}
Expand All @@ -84,6 +98,7 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)
goto fail_page;
if (!tree->node_count)
goto fail_page;

tree->node_size_shift = ffs(size) - 1;

tree->pages_per_bnode = (tree->node_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
Expand All @@ -93,9 +108,9 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)
return tree;

fail_page:
tree->inode->i_mapping->a_ops = &hfsplus_aops;
page_cache_release(page);
free_inode:
tree->inode->i_mapping->a_ops = &hfsplus_aops;
iput(tree->inode);
free_tree:
kfree(tree);
Expand Down
3 changes: 2 additions & 1 deletion fs/hfsplus/hfsplus_raw.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ struct hfsplus_cat_key {
struct hfsplus_unistr name;
} __packed;

#define HFSPLUS_CAT_KEYLEN (sizeof(struct hfsplus_cat_key))

/* Structs from hfs.h */
struct hfsp_point {
Expand Down Expand Up @@ -323,7 +324,7 @@ struct hfsplus_ext_key {
__be32 start_block;
} __packed;

#define HFSPLUS_EXT_KEYLEN 12
#define HFSPLUS_EXT_KEYLEN sizeof(struct hfsplus_ext_key)

/* HFS+ generic BTree key */
typedef union {
Expand Down

0 comments on commit 9250f92

Please sign in to comment.