From nobody Sat Jul 25 22:03:04 2026 Received: from mail-pj1-f44.google.com (mail-pj1-f44.google.com [209.85.216.44]) (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 CD6DA3B6C09 for ; Mon, 13 Jul 2026 10:21:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783938111; cv=none; b=QuoMjFU23yEyl+0ManwDZ2Erw1Ul8r4OM2xLpBt5O4+MJaTeVMOuq3hKmRv0G9ndlM8XrCesJOX+p8VD1et/UKZYFIUcH8dbjGuqffS/rEPs48jUw2sJpO8L1xPf8EW0nbXGn0o94y2o9VrIJvEJD3QXxszulCHqhW6EltvobSM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783938111; c=relaxed/simple; bh=ZmKqgPeSTg6Y9eSZa9MnL16DIwG+gwcAmrYCPXV3Qvg=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=BEEQRMRlXROwHY1MH8V8oLykNHrp8rC14jzFXrl8uu5JRlQ3F9X2XkZnvUWH3bNMjY5+mg2xcaldd6nQUpDfxQybYBwrwCYgcUNp5rzFD5v33JsYxG5rhNEpGDzHzd3kvA6XMZfyhQlukr43U8iq3uX1UbnXkdR839sTSdighB4= 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=NT/0noyH; arc=none smtp.client-ip=209.85.216.44 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="NT/0noyH" Received: by mail-pj1-f44.google.com with SMTP id 98e67ed59e1d1-38a0c7e841fso3429462a91.2 for ; Mon, 13 Jul 2026 03:21:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783938109; x=1784542909; 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=+xza/PD8UkMtS139Jgn1Zcjohy0qVMh1mY86TKWfZ54=; b=NT/0noyHArPyhAr0tPtR/G9L/Fg3uMvhCTLghis2CIQROqmMxTCJR0gVQLri8pxW2C 4jxoPaTll5zbTMxum1JkdpRSFd8aWj4yuB98goGAowX+0FtkEVsrIUOxri0Tw0D5xP6f +vHDE3ZtQnC4gdU84I7XfFpYGzfUO27Z1orR8gKQvgMkpfTyAJ3CTQuOgNrSxjgQMl6J /zSWrN6NdtzSrIKVhOfHXxnkP9jRi9aKHaIxms+c/3oIC7WQpwxARNKHwySZGxxV/GuR lv7v2IybKvXeh1gxA3PSgXrZYs1RQb//C3RnyeS0SJcIpqsAGaT7qR7prGGj0ZH0xvUb MjNA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783938109; x=1784542909; 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=+xza/PD8UkMtS139Jgn1Zcjohy0qVMh1mY86TKWfZ54=; b=JhSuziQpIPeiRTuAy8j6pftGDCYhApQEHUg2RAY0+tyNJ5ctZlO+IWWQIhnSGBIvwa Y21jimJbgmc2lIKD9Jj8LQwQJA7R7hOEo38JwbolQNj56LoCKApPYFFyUG/HSRaJcs17 Huh6CPSIhXl5yh6QqJFkWEM04PXiWUCnkOL/IMyYLc4dVNuDhPPwMjTkX3qGQ4kHw2Fh mlQT86FLdhUey/KPuaedzuBCKvR293PUpTMCzk9G/y0wFy3P94VdvbaCzP0PwEVLhcti eoGUzC+9zGq/Mi/xlq7Z/jnLpdWsJd62c+tnJdiWTcuB2ZQYWML5RceVOHVhRkEMkxES Xevg== X-Forwarded-Encrypted: i=1; AHgh+RrRsius/zUXwjHX0U7hwMo6uEQI83iYmpIfaRA7U/8l3+NNBACRgI8LdfAN8mpHt7qCrE1d4MQ3KtDZ3bU=@vger.kernel.org X-Gm-Message-State: AOJu0YxvvQi9aHz7NYOXXFsUqziJEFiIxxTKIlubROqP4JdewixO/RQR xkOqcK12ajUGXB8SdFIvwuts0O48QSzgNbqkobTssoYaZfy8dM05iMWa2lO8Ki+5WrAaDw== X-Gm-Gg: AfdE7cmOYWVwyRfhRCxy9Bgn/RyE4hOC1x7Cs+EFnAwooPEXTsa+wamCVlr8A6mmZ4H F+j8mNN9Wm6tx0kualOJ9ilwMAE/aGccZBrjm1UvFFPzbDryGuMb0Aflpoh858jUrerHuCzMu1/ 0H2A38NbRFv5fssjOqBytTmLcN0MKZCrt+mfxZ3e6+Q03vT6TWa/QH1C4wingYYwVu3IxvGlfNQ Xt1UDo4owoVX5nS7xPXNGj1+59d30XAVUyYCIeb4Pp7+6hLikDhEjZyiQqZ1YNu6VlQjW8sBjG8 ynX6VlOZ1CdU4dOBMLHzrhsi8YaGuYCfSiU0Y0yaIiqrb+Tr4sHHfwtQIjWjYjWNhtz/jQvdZVl MJbFSD2vNWEOsGqqergu4W91MnpkV+U2fgx10cf8c/W6IBQPn4HVgiGKOwqRKB5uUNjxgoy3b83 ZvSdGOakcDeyJkztk2cXHlyGJI1vzAF8EQx/XxKsb9F3RoJKQneIHevMG9/6nXBW8ivK41ik096 vvbyuGSeNy9aRhVQGCTvqKkSVunWtPX3jWRzvp/fko7X1KF/MzfW2NZjUZoFyVMqS4yA5++BE/0 LUAIXHZQ+cKLx72h6wsuWL1NljVx4xc= X-Received: by 2002:a17:90b:510f:b0:387:e0db:3faf with SMTP id 98e67ed59e1d1-38dc7793df1mr8286274a91.40.1783938108862; Mon, 13 Jul 2026 03:21:48 -0700 (PDT) Received: from cs-1047136853211-default.asia-southeast1-b.c.z168d0f9edf9bc766-tp.internal (54.78.87.34.bc.googleusercontent.com. [34.87.78.54]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-311747f72a6sm81273751eec.1.2026.07.13.03.21.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 13 Jul 2026 03:21:48 -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 v3] hfs: port HFS+ b-tree bitmap corruption check Date: Mon, 13 Jul 2026 10:21:28 +0000 Message-ID: <20260713102129.1657-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. Port hfs_bmap_get_map_page() 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 hfs_bmap_test_bit() 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 | 84 ++++++++++++++++++++++++++++++++++++++ fs/hfs/btree.h | 41 +++++++++++++++++++ include/linux/hfs_common.h | 5 +++ 4 files changed, 130 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..9f85f679fd47 100644 --- a/fs/hfs/btree.c +++ b/fs/hfs/btree.c @@ -23,6 +23,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 +156,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 +371,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]; +} + +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..7c08765144ff 100644 --- a/fs/hfs/btree.h +++ b/fs/hfs/btree.h @@ -93,6 +93,7 @@ extern void hfs_btree_write(struct hfs_btree *tree); extern int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes); extern struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree); extern void hfs_bmap_free(struct hfs_bnode *node); +extern bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx); =20 /* bnode.c */ extern void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32= len); @@ -129,3 +130,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