mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-17 11:28:23 +00:00

The hfs_find_init() method can trigger the crash if tree pointer is NULL: [ 45.746290][ T9787] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000008: 0000 [#1] SMP KAI [ 45.747287][ T9787] KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047] [ 45.748716][ T9787] CPU: 2 UID: 0 PID: 9787 Comm: repro Not tainted 6.16.0-rc3 #10 PREEMPT(full) [ 45.750250][ T9787] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 45.751983][ T9787] RIP: 0010:hfs_find_init+0x86/0x230 [ 45.752834][ T9787] Code: c1 ea 03 80 3c 02 00 0f 85 9a 01 00 00 4c 8d 6b 40 48 c7 45 18 00 00 00 00 48 b8 00 00 00 00 00 fc [ 45.755574][ T9787] RSP: 0018:ffffc90015157668 EFLAGS: 00010202 [ 45.756432][ T9787] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff819a4d09 [ 45.757457][ T9787] RDX: 0000000000000008 RSI: ffffffff819acd3a RDI: ffffc900151576e8 [ 45.758282][ T9787] RBP: ffffc900151576d0 R08: 0000000000000005 R09: 0000000000000000 [ 45.758943][ T9787] R10: 0000000080000000 R11: 0000000000000001 R12: 0000000000000004 [ 45.759619][ T9787] R13: 0000000000000040 R14: ffff88802c50814a R15: 0000000000000000 [ 45.760293][ T9787] FS: 00007ffb72734540(0000) GS:ffff8880cec64000(0000) knlGS:0000000000000000 [ 45.761050][ T9787] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 45.761606][ T9787] CR2: 00007f9bd8225000 CR3: 000000010979a000 CR4: 00000000000006f0 [ 45.762286][ T9787] Call Trace: [ 45.762570][ T9787] <TASK> [ 45.762824][ T9787] hfs_ext_read_extent+0x190/0x9d0 [ 45.763269][ T9787] ? submit_bio_noacct_nocheck+0x2dd/0xce0 [ 45.763766][ T9787] ? __pfx_hfs_ext_read_extent+0x10/0x10 [ 45.764250][ T9787] hfs_get_block+0x55f/0x830 [ 45.764646][ T9787] block_read_full_folio+0x36d/0x850 [ 45.765105][ T9787] ? __pfx_hfs_get_block+0x10/0x10 [ 45.765541][ T9787] ? const_folio_flags+0x5b/0x100 [ 45.765972][ T9787] ? __pfx_hfs_read_folio+0x10/0x10 [ 45.766415][ T9787] filemap_read_folio+0xbe/0x290 [ 45.766840][ T9787] ? __pfx_filemap_read_folio+0x10/0x10 [ 45.767325][ T9787] ? __filemap_get_folio+0x32b/0xbf0 [ 45.767780][ T9787] do_read_cache_folio+0x263/0x5c0 [ 45.768223][ T9787] ? __pfx_hfs_read_folio+0x10/0x10 [ 45.768666][ T9787] read_cache_page+0x5b/0x160 [ 45.769070][ T9787] hfs_btree_open+0x491/0x1740 [ 45.769481][ T9787] hfs_mdb_get+0x15e2/0x1fb0 [ 45.769877][ T9787] ? __pfx_hfs_mdb_get+0x10/0x10 [ 45.770316][ T9787] ? find_held_lock+0x2b/0x80 [ 45.770731][ T9787] ? lockdep_init_map_type+0x5c/0x280 [ 45.771200][ T9787] ? lockdep_init_map_type+0x5c/0x280 [ 45.771674][ T9787] hfs_fill_super+0x38e/0x720 [ 45.772092][ T9787] ? __pfx_hfs_fill_super+0x10/0x10 [ 45.772549][ T9787] ? snprintf+0xbe/0x100 [ 45.772931][ T9787] ? __pfx_snprintf+0x10/0x10 [ 45.773350][ T9787] ? do_raw_spin_lock+0x129/0x2b0 [ 45.773796][ T9787] ? find_held_lock+0x2b/0x80 [ 45.774215][ T9787] ? set_blocksize+0x40a/0x510 [ 45.774636][ T9787] ? sb_set_blocksize+0x176/0x1d0 [ 45.775087][ T9787] ? setup_bdev_super+0x369/0x730 [ 45.775533][ T9787] get_tree_bdev_flags+0x384/0x620 [ 45.775985][ T9787] ? __pfx_hfs_fill_super+0x10/0x10 [ 45.776453][ T9787] ? __pfx_get_tree_bdev_flags+0x10/0x10 [ 45.776950][ T9787] ? bpf_lsm_capable+0x9/0x10 [ 45.777365][ T9787] ? security_capable+0x80/0x260 [ 45.777803][ T9787] vfs_get_tree+0x8e/0x340 [ 45.778203][ T9787] path_mount+0x13de/0x2010 [ 45.778604][ T9787] ? kmem_cache_free+0x2b0/0x4c0 [ 45.779052][ T9787] ? __pfx_path_mount+0x10/0x10 [ 45.779480][ T9787] ? getname_flags.part.0+0x1c5/0x550 [ 45.779954][ T9787] ? putname+0x154/0x1a0 [ 45.780335][ T9787] __x64_sys_mount+0x27b/0x300 [ 45.780758][ T9787] ? __pfx___x64_sys_mount+0x10/0x10 [ 45.781232][ T9787] do_syscall_64+0xc9/0x480 [ 45.781631][ T9787] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 45.782149][ T9787] RIP: 0033:0x7ffb7265b6ca [ 45.782539][ T9787] Code: 48 8b 0d c9 17 0d 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 [ 45.784212][ T9787] RSP: 002b:00007ffc0c10cfb8 EFLAGS: 00000206 ORIG_RAX: 00000000000000a5 [ 45.784935][ T9787] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007ffb7265b6ca [ 45.785626][ T9787] RDX: 0000200000000240 RSI: 0000200000000280 RDI: 00007ffc0c10d100 [ 45.786316][ T9787] RBP: 00007ffc0c10d190 R08: 00007ffc0c10d000 R09: 0000000000000000 [ 45.787011][ T9787] R10: 0000000000000048 R11: 0000000000000206 R12: 0000560246733250 [ 45.787697][ T9787] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 [ 45.788393][ T9787] </TASK> [ 45.788665][ T9787] Modules linked in: [ 45.789058][ T9787] ---[ end trace 0000000000000000 ]--- [ 45.789554][ T9787] RIP: 0010:hfs_find_init+0x86/0x230 [ 45.790028][ T9787] Code: c1 ea 03 80 3c 02 00 0f 85 9a 01 00 00 4c 8d 6b 40 48 c7 45 18 00 00 00 00 48 b8 00 00 00 00 00 fc [ 45.792364][ T9787] RSP: 0018:ffffc90015157668 EFLAGS: 00010202 [ 45.793155][ T9787] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff819a4d09 [ 45.794123][ T9787] RDX: 0000000000000008 RSI: ffffffff819acd3a RDI: ffffc900151576e8 [ 45.795105][ T9787] RBP: ffffc900151576d0 R08: 0000000000000005 R09: 0000000000000000 [ 45.796135][ T9787] R10: 0000000080000000 R11: 0000000000000001 R12: 0000000000000004 [ 45.797114][ T9787] R13: 0000000000000040 R14: ffff88802c50814a R15: 0000000000000000 [ 45.798024][ T9787] FS: 00007ffb72734540(0000) GS:ffff8880cec64000(0000) knlGS:0000000000000000 [ 45.799019][ T9787] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 45.799822][ T9787] CR2: 00007f9bd8225000 CR3: 000000010979a000 CR4: 00000000000006f0 [ 45.800747][ T9787] Kernel panic - not syncing: Fatal exception The hfs_fill_super() calls hfs_mdb_get() method that tries to construct Extents Tree and Catalog Tree: HFS_SB(sb)->ext_tree = hfs_btree_open(sb, HFS_EXT_CNID, hfs_ext_keycmp); if (!HFS_SB(sb)->ext_tree) { pr_err("unable to open extent tree\n"); goto out; } HFS_SB(sb)->cat_tree = hfs_btree_open(sb, HFS_CAT_CNID, hfs_cat_keycmp); if (!HFS_SB(sb)->cat_tree) { pr_err("unable to open catalog tree\n"); goto out; } However, hfs_btree_open() calls read_mapping_page() that calls hfs_get_block(). And this method calls hfs_ext_read_extent(): static int hfs_ext_read_extent(struct inode *inode, u16 block) { struct hfs_find_data fd; int res; if (block >= HFS_I(inode)->cached_start && block < HFS_I(inode)->cached_start + HFS_I(inode)->cached_blocks) return 0; res = hfs_find_init(HFS_SB(inode->i_sb)->ext_tree, &fd); if (!res) { res = __hfs_ext_cache_extent(&fd, inode, block); hfs_find_exit(&fd); } return res; } The problem here that hfs_find_init() is trying to use HFS_SB(inode->i_sb)->ext_tree that is not initialized yet. It will be initailized when hfs_btree_open() finishes the execution. The patch adds checking of tree pointer in hfs_find_init() and it reworks the logic of hfs_btree_open() by reading the b-tree's header directly from the volume. The read_mapping_page() is exchanged on filemap_grab_folio() that grab the folio from mapping. Then, sb_bread() extracts the b-tree's header content and copy it into the folio. Reported-by: Wenzhi Wang <wenzhi.wang@uwaterloo.ca> 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/20250710213657.108285-1-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
241 lines
4.9 KiB
C
241 lines
4.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/hfs/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 "btree.h"
|
|
|
|
int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)
|
|
{
|
|
void *ptr;
|
|
|
|
if (!tree || !fd)
|
|
return -EINVAL;
|
|
|
|
fd->tree = tree;
|
|
fd->bnode = NULL;
|
|
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;
|
|
hfs_dbg(BNODE_REFS, "find_init: %d (%p)\n",
|
|
tree->cnid, __builtin_return_address(0));
|
|
switch (tree->cnid) {
|
|
case HFS_CAT_CNID:
|
|
mutex_lock_nested(&tree->tree_lock, CATALOG_BTREE_MUTEX);
|
|
break;
|
|
case HFS_EXT_CNID:
|
|
mutex_lock_nested(&tree->tree_lock, EXTENTS_BTREE_MUTEX);
|
|
break;
|
|
case HFS_ATTR_CNID:
|
|
mutex_lock_nested(&tree->tree_lock, ATTR_BTREE_MUTEX);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
int cmpval;
|
|
u16 off, len, keylen;
|
|
int rec;
|
|
int b, e;
|
|
int res;
|
|
|
|
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);
|
|
cmpval = bnode->tree->keycmp(fd->key, fd->search_key);
|
|
if (!cmpval) {
|
|
e = rec;
|
|
res = 0;
|
|
goto done;
|
|
}
|
|
if (cmpval < 0)
|
|
b = rec + 1;
|
|
else
|
|
e = rec - 1;
|
|
} 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)
|
|
{
|
|
struct hfs_btree *tree;
|
|
struct hfs_bnode *bnode;
|
|
u32 nidx, parent;
|
|
__be32 data;
|
|
int height, res;
|
|
|
|
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);
|
|
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);
|
|
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;
|
|
}
|