AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/16 13:16 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "HFS_FS"
  ],
  "FocusSymbols": [
    "hfs_btree_open",
    "hfs_bmap_alloc",
    "hfs_bmap_free",
    "hfs_bmap_get_map_page",
    "hfs_bmap_test_bit",
    "hfs_bmap_clear_bit"
  ],
  "Reasoning": "The patch introduces functional changes to the HFS filesystem's btree map handling logic. It adds new functions to safely get map pages, test bits, and clear bits, and modifies node allocation and freeing to use these new functions. These changes affect how the filesystem manages its internal btree structures, which is reachable via filesystem operations and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/07/16 13:16 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit cb2cd0781150eeb6aa4073e89cc5c00b1b2cd623\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 16 13:16:01 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/hfs/bnode.c b/fs/hfs/bnode.c\nindex da8e5342c91cc2..1b331108d9c01b 100644\n--- a/fs/hfs/bnode.c\n+++ b/fs/hfs/bnode.c\n@@ -15,48 +15,6 @@\n \n #include \"btree.h\"\n \n-static inline\n-bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)\n-{\n-\tbool is_valid = off \u003c node-\u003etree-\u003enode_size;\n-\n-\tif (!is_valid) {\n-\t\tpr_err(\"requested invalid offset: \"\n-\t\t       \"NODE: id %u, type %#x, height %u, \"\n-\t\t       \"node_size %u, offset %u\\n\",\n-\t\t       node-\u003ethis, node-\u003etype, node-\u003eheight,\n-\t\t       node-\u003etree-\u003enode_size, off);\n-\t}\n-\n-\treturn is_valid;\n-}\n-\n-static inline\n-u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)\n-{\n-\tunsigned int node_size;\n-\n-\tif (!is_bnode_offset_valid(node, off))\n-\t\treturn 0;\n-\n-\tnode_size = node-\u003etree-\u003enode_size;\n-\n-\tif ((u64)off + len \u003e node_size) {\n-\t\tu32 new_len = node_size - off;\n-\n-\t\tpr_err(\"requested length has been corrected: \"\n-\t\t       \"NODE: id %u, type %#x, height %u, \"\n-\t\t       \"node_size %u, offset %u, \"\n-\t\t       \"requested_len %u, corrected_len %u\\n\",\n-\t\t       node-\u003ethis, node-\u003etype, node-\u003eheight,\n-\t\t       node-\u003etree-\u003enode_size, off, len, new_len);\n-\n-\t\treturn new_len;\n-\t}\n-\n-\treturn len;\n-}\n-\n void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)\n {\n \tstruct page *page;\ndiff --git a/fs/hfs/btree.c b/fs/hfs/btree.c\nindex 2eb37a2f64e866..41b4e8fc94637d 100644\n--- a/fs/hfs/btree.c\n+++ b/fs/hfs/btree.c\n@@ -15,6 +15,121 @@\n \n #include \"btree.h\"\n \n+/* Context for iterating b-tree map pages\n+ * @page_idx: The index of the page within the b-node's page array\n+ * @off: The byte offset within the mapped page\n+ * @len: The remaining length of the map record\n+ */\n+struct hfs_bmap_ctx {\n+\tunsigned int page_idx;\n+\tunsigned int off;\n+\tu16 len;\n+};\n+\n+/*\n+ * Finds the specific page containing the requested byte offset within the map\n+ * record. Automatically handles the difference between header and map nodes.\n+ * Returns the struct page pointer, or an ERR_PTR on failure.\n+ * Note: The caller is responsible for mapping/unmapping the returned page.\n+ */\n+static struct page *hfs_bmap_get_map_page(struct hfs_bnode *node,\n+\t\t\t\t\t  struct hfs_bmap_ctx *ctx,\n+\t\t\t\t\t  u32 byte_offset)\n+{\n+\tu16 rec_idx, off16;\n+\tunsigned int page_off;\n+\n+\tif (node-\u003ethis == HFS_TREE_HEAD) {\n+\t\tif (node-\u003etype != HFS_NODE_HEADER) {\n+\t\t\tpr_err(\"hfs: invalid btree header node\\n\");\n+\t\t\treturn ERR_PTR(-EIO);\n+\t\t}\n+\t\trec_idx = HFS_BTREE_HDR_MAP_REC_INDEX;\n+\t} else {\n+\t\tif (node-\u003etype != HFS_NODE_MAP) {\n+\t\t\tpr_err(\"hfs: invalid btree map node\\n\");\n+\t\t\treturn ERR_PTR(-EIO);\n+\t\t}\n+\t\trec_idx = HFS_BTREE_MAP_NODE_REC_INDEX;\n+\t}\n+\n+\tctx-\u003elen = hfs_brec_lenoff(node, rec_idx, \u0026off16);\n+\tif (!ctx-\u003elen)\n+\t\treturn ERR_PTR(-ENOENT);\n+\n+\tif (!is_bnode_offset_valid(node, off16))\n+\t\treturn ERR_PTR(-EIO);\n+\n+\tctx-\u003elen = check_and_correct_requested_length(node, off16, ctx-\u003elen);\n+\n+\tif (byte_offset \u003e= ctx-\u003elen)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tpage_off = (u32)off16 + node-\u003epage_offset + byte_offset;\n+\tctx-\u003epage_idx = page_off \u003e\u003e PAGE_SHIFT;\n+\tctx-\u003eoff = page_off \u0026 ~PAGE_MASK;\n+\n+\treturn node-\u003epage[ctx-\u003epage_idx];\n+}\n+\n+/**\n+ * hfs_bmap_test_bit - test a bit in the b-tree map\n+ * @node: the b-tree node containing the map record\n+ * @node_bit_idx: the relative bit index within the node's map record\n+ *\n+ * Returns true if set, false if clear or on failure.\n+ */\n+static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx)\n+{\n+\tstruct hfs_bmap_ctx ctx;\n+\tstruct page *page;\n+\tu8 *bmap, byte, mask;\n+\n+\tpage = hfs_bmap_get_map_page(node, \u0026ctx, node_bit_idx / BITS_PER_BYTE);\n+\tif (IS_ERR(page))\n+\t\treturn false;\n+\n+\tbmap = kmap_local_page(page);\n+\tbyte = bmap[ctx.off];\n+\tkunmap_local(bmap);\n+\n+\tmask = 1 \u003c\u003c (7 - (node_bit_idx % BITS_PER_BYTE));\n+\treturn (byte \u0026 mask) != 0;\n+}\n+\n+/**\n+ * hfs_bmap_clear_bit - clear a bit in the b-tree map\n+ * @node: the b-tree node containing the map record\n+ * @node_bit_idx: the relative bit index within the node's map record\n+ *\n+ * Returns 0 on success, -EINVAL if already clear, or negative error code.\n+ */\n+static int hfs_bmap_clear_bit(struct hfs_bnode *node, u32 node_bit_idx)\n+{\n+\tstruct hfs_bmap_ctx ctx;\n+\tstruct page *page;\n+\tu8 *bmap, mask;\n+\n+\tpage = hfs_bmap_get_map_page(node, \u0026ctx, node_bit_idx / BITS_PER_BYTE);\n+\tif (IS_ERR(page))\n+\t\treturn PTR_ERR(page);\n+\n+\tbmap = kmap_local_page(page);\n+\n+\tmask = 1 \u003c\u003c (7 - (node_bit_idx % BITS_PER_BYTE));\n+\n+\tif (!(bmap[ctx.off] \u0026 mask)) {\n+\t\tkunmap_local(bmap);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tbmap[ctx.off] \u0026= ~mask;\n+\tset_page_dirty(page);\n+\tkunmap_local(bmap);\n+\n+\treturn 0;\n+}\n+\n /* Get a reference to a B*Tree and do some initial checks */\n struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp keycmp)\n {\n@@ -23,6 +138,7 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke\n \tstruct address_space *mapping;\n \tstruct folio *folio;\n \tstruct buffer_head *bh;\n+\tstruct hfs_bnode *node;\n \tunsigned int size;\n \tu16 dblock;\n \tsector_t start_block;\n@@ -155,6 +271,20 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke\n \tkunmap_local(head);\n \tfolio_unlock(folio);\n \tfolio_put(folio);\n+\n+\tnode = hfs_bnode_find(tree, HFS_TREE_HEAD);\n+\tif (IS_ERR(node))\n+\t\tgoto free_inode;\n+\n+\tif (!hfs_bmap_test_bit(node, HFS_TREE_HEAD)) {\n+\t\tpr_warn(\"(%s): %s (cnid 0x%x) bitmap corrupted, forcing rdonly\\n\",\n+\t\t\tsb-\u003es_id, id == HFS_EXT_CNID ? \"extents\" : \"catalog\", id);\n+\t\tpr_warn(\"Run fsck.hfs to repair.\\n\");\n+\t\tsb-\u003es_flags |= SB_RDONLY;\n+\t}\n+\n+\thfs_bnode_put(node);\n+\n \treturn tree;\n \n fail_folio:\n@@ -285,11 +415,9 @@ int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)\n struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)\n {\n \tstruct hfs_bnode *node, *next_node;\n-\tstruct page **pagep;\n+\tstruct hfs_bmap_ctx ctx;\n+\tstruct page *page;\n \tu32 nidx, idx;\n-\tunsigned off;\n-\tu16 off16;\n-\tu16 len;\n \tu8 *data, byte, m;\n \tint i, res;\n \n@@ -301,24 +429,26 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)\n \tnode = hfs_bnode_find(tree, nidx);\n \tif (IS_ERR(node))\n \t\treturn node;\n-\tlen = hfs_brec_lenoff(node, 2, \u0026off16);\n-\toff = off16;\n \n-\toff += node-\u003epage_offset;\n-\tpagep = node-\u003epage + (off \u003e\u003e PAGE_SHIFT);\n-\tdata = kmap_local_page(*pagep);\n-\toff \u0026= ~PAGE_MASK;\n+\tpage = hfs_bmap_get_map_page(node, \u0026ctx, 0);\n+\tif (IS_ERR(page)) {\n+\t\tres = PTR_ERR(page);\n+\t\thfs_bnode_put(node);\n+\t\treturn ERR_PTR(res);\n+\t}\n+\n+\tdata = kmap_local_page(page);\n \tidx = 0;\n \n \tfor (;;) {\n-\t\twhile (len) {\n-\t\t\tbyte = data[off];\n+\t\twhile (ctx.len) {\n+\t\t\tbyte = data[ctx.off];\n \t\t\tif (byte != 0xff) {\n \t\t\t\tfor (m = 0x80, i = 0; i \u003c 8; m \u003e\u003e= 1, i++) {\n \t\t\t\t\tif (!(byte \u0026 m)) {\n \t\t\t\t\t\tidx += i;\n-\t\t\t\t\t\tdata[off] |= m;\n-\t\t\t\t\t\tset_page_dirty(*pagep);\n+\t\t\t\t\t\tdata[ctx.off] |= m;\n+\t\t\t\t\t\tset_page_dirty(page);\n \t\t\t\t\t\tkunmap_local(data);\n \t\t\t\t\t\ttree-\u003efree_nodes--;\n \t\t\t\t\t\tmark_inode_dirty(tree-\u003einode);\n@@ -327,13 +457,14 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)\n \t\t\t\t\t}\n \t\t\t\t}\n \t\t\t}\n-\t\t\tif (++off \u003e= PAGE_SIZE) {\n+\t\t\tif (++ctx.off \u003e= PAGE_SIZE) {\n \t\t\t\tkunmap_local(data);\n-\t\t\t\tdata = kmap_local_page(*++pagep);\n-\t\t\t\toff = 0;\n+\t\t\t\tpage = node-\u003epage[++ctx.page_idx];\n+\t\t\t\tdata = kmap_local_page(page);\n+\t\t\t\tctx.off = 0;\n \t\t\t}\n \t\t\tidx += 8;\n-\t\t\tlen--;\n+\t\t\tctx.len--;\n \t\t}\n \t\tkunmap_local(data);\n \t\tnidx = node-\u003enext;\n@@ -347,22 +478,22 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)\n \t\t\treturn next_node;\n \t\tnode = next_node;\n \n-\t\tlen = hfs_brec_lenoff(node, 0, \u0026off16);\n-\t\toff = off16;\n-\t\toff += node-\u003epage_offset;\n-\t\tpagep = node-\u003epage + (off \u003e\u003e PAGE_SHIFT);\n-\t\tdata = kmap_local_page(*pagep);\n-\t\toff \u0026= ~PAGE_MASK;\n+\t\tpage = hfs_bmap_get_map_page(node, \u0026ctx, 0);\n+\t\tif (IS_ERR(page)) {\n+\t\t\tres = PTR_ERR(page);\n+\t\t\thfs_bnode_put(node);\n+\t\t\treturn ERR_PTR(res);\n+\t\t}\n+\t\tdata = kmap_local_page(page);\n \t}\n }\n \n void hfs_bmap_free(struct hfs_bnode *node)\n {\n \tstruct hfs_btree *tree;\n-\tstruct page *page;\n \tu16 off, len;\n \tu32 nidx;\n-\tu8 *data, byte, m;\n+\tint res;\n \n \thfs_dbg(\"node %u\\n\", node-\u003ethis);\n \ttree = node-\u003etree;\n@@ -396,23 +527,18 @@ void hfs_bmap_free(struct hfs_bnode *node)\n \t\t}\n \t\tlen = hfs_brec_lenoff(node, 0, \u0026off);\n \t}\n-\toff += node-\u003epage_offset + nidx / 8;\n-\tpage = node-\u003epage[off \u003e\u003e PAGE_SHIFT];\n-\tdata = kmap_local_page(page);\n-\toff \u0026= ~PAGE_MASK;\n-\tm = 1 \u003c\u003c (~nidx \u0026 7);\n-\tbyte = data[off];\n-\tif (!(byte \u0026 m)) {\n+\n+\tres = hfs_bmap_clear_bit(node, nidx);\n+\tif (res == -EINVAL) {\n \t\tpr_crit(\"trying to free free bnode %u(%d)\\n\",\n-\t\t\tnode-\u003ethis, node-\u003etype);\n-\t\tkunmap_local(data);\n-\t\thfs_bnode_put(node);\n-\t\treturn;\n+\t\t\tnidx, node-\u003etype);\n+\t} else if (res) {\n+\t\tpr_crit(\"fail to free bnode %u(%d)\\n\",\n+\t\t\tnidx, node-\u003etype);\n+\t} else {\n+\t\ttree-\u003efree_nodes++;\n+\t\tmark_inode_dirty(tree-\u003einode);\n \t}\n-\tdata[off] = byte \u0026 ~m;\n-\tset_page_dirty(page);\n-\tkunmap_local(data);\n+\n \thfs_bnode_put(node);\n-\ttree-\u003efree_nodes++;\n-\tmark_inode_dirty(tree-\u003einode);\n }\ndiff --git a/fs/hfs/btree.h b/fs/hfs/btree.h\nindex 99be858b244650..b4c3f2a3147100 100644\n--- a/fs/hfs/btree.h\n+++ b/fs/hfs/btree.h\n@@ -129,3 +129,43 @@ extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd);\n extern int hfs_brec_find(struct hfs_find_data *fd);\n extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);\n extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt);\n+\n+static inline bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)\n+{\n+\tbool is_valid;\n+\n+\tif (!node || !node-\u003etree)\n+\t\treturn false;\n+\n+\tis_valid = off \u003c node-\u003etree-\u003enode_size;\n+\n+\tif (!is_valid) {\n+\t\tpr_err(\"invalid offset: id %u, type %#x, h %u, sz %u, off %u\\n\",\n+\t\t       node-\u003ethis, node-\u003etype, node-\u003eheight,\n+\t\t       node-\u003etree-\u003enode_size, off);\n+\t}\n+\n+\treturn is_valid;\n+}\n+\n+static inline u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)\n+{\n+\tunsigned int node_size;\n+\n+\tif (!is_bnode_offset_valid(node, off))\n+\t\treturn 0;\n+\n+\tnode_size = node-\u003etree-\u003enode_size;\n+\n+\tif ((u64)off + len \u003e node_size) {\n+\t\tu32 new_len = node_size - off;\n+\n+\t\tpr_err(\"corrected len: id %u, type %#x, h %u, sz %u, off %u, len %u-\u003e%u\\n\",\n+\t\t       node-\u003ethis, node-\u003etype, node-\u003eheight,\n+\t\t       node_size, off, len, new_len);\n+\n+\t\treturn new_len;\n+\t}\n+\n+\treturn len;\n+}\ndiff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h\nindex 45fb4c9ff9f5ee..d6a615e74b26e2 100644\n--- a/include/linux/hfs_common.h\n+++ b/include/linux/hfs_common.h\n@@ -510,14 +510,21 @@ struct hfs_btree_header_rec {\n \t\t\t\t\t\t   b-tree but not in extents\n \t\t\t\t\t\t   b-tree (hfsplus). */\n \n+/* HFS BTree misc info */\n+#define HFS_TREE_HEAD\t\t\t\t0\n+#define HFS_BTREE_HDR_MAP_REC_INDEX\t\t2\t/* Map (bitmap) record in Header node */\n+#define HFS_BTREE_MAP_NODE_REC_INDEX\t\t0\t/* Map record in Map Node */\n+\n /* HFS+ BTree misc info */\n-#define HFSPLUS_TREE_HEAD\t\t\t0\n+#define HFSPLUS_TREE_HEAD\t\t\tHFS_TREE_HEAD\n #define HFSPLUS_NODE_MXSZ\t\t\t32768\n #define HFSPLUS_NODE_MINSZ\t\t\t512\n #define HFSPLUS_ATTR_TREE_NODE_SIZE\t\t8192\n #define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT\t3\n-#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX\t\t2\t/* Map (bitmap) record in Header node */\n-#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX\t0\t/* Map record in Map Node */\n+/* Map (bitmap) record in Header node */\n+#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX\t\tHFS_BTREE_HDR_MAP_REC_INDEX\n+/* Map record in Map Node */\n+#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX\tHFS_BTREE_MAP_NODE_REC_INDEX\n #define HFSPLUS_BTREE_HDR_USER_BYTES\t\t128\n #define HFSPLUS_BTREE_MAP_NODE_RECS_COUNT\t2\n #define HFSPLUS_BTREE_MAP_NODE_RESERVED_BYTES\t2\n"
}

