From: Yang Chenzhi <yang.chenzhi@xxxxxxxx> Restructure hfs_brec_lenoff into a function that validates both offset and length. This function now returns an error code to indicate whether the execution is correct. This helps fix slab out-of-bounds issues in hfs_bmap_alloc Replace the old hfs_brec_lenoff interface with the new version. Signed-off-by: Yang Chenzhi <yang.chenzhi@xxxxxxxx> --- fs/hfs/bfind.c | 14 +++++++------- fs/hfs/brec.c | 13 ++++++++++--- fs/hfs/btree.c | 21 +++++++++++++++++---- fs/hfs/btree.h | 2 +- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/fs/hfs/bfind.c b/fs/hfs/bfind.c index 34e9804e0f36..aea6edd4d830 100644 --- a/fs/hfs/bfind.c +++ b/fs/hfs/bfind.c @@ -61,16 +61,16 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd) u16 off, len, keylen; int rec; int b, e; - int res; + int res, ret; b = 0; e = bnode->num_recs - 1; res = -ENOENT; do { rec = (e + b) / 2; - len = hfs_brec_lenoff(bnode, rec, &off); + ret = hfs_brec_lenoff(bnode, rec, &off, &len); keylen = hfs_brec_keylen(bnode, rec); - if (keylen == 0) { + if (keylen == 0 || ret) { res = -EINVAL; goto fail; } @@ -87,9 +87,9 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd) e = rec - 1; } while (b <= e); if (rec != e && e >= 0) { - len = hfs_brec_lenoff(bnode, e, &off); + ret = hfs_brec_lenoff(bnode, e, &off, &len); keylen = hfs_brec_keylen(bnode, e); - if (keylen == 0) { + if (keylen == 0 || ret) { res = -EINVAL; goto fail; } @@ -223,9 +223,9 @@ int hfs_brec_goto(struct hfs_find_data *fd, int cnt) fd->record += cnt; } - len = hfs_brec_lenoff(bnode, fd->record, &off); + res = hfs_brec_lenoff(bnode, fd->record, &off, &len); keylen = hfs_brec_keylen(bnode, fd->record); - if (keylen == 0) { + if (keylen == 0 || res) { res = -EINVAL; goto out; } diff --git a/fs/hfs/brec.c b/fs/hfs/brec.c index 896396554bcc..d7026a3ffeea 100644 --- a/fs/hfs/brec.c +++ b/fs/hfs/brec.c @@ -16,15 +16,22 @@ static int hfs_brec_update_parent(struct hfs_find_data *fd); static int hfs_btree_inc_height(struct hfs_btree *tree); /* Get the length and offset of the given record in the given node */ -u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off) +int hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off, u16 *len) { __be16 retval[2]; u16 dataoff; + int res; dataoff = node->tree->node_size - (rec + 2) * 2; - hfs_bnode_read(node, retval, dataoff, 4); + res = __hfs_bnode_read(node, retval, dataoff, 4); + if (res) + return -EINVAL; *off = be16_to_cpu(retval[1]); - return be16_to_cpu(retval[0]) - *off; + *len = be16_to_cpu(retval[0]) - *off; + if (!hfs_off_and_len_is_valid(node, *off, *len) || + *off < sizeof(struct hfs_bnode_desc)) + return -EINVAL; + return 0; } /* Get the length of the key from a keyed record */ diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c index e86e1e235658..b13582dcc27a 100644 --- a/fs/hfs/btree.c +++ b/fs/hfs/btree.c @@ -301,7 +301,9 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) node = hfs_bnode_find(tree, nidx); if (IS_ERR(node)) return node; - len = hfs_brec_lenoff(node, 2, &off16); + res = hfs_brec_lenoff(node, 2, &off16, &len); + if (res) + return ERR_PTR(res); off = off16; off += node->page_offset; @@ -347,7 +349,9 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) return next_node; node = next_node; - len = hfs_brec_lenoff(node, 0, &off16); + res = hfs_brec_lenoff(node, 0, &off16, &len); + if (res) + return ERR_PTR(res); off = off16; off += node->page_offset; pagep = node->page + (off >> PAGE_SHIFT); @@ -363,6 +367,7 @@ void hfs_bmap_free(struct hfs_bnode *node) u16 off, len; u32 nidx; u8 *data, byte, m; + int res; hfs_dbg(BNODE_MOD, "btree_free_node: %u\n", node->this); tree = node->tree; @@ -370,7 +375,9 @@ void hfs_bmap_free(struct hfs_bnode *node) node = hfs_bnode_find(tree, 0); if (IS_ERR(node)) return; - len = hfs_brec_lenoff(node, 2, &off); + res = hfs_brec_lenoff(node, 2, &off, &len); + if (res) + goto fail; while (nidx >= len * 8) { u32 i; @@ -394,7 +401,9 @@ void hfs_bmap_free(struct hfs_bnode *node) hfs_bnode_put(node); return; } - len = hfs_brec_lenoff(node, 0, &off); + res = hfs_brec_lenoff(node, 0, &off, &len); + if (res) + goto fail; } off += node->page_offset + nidx / 8; page = node->page[off >> PAGE_SHIFT]; @@ -415,4 +424,8 @@ void hfs_bmap_free(struct hfs_bnode *node) hfs_bnode_put(node); tree->free_nodes++; mark_inode_dirty(tree->inode); + return; +fail: + hfs_bnode_put(node); + pr_err("fail to free a bnode due to invalid off or len\n"); } diff --git a/fs/hfs/btree.h b/fs/hfs/btree.h index bf780bf4a016..78f228e62a86 100644 --- a/fs/hfs/btree.h +++ b/fs/hfs/btree.h @@ -117,7 +117,7 @@ extern void hfs_bnode_get(struct hfs_bnode *); extern void hfs_bnode_put(struct hfs_bnode *); /* brec.c */ -extern u16 hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *); +extern int hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *, u16 *); extern u16 hfs_brec_keylen(struct hfs_bnode *, u16); extern int hfs_brec_insert(struct hfs_find_data *, void *, int); extern int hfs_brec_remove(struct hfs_find_data *); -- 2.43.0