[ Upstream commit 4840ceadef4290c56cc422f0fc697655f3cbf070 ] The syzbot reported issue in __hfsplus_ext_cache_extent(): [ 70.194323][ T9350] BUG: KMSAN: uninit-value in __hfsplus_ext_cache_extent+0x7d0/0x990 [ 70.195022][ T9350] __hfsplus_ext_cache_extent+0x7d0/0x990 [ 70.195530][ T9350] hfsplus_file_extend+0x74f/0x1cf0 [ 70.195998][ T9350] hfsplus_get_block+0xe16/0x17b0 [ 70.196458][ T9350] __block_write_begin_int+0x962/0x2ce0 [ 70.196959][ T9350] cont_write_begin+0x1000/0x1950 [ 70.197416][ T9350] hfsplus_write_begin+0x85/0x130 [ 70.197873][ T9350] generic_perform_write+0x3e8/0x1060 [ 70.198374][ T9350] __generic_file_write_iter+0x215/0x460 [ 70.198892][ T9350] generic_file_write_iter+0x109/0x5e0 [ 70.199393][ T9350] vfs_write+0xb0f/0x14e0 [ 70.199771][ T9350] ksys_write+0x23e/0x490 [ 70.200149][ T9350] __x64_sys_write+0x97/0xf0 [ 70.200570][ T9350] x64_sys_call+0x3015/0x3cf0 [ 70.201065][ T9350] do_syscall_64+0xd9/0x1d0 [ 70.201506][ T9350] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 70.202054][ T9350] [ 70.202279][ T9350] Uninit was created at: [ 70.202693][ T9350] __kmalloc_noprof+0x621/0xf80 [ 70.203149][ T9350] hfsplus_find_init+0x8d/0x1d0 [ 70.203602][ T9350] hfsplus_file_extend+0x6ca/0x1cf0 [ 70.204087][ T9350] hfsplus_get_block+0xe16/0x17b0 [ 70.204561][ T9350] __block_write_begin_int+0x962/0x2ce0 [ 70.205074][ T9350] cont_write_begin+0x1000/0x1950 [ 70.205547][ T9350] hfsplus_write_begin+0x85/0x130 [ 70.206017][ T9350] generic_perform_write+0x3e8/0x1060 [ 70.206519][ T9350] __generic_file_write_iter+0x215/0x460 [ 70.207042][ T9350] generic_file_write_iter+0x109/0x5e0 [ 70.207552][ T9350] vfs_write+0xb0f/0x14e0 [ 70.207961][ T9350] ksys_write+0x23e/0x490 [ 70.208375][ T9350] __x64_sys_write+0x97/0xf0 [ 70.208810][ T9350] x64_sys_call+0x3015/0x3cf0 [ 70.209255][ T9350] do_syscall_64+0xd9/0x1d0 [ 70.209680][ T9350] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 70.210230][ T9350] [ 70.210454][ T9350] CPU: 2 UID: 0 PID: 9350 Comm: repro Not tainted 6.12.0-rc5 #5 [ 70.211174][ T9350] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 70.212115][ T9350] ===================================================== [ 70.212734][ T9350] Disabling lock debugging due to kernel taint [ 70.213284][ T9350] Kernel panic - not syncing: kmsan.panic set ... [ 70.213858][ T9350] CPU: 2 UID: 0 PID: 9350 Comm: repro Tainted: G B 6.12.0-rc5 #5 [ 70.214679][ T9350] Tainted: [B]=BAD_PAGE [ 70.215057][ T9350] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 70.215999][ T9350] Call Trace: [ 70.216309][ T9350] <TASK> [ 70.216585][ T9350] dump_stack_lvl+0x1fd/0x2b0 [ 70.217025][ T9350] dump_stack+0x1e/0x30 [ 70.217421][ T9350] panic+0x502/0xca0 [ 70.217803][ T9350] ? kmsan_get_metadata+0x13e/0x1c0 [ 70.218294][ Message fromT sy9350] kmsan_report+0x296/slogd@syzkaller 0x2aat Aug 18 22:11:058 ... kernel :[ 70.213284][ T9350] Kernel panic - not syncing: kmsan.panic [ 70.220179][ T9350] ? kmsan_get_metadata+0x13e/0x1c0 set ... [ 70.221254][ T9350] ? __msan_warning+0x96/0x120 [ 70.222066][ T9350] ? __hfsplus_ext_cache_extent+0x7d0/0x990 [ 70.223023][ T9350] ? hfsplus_file_extend+0x74f/0x1cf0 [ 70.224120][ T9350] ? hfsplus_get_block+0xe16/0x17b0 [ 70.224946][ T9350] ? __block_write_begin_int+0x962/0x2ce0 [ 70.225756][ T9350] ? cont_write_begin+0x1000/0x1950 [ 70.226337][ T9350] ? hfsplus_write_begin+0x85/0x130 [ 70.226852][ T9350] ? generic_perform_write+0x3e8/0x1060 [ 70.227405][ T9350] ? __generic_file_write_iter+0x215/0x460 [ 70.227979][ T9350] ? generic_file_write_iter+0x109/0x5e0 [ 70.228540][ T9350] ? vfs_write+0xb0f/0x14e0 [ 70.228997][ T9350] ? ksys_write+0x23e/0x490 [ 70.229458][ T9350] ? __x64_sys_write+0x97/0xf0 [ 70.229939][ T9350] ? x64_sys_call+0x3015/0x3cf0 [ 70.230432][ T9350] ? do_syscall_64+0xd9/0x1d0 [ 70.230941][ T9350] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 70.231926][ T9350] ? kmsan_get_metadata+0x13e/0x1c0 [ 70.232738][ T9350] ? kmsan_internal_set_shadow_origin+0x77/0x110 [ 70.233711][ T9350] ? kmsan_get_metadata+0x13e/0x1c0 [ 70.234516][ T9350] ? kmsan_get_shadow_origin_ptr+0x4a/0xb0 [ 70.235398][ T9350] ? __msan_metadata_ptr_for_load_4+0x24/0x40 [ 70.236323][ T9350] ? hfsplus_brec_find+0x218/0x9f0 [ 70.237090][ T9350] ? __pfx_hfs_find_rec_by_key+0x10/0x10 [ 70.237938][ T9350] ? __msan_instrument_asm_store+0xbf/0xf0 [ 70.238827][ T9350] ? __msan_metadata_ptr_for_store_4+0x27/0x40 [ 70.239772][ T9350] ? __hfsplus_ext_write_extent+0x536/0x620 [ 70.240666][ T9350] ? kmsan_get_metadata+0x13e/0x1c0 [ 70.241175][ T9350] __msan_warning+0x96/0x120 [ 70.241645][ T9350] __hfsplus_ext_cache_extent+0x7d0/0x990 [ 70.242223][ T9350] hfsplus_file_extend+0x74f/0x1cf0 [ 70.242748][ T9350] hfsplus_get_block+0xe16/0x17b0 [ 70.243255][ T9350] ? kmsan_internal_set_shadow_origin+0x77/0x110 [ 70.243878][ T9350] ? kmsan_get_metadata+0x13e/0x1c0 [ 70.244400][ T9350] ? kmsan_get_shadow_origin_ptr+0x4a/0xb0 [ 70.244967][ T9350] __block_write_begin_int+0x962/0x2ce0 [ 70.245531][ T9350] ? __pfx_hfsplus_get_block+0x10/0x10 [ 70.246079][ T9350] cont_write_begin+0x1000/0x1950 [ 70.246598][ T9350] hfsplus_write_begin+0x85/0x130 [ 70.247105][ T9350] ? __pfx_hfsplus_get_block+0x10/0x10 [ 70.247650][ T9350] ? __pfx_hfsplus_write_begin+0x10/0x10 [ 70.248211][ T9350] generic_perform_write+0x3e8/0x1060 [ 70.248752][ T9350] __generic_file_write_iter+0x215/0x460 [ 70.249314][ T9350] generic_file_write_iter+0x109/0x5e0 [ 70.249856][ T9350] ? kmsan_internal_set_shadow_origin+0x77/0x110 [ 70.250487][ T9350] vfs_write+0xb0f/0x14e0 [ 70.250930][ T9350] ? __pfx_generic_file_write_iter+0x10/0x10 [ 70.251530][ T9350] ksys_write+0x23e/0x490 [ 70.251974][ T9350] __x64_sys_write+0x97/0xf0 [ 70.252450][ T9350] x64_sys_call+0x3015/0x3cf0 [ 70.252924][ T9350] do_syscall_64+0xd9/0x1d0 [ 70.253384][ T9350] ? irqentry_exit+0x16/0x60 [ 70.253844][ T9350] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 70.254430][ T9350] RIP: 0033:0x7f7a92adffc9 [ 70.254873][ T9350] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 48 [ 70.256674][ T9350] RSP: 002b:00007fff0bca3188 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 [ 70.257485][ T9350] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f7a92adffc9 [ 70.258246][ T9350] RDX: 000000000208e24b RSI: 0000000020000100 RDI: 0000000000000004 [ 70.258998][ T9350] RBP: 00007fff0bca31a0 R08: 00007fff0bca31a0 R09: 00007fff0bca31a0 [ 70.259769][ T9350] R10: 0000000000000000 R11: 0000000000000202 R12: 000055e0d75f8250 [ 70.260520][ T9350] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 [ 70.261286][ T9350] </TASK> [ 70.262026][ T9350] Kernel Offset: disabled (gdb) l *__hfsplus_ext_cache_extent+0x7d0 0xffffffff8318aef0 is in __hfsplus_ext_cache_extent (fs/hfsplus/extents.c:168). 163 fd->key->ext.cnid = 0; 164 res = hfs_brec_find(fd, hfs_find_rec_by_key); 165 if (res && res != -ENOENT) 166 return res; 167 if (fd->key->ext.cnid != fd->search_key->ext.cnid || 168 fd->key->ext.fork_type != fd->search_key->ext.fork_type) 169 return -ENOENT; 170 if (fd->entrylength != sizeof(hfsplus_extent_rec)) 171 return -EIO; 172 hfs_bnode_read(fd->bnode, extent, fd->entryoffset, The __hfsplus_ext_cache_extent() calls __hfsplus_ext_read_extent(): res = __hfsplus_ext_read_extent(fd, hip->cached_extents, inode->i_ino, block, HFSPLUS_IS_RSRC(inode) ? HFSPLUS_TYPE_RSRC : HFSPLUS_TYPE_DATA); And if inode->i_ino could be equal to zero or any non-available CNID, then hfs_brec_find() could not find the record in the tree. As a result, fd->key could be compared with fd->search_key. But hfsplus_find_init() uses kmalloc() for fd->key and fd->search_key allocation: int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd) { <skipped> ptr = kmalloc(tree->max_key_len * 2 + 4, GFP_KERNEL); if (!ptr) return -ENOMEM; fd->search_key = ptr; fd->key = ptr + tree->max_key_len + 2; <skipped> } Finally, fd->key is still not initialized if hfs_brec_find() has found nothing. This patch changes kmalloc() on kzalloc() in hfs_find_init() and intializes fd->record, fd->keyoffset, fd->keylength, fd->entryoffset, fd->entrylength for the case if hfs_brec_find() has been found nothing in the b-tree node. Reported-by: syzbot <syzbot+55ad87f38795d6787521@syzkaller.appspotmail.com> Closes: https://syzkaller.appspot.com/bug?extid=55ad87f38795d6787521 Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de> cc: Yangtao Li <frank.li@vivo.com> cc: linux-fsdevel@vger.kernel.org Link: https://lore.kernel.org/r/20250818225232.126402-1-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
290 lines
6.0 KiB
C
290 lines
6.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/hfsplus/bfind.c
|
|
*
|
|
* Copyright (C) 2001
|
|
* Brad Boyer (flar@allandria.com)
|
|
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
|
*
|
|
* Search routines for btrees
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include "hfsplus_fs.h"
|
|
|
|
int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)
|
|
{
|
|
void *ptr;
|
|
|
|
fd->tree = tree;
|
|
fd->bnode = NULL;
|
|
ptr = kzalloc(tree->max_key_len * 2 + 4, GFP_KERNEL);
|
|
if (!ptr)
|
|
return -ENOMEM;
|
|
fd->search_key = ptr;
|
|
fd->key = ptr + tree->max_key_len + 2;
|
|
hfs_dbg(BNODE_REFS, "find_init: %d (%p)\n",
|
|
tree->cnid, __builtin_return_address(0));
|
|
mutex_lock_nested(&tree->tree_lock,
|
|
hfsplus_btree_lock_class(tree));
|
|
return 0;
|
|
}
|
|
|
|
void hfs_find_exit(struct hfs_find_data *fd)
|
|
{
|
|
hfs_bnode_put(fd->bnode);
|
|
kfree(fd->search_key);
|
|
hfs_dbg(BNODE_REFS, "find_exit: %d (%p)\n",
|
|
fd->tree->cnid, __builtin_return_address(0));
|
|
mutex_unlock(&fd->tree->tree_lock);
|
|
fd->tree = NULL;
|
|
}
|
|
|
|
int hfs_find_1st_rec_by_cnid(struct hfs_bnode *bnode,
|
|
struct hfs_find_data *fd,
|
|
int *begin,
|
|
int *end,
|
|
int *cur_rec)
|
|
{
|
|
__be32 cur_cnid;
|
|
__be32 search_cnid;
|
|
|
|
if (bnode->tree->cnid == HFSPLUS_EXT_CNID) {
|
|
cur_cnid = fd->key->ext.cnid;
|
|
search_cnid = fd->search_key->ext.cnid;
|
|
} else if (bnode->tree->cnid == HFSPLUS_CAT_CNID) {
|
|
cur_cnid = fd->key->cat.parent;
|
|
search_cnid = fd->search_key->cat.parent;
|
|
} else if (bnode->tree->cnid == HFSPLUS_ATTR_CNID) {
|
|
cur_cnid = fd->key->attr.cnid;
|
|
search_cnid = fd->search_key->attr.cnid;
|
|
} else {
|
|
cur_cnid = 0; /* used-uninitialized warning */
|
|
search_cnid = 0;
|
|
BUG();
|
|
}
|
|
|
|
if (cur_cnid == search_cnid) {
|
|
(*end) = (*cur_rec);
|
|
if ((*begin) == (*end))
|
|
return 1;
|
|
} else {
|
|
if (be32_to_cpu(cur_cnid) < be32_to_cpu(search_cnid))
|
|
(*begin) = (*cur_rec) + 1;
|
|
else
|
|
(*end) = (*cur_rec) - 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hfs_find_rec_by_key(struct hfs_bnode *bnode,
|
|
struct hfs_find_data *fd,
|
|
int *begin,
|
|
int *end,
|
|
int *cur_rec)
|
|
{
|
|
int cmpval;
|
|
|
|
cmpval = bnode->tree->keycmp(fd->key, fd->search_key);
|
|
if (!cmpval) {
|
|
(*end) = (*cur_rec);
|
|
return 1;
|
|
}
|
|
if (cmpval < 0)
|
|
(*begin) = (*cur_rec) + 1;
|
|
else
|
|
*(end) = (*cur_rec) - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Find the record in bnode that best matches key (not greater than...)*/
|
|
int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd,
|
|
search_strategy_t rec_found)
|
|
{
|
|
u16 off, len, keylen;
|
|
int rec;
|
|
int b, e;
|
|
int res;
|
|
|
|
BUG_ON(!rec_found);
|
|
b = 0;
|
|
e = bnode->num_recs - 1;
|
|
res = -ENOENT;
|
|
do {
|
|
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);
|
|
if (rec_found(bnode, fd, &b, &e, &rec)) {
|
|
res = 0;
|
|
goto done;
|
|
}
|
|
} while (b <= e);
|
|
|
|
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:
|
|
fd->record = e;
|
|
fd->keyoffset = off;
|
|
fd->keylength = keylen;
|
|
fd->entryoffset = off + keylen;
|
|
fd->entrylength = len - keylen;
|
|
|
|
fail:
|
|
return res;
|
|
}
|
|
|
|
/* Traverse a B*Tree from the root to a leaf finding best fit to key */
|
|
/* Return allocated copy of node found, set recnum to best record */
|
|
int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare)
|
|
{
|
|
struct hfs_btree *tree;
|
|
struct hfs_bnode *bnode;
|
|
u32 nidx, parent;
|
|
__be32 data;
|
|
int height, res;
|
|
|
|
fd->record = -1;
|
|
fd->keyoffset = -1;
|
|
fd->keylength = -1;
|
|
fd->entryoffset = -1;
|
|
fd->entrylength = -1;
|
|
|
|
tree = fd->tree;
|
|
if (fd->bnode)
|
|
hfs_bnode_put(fd->bnode);
|
|
fd->bnode = NULL;
|
|
nidx = tree->root;
|
|
if (!nidx)
|
|
return -ENOENT;
|
|
height = tree->depth;
|
|
res = 0;
|
|
parent = 0;
|
|
for (;;) {
|
|
bnode = hfs_bnode_find(tree, nidx);
|
|
if (IS_ERR(bnode)) {
|
|
res = PTR_ERR(bnode);
|
|
bnode = NULL;
|
|
break;
|
|
}
|
|
if (bnode->height != height)
|
|
goto invalid;
|
|
if (bnode->type != (--height ? HFS_NODE_INDEX : HFS_NODE_LEAF))
|
|
goto invalid;
|
|
bnode->parent = parent;
|
|
|
|
res = __hfs_brec_find(bnode, fd, do_key_compare);
|
|
if (!height)
|
|
break;
|
|
if (fd->record < 0)
|
|
goto release;
|
|
|
|
parent = nidx;
|
|
hfs_bnode_read(bnode, &data, fd->entryoffset, 4);
|
|
nidx = be32_to_cpu(data);
|
|
hfs_bnode_put(bnode);
|
|
}
|
|
fd->bnode = bnode;
|
|
return res;
|
|
|
|
invalid:
|
|
pr_err("inconsistency in B*Tree (%d,%d,%d,%u,%u)\n",
|
|
height, bnode->height, bnode->type, nidx, parent);
|
|
res = -EIO;
|
|
release:
|
|
hfs_bnode_put(bnode);
|
|
return res;
|
|
}
|
|
|
|
int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)
|
|
{
|
|
int res;
|
|
|
|
res = hfs_brec_find(fd, hfs_find_rec_by_key);
|
|
if (res)
|
|
return res;
|
|
if (fd->entrylength > rec_len)
|
|
return -EINVAL;
|
|
hfs_bnode_read(fd->bnode, rec, fd->entryoffset, fd->entrylength);
|
|
return 0;
|
|
}
|
|
|
|
int hfs_brec_goto(struct hfs_find_data *fd, int cnt)
|
|
{
|
|
struct hfs_btree *tree;
|
|
struct hfs_bnode *bnode;
|
|
int idx, res = 0;
|
|
u16 off, len, keylen;
|
|
|
|
bnode = fd->bnode;
|
|
tree = bnode->tree;
|
|
|
|
if (cnt < 0) {
|
|
cnt = -cnt;
|
|
while (cnt > fd->record) {
|
|
cnt -= fd->record + 1;
|
|
fd->record = bnode->num_recs - 1;
|
|
idx = bnode->prev;
|
|
if (!idx) {
|
|
res = -ENOENT;
|
|
goto out;
|
|
}
|
|
hfs_bnode_put(bnode);
|
|
bnode = hfs_bnode_find(tree, idx);
|
|
if (IS_ERR(bnode)) {
|
|
res = PTR_ERR(bnode);
|
|
bnode = NULL;
|
|
goto out;
|
|
}
|
|
}
|
|
fd->record -= cnt;
|
|
} else {
|
|
while (cnt >= bnode->num_recs - fd->record) {
|
|
cnt -= bnode->num_recs - fd->record;
|
|
fd->record = 0;
|
|
idx = bnode->next;
|
|
if (!idx) {
|
|
res = -ENOENT;
|
|
goto out;
|
|
}
|
|
hfs_bnode_put(bnode);
|
|
bnode = hfs_bnode_find(tree, idx);
|
|
if (IS_ERR(bnode)) {
|
|
res = PTR_ERR(bnode);
|
|
bnode = NULL;
|
|
goto out;
|
|
}
|
|
}
|
|
fd->record += 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;
|
|
fd->entrylength = len - keylen;
|
|
hfs_bnode_read(bnode, fd->key, off, keylen);
|
|
out:
|
|
fd->bnode = bnode;
|
|
return res;
|
|
}
|