2/1 2026/07/16 13:16 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "HFS_FS"
  ],
  "FocusSymbols": [
    "hfs_btree_open",
    "hfs_bmap_alloc",
    "hfs_bmap_free",
    "hfs_bmap_get_map_page",
    "hfs_bmap_test_bit",
    "hfs_bmap_clear_bit"
  ],
  "Reasoning": "The patch introduces functional changes to the HFS filesystem's btree map handling logic. It adds new functions to safely get map pages, test bits, and clear bits, and modifies node allocation and freeing to use these new functions. These changes affect how the filesystem manages its internal btree structures, which is reachable via filesystem operations and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit cb2cd0781150eeb6aa4073e89cc5c00b1b2cd623
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 16 13:16:01 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/hfs/bnode.c b/fs/hfs/bnode.c
index da8e5342c91cc2..1b331108d9c01b 100644
--- a/fs/hfs/bnode.c
+++ b/fs/hfs/bnode.c
@@ -15,48 +15,6 @@
 
 #include "btree.h"
 
-static inline
-bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
-{
-	bool is_valid = off < node->tree->node_size;
-
-	if (!is_valid) {
-		pr_err("requested invalid offset: "
-		       "NODE: id %u, type %#x, height %u, "
-		       "node_size %u, offset %u\n",
-		       node->this, node->type, node->height,
-		       node->tree->node_size, off);
-	}
-
-	return is_valid;
-}
-
-static inline
-u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
-{
-	unsigned int node_size;
-
-	if (!is_bnode_offset_valid(node, off))
-		return 0;
-
-	node_size = node->tree->node_size;
-
-	if ((u64)off + len > node_size) {
-		u32 new_len = node_size - off;
-
-		pr_err("requested length has been corrected: "
-		       "NODE: id %u, type %#x, height %u, "
-		       "node_size %u, offset %u, "
-		       "requested_len %u, corrected_len %u\n",
-		       node->this, node->type, node->height,
-		       node->tree->node_size, off, len, new_len);
-
-		return new_len;
-	}
-
-	return len;
-}
-
 void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 {
 	struct page *page;
diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c
index 2eb37a2f64e866..41b4e8fc94637d 100644
--- a/fs/hfs/btree.c
+++ b/fs/hfs/btree.c
@@ -15,6 +15,121 @@
 
 #include "btree.h"
 
+/* Context for iterating b-tree map pages
+ * @page_idx: The index of the page within the b-node's page array
+ * @off: The byte offset within the mapped page
+ * @len: The remaining length of the map record
+ */
+struct hfs_bmap_ctx {
+	unsigned int page_idx;
+	unsigned int off;
+	u16 len;
+};
+
+/*
+ * Finds the specific page containing the requested byte offset within the map
+ * record. Automatically handles the difference between header and map nodes.
+ * Returns the struct page pointer, or an ERR_PTR on failure.
+ * Note: The caller is responsible for mapping/unmapping the returned page.
+ */
+static struct page *hfs_bmap_get_map_page(struct hfs_bnode *node,
+					  struct hfs_bmap_ctx *ctx,
+					  u32 byte_offset)
+{
+	u16 rec_idx, off16;
+	unsigned int page_off;
+
+	if (node->this == HFS_TREE_HEAD) {
+		if (node->type != HFS_NODE_HEADER) {
+			pr_err("hfs: invalid btree header node\n");
+			return ERR_PTR(-EIO);
+		}
+		rec_idx = HFS_BTREE_HDR_MAP_REC_INDEX;
+	} else {
+		if (node->type != HFS_NODE_MAP) {
+			pr_err("hfs: invalid btree map node\n");
+			return ERR_PTR(-EIO);
+		}
+		rec_idx = HFS_BTREE_MAP_NODE_REC_INDEX;
+	}
+
+	ctx->len = hfs_brec_lenoff(node, rec_idx, &off16);
+	if (!ctx->len)
+		return ERR_PTR(-ENOENT);
+
+	if (!is_bnode_offset_valid(node, off16))
+		return ERR_PTR(-EIO);
+
+	ctx->len = check_and_correct_requested_length(node, off16, ctx->len);
+
+	if (byte_offset >= ctx->len)
+		return ERR_PTR(-EINVAL);
+
+	page_off = (u32)off16 + node->page_offset + byte_offset;
+	ctx->page_idx = page_off >> PAGE_SHIFT;
+	ctx->off = page_off & ~PAGE_MASK;
+
+	return node->page[ctx->page_idx];
+}
+
+/**
+ * hfs_bmap_test_bit - test a bit in the b-tree map
+ * @node: the b-tree node containing the map record
+ * @node_bit_idx: the relative bit index within the node's map record
+ *
+ * Returns true if set, false if clear or on failure.
+ */
+static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx)
+{
+	struct hfs_bmap_ctx ctx;
+	struct page *page;
+	u8 *bmap, byte, mask;
+
+	page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
+	if (IS_ERR(page))
+		return false;
+
+	bmap = kmap_local_page(page);
+	byte = bmap[ctx.off];
+	kunmap_local(bmap);
+
+	mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
+	return (byte & mask) != 0;
+}
+
+/**
+ * hfs_bmap_clear_bit - clear a bit in the b-tree map
+ * @node: the b-tree node containing the map record
+ * @node_bit_idx: the relative bit index within the node's map record
+ *
+ * Returns 0 on success, -EINVAL if already clear, or negative error code.
+ */
+static int hfs_bmap_clear_bit(struct hfs_bnode *node, u32 node_bit_idx)
+{
+	struct hfs_bmap_ctx ctx;
+	struct page *page;
+	u8 *bmap, mask;
+
+	page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
+	if (IS_ERR(page))
+		return PTR_ERR(page);
+
+	bmap = kmap_local_page(page);
+
+	mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
+
+	if (!(bmap[ctx.off] & mask)) {
+		kunmap_local(bmap);
+		return -EINVAL;
+	}
+
+	bmap[ctx.off] &= ~mask;
+	set_page_dirty(page);
+	kunmap_local(bmap);
+
+	return 0;
+}
+
 /* Get a reference to a B*Tree and do some initial checks */
 struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp keycmp)
 {
@@ -23,6 +138,7 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
 	struct address_space *mapping;
 	struct folio *folio;
 	struct buffer_head *bh;
+	struct hfs_bnode *node;
 	unsigned int size;
 	u16 dblock;
 	sector_t start_block;
@@ -155,6 +271,20 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
 	kunmap_local(head);
 	folio_unlock(folio);
 	folio_put(folio);
+
+	node = hfs_bnode_find(tree, HFS_TREE_HEAD);
+	if (IS_ERR(node))
+		goto free_inode;
+
+	if (!hfs_bmap_test_bit(node, HFS_TREE_HEAD)) {
+		pr_warn("(%s): %s (cnid 0x%x) bitmap corrupted, forcing rdonly\n",
+			sb->s_id, id == HFS_EXT_CNID ? "extents" : "catalog", id);
+		pr_warn("Run fsck.hfs to repair.\n");
+		sb->s_flags |= SB_RDONLY;
+	}
+
+	hfs_bnode_put(node);
+
 	return tree;
 
 fail_folio:
@@ -285,11 +415,9 @@ int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
 struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
 {
 	struct hfs_bnode *node, *next_node;
-	struct page **pagep;
+	struct hfs_bmap_ctx ctx;
+	struct page *page;
 	u32 nidx, idx;
-	unsigned off;
-	u16 off16;
-	u16 len;
 	u8 *data, byte, m;
 	int i, res;
 
@@ -301,24 +429,26 @@ 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);
-	off = off16;
 
-	off += node->page_offset;
-	pagep = node->page + (off >> PAGE_SHIFT);
-	data = kmap_local_page(*pagep);
-	off &= ~PAGE_MASK;
+	page = hfs_bmap_get_map_page(node, &ctx, 0);
+	if (IS_ERR(page)) {
+		res = PTR_ERR(page);
+		hfs_bnode_put(node);
+		return ERR_PTR(res);
+	}
+
+	data = kmap_local_page(page);
 	idx = 0;
 
 	for (;;) {
-		while (len) {
-			byte = data[off];
+		while (ctx.len) {
+			byte = data[ctx.off];
 			if (byte != 0xff) {
 				for (m = 0x80, i = 0; i < 8; m >>= 1, i++) {
 					if (!(byte & m)) {
 						idx += i;
-						data[off] |= m;
-						set_page_dirty(*pagep);
+						data[ctx.off] |= m;
+						set_page_dirty(page);
 						kunmap_local(data);
 						tree->free_nodes--;
 						mark_inode_dirty(tree->inode);
@@ -327,13 +457,14 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
 					}
 				}
 			}
-			if (++off >= PAGE_SIZE) {
+			if (++ctx.off >= PAGE_SIZE) {
 				kunmap_local(data);
-				data = kmap_local_page(*++pagep);
-				off = 0;
+				page = node->page[++ctx.page_idx];
+				data = kmap_local_page(page);
+				ctx.off = 0;
 			}
 			idx += 8;
-			len--;
+			ctx.len--;
 		}
 		kunmap_local(data);
 		nidx = node->next;
@@ -347,22 +478,22 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
 			return next_node;
 		node = next_node;
 
-		len = hfs_brec_lenoff(node, 0, &off16);
-		off = off16;
-		off += node->page_offset;
-		pagep = node->page + (off >> PAGE_SHIFT);
-		data = kmap_local_page(*pagep);
-		off &= ~PAGE_MASK;
+		page = hfs_bmap_get_map_page(node, &ctx, 0);
+		if (IS_ERR(page)) {
+			res = PTR_ERR(page);
+			hfs_bnode_put(node);
+			return ERR_PTR(res);
+		}
+		data = kmap_local_page(page);
 	}
 }
 
 void hfs_bmap_free(struct hfs_bnode *node)
 {
 	struct hfs_btree *tree;
-	struct page *page;
 	u16 off, len;
 	u32 nidx;
-	u8 *data, byte, m;
+	int res;
 
 	hfs_dbg("node %u\n", node->this);
 	tree = node->tree;
@@ -396,23 +527,18 @@ void hfs_bmap_free(struct hfs_bnode *node)
 		}
 		len = hfs_brec_lenoff(node, 0, &off);
 	}
-	off += node->page_offset + nidx / 8;
-	page = node->page[off >> PAGE_SHIFT];
-	data = kmap_local_page(page);
-	off &= ~PAGE_MASK;
-	m = 1 << (~nidx & 7);
-	byte = data[off];
-	if (!(byte & m)) {
+
+	res = hfs_bmap_clear_bit(node, nidx);
+	if (res == -EINVAL) {
 		pr_crit("trying to free free bnode %u(%d)\n",
-			node->this, node->type);
-		kunmap_local(data);
-		hfs_bnode_put(node);
-		return;
+			nidx, node->type);
+	} else if (res) {
+		pr_crit("fail to free bnode %u(%d)\n",
+			nidx, node->type);
+	} else {
+		tree->free_nodes++;
+		mark_inode_dirty(tree->inode);
 	}
-	data[off] = byte & ~m;
-	set_page_dirty(page);
-	kunmap_local(data);
+
 	hfs_bnode_put(node);
-	tree->free_nodes++;
-	mark_inode_dirty(tree->inode);
 }
