From nobody Sat Jul 25 19:26:40 2026 Received: from mail-pl1-f172.google.com (mail-pl1-f172.google.com [209.85.214.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5F0E443F4A7 for ; Tue, 14 Jul 2026 09:27:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021268; cv=none; b=tO1emPue8E2DPv1SF05OAXNbvHzZn7PuDhzo3jnUDVvUMyaPAPFA1PmDs6y+JJEJD0/5ND9lgAbY3AKXVbIhGoq+UNRjR5C5VEpUe6V4XKDmvuuP/TU3EViqSrygerE4HkkbiYJ9Y0VDIPLgaOtKhqg1/XGMmAbCnApeWHFDO+U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021268; c=relaxed/simple; bh=gM+LMbsXNxoFatR9xtTCX3IOzOl+ARpL2qPAr+sxBZA=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=HcGb1Hfq86LVxa75BJsyvtAvnsCYnhgo1OeRXDgAOLczoxNQ1U5/sNOESD/t3O/vn+tOfdint/R4mpR+WGezGhBFMQ1Z5xJJ40Tfaecrc9A1XC4u/YHcDKUrkoR3T/SFcghmWBbQEXXKX5skXthWjNE/0/lm1ecmHZSPfdmUWLQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=VmKTgVQa; arc=none smtp.client-ip=209.85.214.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="VmKTgVQa" Received: by mail-pl1-f172.google.com with SMTP id d9443c01a7336-2caf228a910so29909555ad.2 for ; Tue, 14 Jul 2026 02:27:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784021258; x=1784626058; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=+Rn93eU6b2qhqsXg43+dcgl5tQCYBCauMnNxDhXkhac=; b=VmKTgVQaZCQB7sjlxfvHo9RpdATrNNYSYPEiQW+RVBWfhxeDTmaB7SxmH3luUFv7F1 km2LQqre2QbjBGyg7KeOS6lZ49cRi70ylOGBct/GmyJO3E2BujXpjertnT/H9YAjQB4Z ouCg1kCPH8sU8KaCTGgqBTVERHMV5t50Xsj643W5JdfM1VDdcj5dSh/3kbBSUpyoF2Do tU1GOflOZpdmm144q+ptgjgHlxv77cz9MmcHNfQsoVrhn6Sl03XcVvU3rOXR3daMVI8F UHfvPUH2n4gRk4Dw0VBxSAvjbxzueqpQ2khG0XAsRdrt6RKECod86bk7tL3ta4YXke7f KoPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784021258; x=1784626058; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=+Rn93eU6b2qhqsXg43+dcgl5tQCYBCauMnNxDhXkhac=; b=GuyyncXd4tGC0FQdJs1HECp7uUwHz+2fgkRYKAMlfoM/7wxvY7mrcjfTo05X+7YJJd osHHWVqa1Lq0DwiIDS6IF70ZwknELo3TRaOnba3eB0TwKnLGaa6RoEihznGSMaGmwi2b iCqQUQSWTCWF0d+DYICupPfGUlUgEHklX86S+AwRbxcGznVczIToGVwAdgXQFGApC6PL mQsDg7bH2sh8DMO0468PGdtX9jq2b4tBSlolV7+wO3EzpwFOUVSwgAVEKdPYqac4d56L jIsLwIX+2PwAx/JyMR9pfhQAeSxaSA09nAvpgSUbuVQ77MHfJGH8gI/dmdVESYNBOmCp TUkw== X-Forwarded-Encrypted: i=1; AHgh+RrIigVjuX4k44l4JIXZCtGR8eeZfHK0AbmRSFZKVgtnuFOyJwsfoU+DfT8xeReQ+H7Oxmc/dXDLhxC34f0=@vger.kernel.org X-Gm-Message-State: AOJu0YzlQIRi0utca3VqdXJpLXYe6zhYii2vO0xs8hCrZXtNlH17SKw9 afxfOrkUFZIwr5YVOCeRM6OUtMNa8DQ8b9uQOcOQb11GhVfrSTGDWUzl X-Gm-Gg: AfdE7clF7KT6lS6RtjFN/1AaG6Igpyx2NSv8Np7BeB6IepLcktjmKYGd6UD0LMXJbW4 AplCQYCKn9YHAasXOShjtSET1p8xvHKFQLiCC4yItgxSPAov6cDLM1CMqEyF3gHiX9i3coJhkcL sIFeuXXqLO7j0tG0gCvo1s1kGW1HHUFSw/YrNTeHfXRN1g9TyY0BJGNRIIbrAV1HNiRghBKCQZK gERsDkM0P9p/RIGJ+OH4e6n97ZezhznoP8ORrma0PGJIH88x6zBhPUNz+2MYdXURIM8muTlHfrm sJtDXwHjvGb6IGJCmsHeDZUJtT2V0jdCAhotAeWULYvguZriP8xlbFrvThsqdGZYldozCcTACV3 z52/TRO74o9vuZaWCK+C1buRJLe3jdhIeYZuvnjVGfG1IDneLLTGOhJhuJXhdHk1sATweNuGscn aWbGpWR2oaePlzno45Yjg6Cqvq1pFVeaI3XDzCCP6n4xVMz3Glie+k3rAyjdJTZer070PyTrsMk BTot1aUGk6WaRg0lpCZfFLz/Hm8FIG7MuSmtyx+GwnldXrhl3iym5FWvlNfna80dvvTrFVTIeld bo1aPMDAEpwdTz0na7GxbEcjxM/2h+aaeZ8S X-Received: by 2002:a17:903:3503:b0:2ca:e0ff:e696 with SMTP id d9443c01a7336-2ce9e7a51b1mr131305665ad.9.1784021258208; Tue, 14 Jul 2026 02:27:38 -0700 (PDT) Received: from cs-1047136853211-default.asia-southeast1-a.c.z168d0f9edf9bc766-tp.internal (48.144.186.35.bc.googleusercontent.com. [35.186.144.48]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ce95e001b1sm57043645ad.66.2026.07.14.02.27.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jul 2026 02:27:37 -0700 (PDT) From: Aditya Prakash Srivastava To: slava@dubeyko.com Cc: glaubitz@physik.fu-berlin.de, frank.li@vivo.com, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Aditya Prakash Srivastava Subject: [PATCH v4] hfs: port HFS+ b-tree bitmap corruption check Date: Tue, 14 Jul 2026 09:27:15 +0000 Message-ID: <20260714092715.2758-1-aditya.ansh182@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In HFS+ filesystems, during b-tree open (hfs_btree_open()), the code verifies that the allocation map bit for the tree header (node 0) is set. If not, it indicates a corrupted map record/bitmap and mounts the volume as read-only (SB_RDONLY) to prevent further damage. HFS filesystems share the same b-tree structure but currently lack this corruption detection check. Port this check to HFS, aligning its implementation with HFS+ to maintain consistent b-tree logic across both filesystems: 1. Define struct hfs_bmap_ctx, and define HFS_TREE_HEAD and the relevant map record indices in include/linux/hfs_common.h. 2. Implement static hfs_bmap_get_map_page() in fs/hfs/btree.c, and port the necessary offset and length validation helpers to fs/hfs/btree.h as static inline functions with robust null pointer checks. 3. Implement static hfs_bmap_test_bit() in fs/hfs/btree.c to inspect the B-tree bitmap using the new get_map_page helper. 4. In hfs_btree_open(), retrieve the header node via hfs_bnode_find(), test its allocation bit with hfs_bmap_test_bit(), and release it using hfs_bnode_put(). Suggested-by: Viacheslav Dubeyko Link: https://lore.kernel.org/all/6a36101b.be22b350.2a3e9.0001.GAE@google.c= om/T/#r446d0fed2a2900bd805534bbcb799d86619ae2ea Signed-off-by: Aditya Prakash Srivastava --- fs/hfs/bnode.c | 42 ------------------- fs/hfs/btree.c | 86 ++++++++++++++++++++++++++++++++++++++ fs/hfs/btree.h | 40 ++++++++++++++++++ include/linux/hfs_common.h | 5 +++ 4 files changed, 131 insertions(+), 42 deletions(-) diff --git a/fs/hfs/bnode.c b/fs/hfs/bnode.c index da8e5342c91c..1b331108d9c0 100644 --- a/fs/hfs/bnode.c +++ b/fs/hfs/bnode.c @@ -15,48 +15,6 @@ =20 #include "btree.h" =20 -static inline -bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off) -{ - bool is_valid =3D 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, u3= 2 len) -{ - unsigned int node_size; - - if (!is_bnode_offset_valid(node, off)) - return 0; - - node_size =3D node->tree->node_size; - - if ((u64)off + len > node_size) { - u32 new_len =3D 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 2eb37a2f64e8..2c841221c45c 100644 --- a/fs/hfs/btree.c +++ b/fs/hfs/btree.c @@ -15,6 +15,8 @@ =20 #include "btree.h" =20 +static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx); + /* 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_key= cmp keycmp) { @@ -23,6 +25,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 +158,20 @@ struct hfs_btree *hfs_btree_open(struct super_block *s= b, u32 id, btree_keycmp ke kunmap_local(head); folio_unlock(folio); folio_put(folio); + + node =3D 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 =3D=3D HFS_EXT_CNID ? "extents" : "catalog", id); + pr_warn("Run fsck.hfs to repair.\n"); + sb->s_flags |=3D SB_RDONLY; + } + + hfs_bnode_put(node); + return tree; =20 fail_folio: @@ -356,6 +373,75 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tre= e) } } =20 +/* 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; +}; + +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 =3D=3D HFS_TREE_HEAD) { + if (node->type !=3D HFS_NODE_HEADER) { + pr_err("hfs: invalid btree header node\n"); + return ERR_PTR(-EIO); + } + rec_idx =3D HFS_BTREE_HDR_MAP_REC_INDEX; + } else { + if (node->type !=3D HFS_NODE_MAP) { + pr_err("hfs: invalid btree map node\n"); + return ERR_PTR(-EIO); + } + rec_idx =3D HFS_BTREE_MAP_NODE_REC_INDEX; + } + + ctx->len =3D 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 =3D check_and_correct_requested_length(node, off16, ctx->len); + + if (byte_offset >=3D ctx->len) + return ERR_PTR(-EINVAL); + + page_off =3D (u32)off16 + node->page_offset + byte_offset; + ctx->page_idx =3D page_off >> PAGE_SHIFT; + ctx->off =3D page_off & ~PAGE_MASK; + + return node->page[ctx->page_idx]; +} + +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 =3D hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE); + if (IS_ERR(page)) + return false; + + bmap =3D kmap_local_page(page); + byte =3D bmap[ctx.off]; + kunmap_local(bmap); + + mask =3D 1 << (7 - (node_bit_idx % BITS_PER_BYTE)); + return (byte & mask) !=3D 0; +} + void hfs_bmap_free(struct hfs_bnode *node) { struct hfs_btree *tree; diff --git a/fs/hfs/btree.h b/fs/hfs/btree.h index 99be858b2446..f8afa33f709a 100644 --- a/fs/hfs/btree.h +++ b/fs/hfs/btree.h @@ -129,3 +129,43 @@ extern int __hfs_brec_find(struct hfs_bnode *bnode, st= ruct 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 =3D 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 *nod= e, u32 off, u32 len) +{ + unsigned int node_size; + + if (!is_bnode_offset_valid(node, off)) + return 0; + + node_size =3D node->tree->node_size; + + if ((u64)off + len > node_size) { + u32 new_len =3D 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 45fb4c9ff9f5..8dc10e0bfa7e 100644 --- a/include/linux/hfs_common.h +++ b/include/linux/hfs_common.h @@ -510,6 +510,11 @@ struct hfs_btree_header_rec { b-tree but not in extents b-tree (hfsplus). */ =20 +/* HFS BTree misc info */ +#define HFS_TREE_HEAD 0 +#define HFS_BTREE_HDR_MAP_REC_INDEX 2 /* Map (bitmap) record in Header no= de */ +#define HFS_BTREE_MAP_NODE_REC_INDEX 0 /* Map record in Map Node */ + /* HFS+ BTree misc info */ #define HFSPLUS_TREE_HEAD 0 #define HFSPLUS_NODE_MXSZ 32768 --=20 2.47.3