diff --git a/fs/hfs/btree.h b/fs/hfs/btree.h
index 99be858b244650..b4c3f2a3147100 100644
--- a/fs/hfs/btree.h
+++ b/fs/hfs/btree.h
@@ -129,3 +129,43 @@ extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd);
 extern int hfs_brec_find(struct hfs_find_data *fd);
 extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
 extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
+
+static inline bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
+{
+	bool is_valid;
+
+	if (!node || !node->tree)
+		return false;
+
+	is_valid = off < node->tree->node_size;
+
+	if (!is_valid) {
+		pr_err("invalid offset: id %u, type %#x, h %u, sz %u, off %u\n",
+		       node->this, node->type, node->height,
+		       node->tree->node_size, off);
+	}
+
+	return is_valid;
+}
+
+static inline u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
+{
+	unsigned int node_size;
+
+	if (!is_bnode_offset_valid(node, off))
+		return 0;
+
+	node_size = node->tree->node_size;
+
+	if ((u64)off + len > node_size) {
+		u32 new_len = node_size - off;
+
+		pr_err("corrected len: id %u, type %#x, h %u, sz %u, off %u, len %u->%u\n",
+		       node->this, node->type, node->height,
+		       node_size, off, len, new_len);
+
+		return new_len;
+	}
+
+	return len;
+}
diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h
index 45fb4c9ff9f5ee..d6a615e74b26e2 100644
--- a/include/linux/hfs_common.h
+++ b/include/linux/hfs_common.h
@@ -510,14 +510,21 @@ struct hfs_btree_header_rec {
 						   b-tree but not in extents
 						   b-tree (hfsplus). */
 
+/* HFS BTree misc info */
+#define HFS_TREE_HEAD				0
+#define HFS_BTREE_HDR_MAP_REC_INDEX		2	/* Map (bitmap) record in Header node */
+#define HFS_BTREE_MAP_NODE_REC_INDEX		0	/* Map record in Map Node */
+
 /* HFS+ BTree misc info */
-#define HFSPLUS_TREE_HEAD			0
+#define HFSPLUS_TREE_HEAD			HFS_TREE_HEAD
 #define HFSPLUS_NODE_MXSZ			32768
 #define HFSPLUS_NODE_MINSZ			512
 #define HFSPLUS_ATTR_TREE_NODE_SIZE		8192
 #define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT	3
-#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX		2	/* Map (bitmap) record in Header node */
-#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX	0	/* Map record in Map Node */
+/* Map (bitmap) record in Header node */
+#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX		HFS_BTREE_HDR_MAP_REC_INDEX
+/* Map record in Map Node */
+#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX	HFS_BTREE_MAP_NODE_REC_INDEX
 #define HFSPLUS_BTREE_HDR_USER_BYTES		128
 #define HFSPLUS_BTREE_MAP_NODE_RECS_COUNT	2
 #define HFSPLUS_BTREE_MAP_NODE_RESERVED_BYTES	2

3/2 2026/07/16 13:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 6726
							output: 30
							thoughts: 348
						

Thoughts:
**My Assessment of the HFS Filesystem Driver Patch**

Okay, so I'm looking at this patch and it's making some pretty fundamental changes to the HFS filesystem driver. Specifically, it's touching core components: `fs/hfs/btree.c`, `fs/hfs/bnode.c`, `fs/hfs/btree.h`, and `include/linux/hfs_common.h`. This isn't just a minor tweak; we're talking about direct modifications to how the HFS driver interacts with its B-tree structure and bitmap management.

The patch introduces a new context structure, `struct hfs_bmap_ctx`, and a new function, `hfs_bmap_get_map_page`, which I immediately recognize as an attempt to safely manage memory access within the B-tree context. It also defines `hfs_bmap_test_bit` and `hfs_bmap_clear_bit`, which I can infer are bit manipulation functions specifically tailored for bitmap management, likely related to allocation and deallocation within the filesystem. Critically, existing functions like `hfs_btree_open`, `hfs_bmap_alloc`, and `hfs_bmap_free` are being updated to use these new functions. This means a significant modification to the existing control flow.

Given the scope of these changes and the critical nature of the touched code, this is definitely WorthFuzzing. It modifies reachable core kernel logic, specifically the HFS filesystem. My immediate instinct is that this patch introduces new potential vulnerabilities.

Now, let's identify the specific functions to target during fuzzing:

*   `hfs_bmap_get_map_page` (static)
*   `hfs_bmap_test_bit` (static)
*   `hfs_bmap_clear_bit` (static)
*   `hfs_btree_open`
*   `hfs_bmap_alloc`
*   `hfs_bmap_free`

I need to confirm these functions are reachable, and what kernel configurations are needed. I'm going to quickly check `fs/hfs/btree.c` and make sure it's linked in the build. The necessary config is `CONFIG_HFS_FS`. I'll verify all this using `grepper` as a final check, making sure I haven't missed anything.



4/2 2026/07/16 13:16 tool grepper
0m Args:
{
  "Expression": "config HFS_FS",
  "PathPrefix": "fs/hfs/"
}

Results:
{
  "Output": "fs/hfs/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/hfs/Kconfig:2:config HFS_FS\nfs/hfs/Kconfig-3-\ttristate \"Apple Macintosh file system support\"\n"
}

5/2 2026/07/16 13:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 7699
							output: 176
							thoughts: 504
						

Thoughts:
**Focusing on HFS Filesystem Fuzzing Targets**

Okay, so the config indicates we're dealing with `HFS_FS`. I need to zero in on potential fuzzing targets. Looking at the modified files, it's clear these changes are within the `fs/hfs` directory, specifically `bnode.c`, `btree.c`, `btree.h`, and `include/linux/hfs_common.h`. All of this confirms we're indeed focused on `HFS_FS`.

Now, I need to identify key functions. I see `hfs_btree_open`, `hfs_bmap_alloc`, and `hfs_bmap_free` have been touched. Let's see if these are hot-path functions, because that would make them ideal for fuzzing. `hfs_btree_open` is called during mount or when opening a btree – definitely a good candidate. `hfs_bmap_alloc` handles btree node allocation, and `hfs_bmap_free` does the opposite: releasing those nodes. Both are core operations, very good.

I need to confirm if these are indeed the right ones to focus on. I'll take a look at the static functions, like `hfs_bmap_get_map_page`, `hfs_bmap_test_bit`, and `hfs_bmap_clear_bit`. It's generally better to focus on the non-static, exported functions that call them, unless the statics are substantial in their own right. And it looks like these are static *and* have been modified. They are also new. I see `hfs_btree_open`, `hfs_bmap_alloc`, and `hfs_bmap_free` are also modified. So, all of these are candidates.

So, here's what I'll list:
- `hfs_btree_open`
- `hfs_bmap_alloc`
- `hfs_bmap_free`
- `hfs_bmap_get_map_page`
- `hfs_bmap_test_bit`
- `hfs_bmap_clear_bit`

Just to be absolutely sure, `hfs_bmap_get_map_page` should be in `fs/hfs/btree.c`. Yep, it's there. Alright, time to set the results.



6/2 2026/07/16 13:16 tool set-results
0m Args:
{
  "EnableConfigs": [
    "HFS_FS"
  ],
  "FocusSymbols": [
    "hfs_btree_open",
    "hfs_bmap_alloc",
    "hfs_bmap_free",
    "hfs_bmap_get_map_page",
    "hfs_bmap_test_bit",
    "hfs_bmap_clear_bit"
  ],
  "Reasoning": "The patch introduces functional changes to the HFS filesystem's btree map handling logic. It adds new functions to safely get map pages, test bits, and clear bits, and modifies node allocation and freeing to use these new functions. These changes affect how the filesystem manages its internal btree structures, which is reachable via filesystem operations and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "HFS_FS"
  ],
  "FocusSymbols": [
    "hfs_btree_open",
    "hfs_bmap_alloc",
    "hfs_bmap_free",
    "hfs_bmap_get_map_page",
    "hfs_bmap_test_bit",
    "hfs_bmap_clear_bit"
  ],
  "Reasoning": "The patch introduces functional changes to the HFS filesystem's btree map handling logic. It adds new functions to safely get map pages, test bits, and clear bits, and modifies node allocation and freeing to use these new functions. These changes affect how the filesystem manages its internal btree structures, which is reachable via filesystem operations and should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)