From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj1-f51.google.com (mail-pj1-f51.google.com [209.85.216.51]) (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 B2402352025 for ; Fri, 3 Jul 2026 05:16:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.51 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783055816; cv=none; b=DXI159wdV704Kkx0LDf565hY63EwgAKbQ0ZBkbtm4/L2iqTx2T/Rdb53rth24irPfbHQ2y1E783elKIAt8N7bBRYxbyhBAwlWbXS0opdTN+Kgj9Yl+uoWmYCIBpr5UmzFME6mz8mvv31Rylhz5/vUy/Xtc6xzSp+EO7B/E/o+cI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783055816; c=relaxed/simple; bh=YfHUT/51C/G8HybR7+5gnb3uFvfE+SYPBQ+X0BnNrqE=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=iN2yRYKEwUTV2MORSs9j0uFF1amYQ0mbvy8+6wcQid6O7Cugye2a57YN6GERzOXKCC83l72kayDzN9wypvXoCgmv/4a1mkSiGWjPjiCAYtEjnlcJpjYwr4Y/PZS3Epd6UgG+olnVrcS2KYxYYhuHsRvywg+XzusYNtrFNjvHYl4= 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=QXiCbL6N; arc=none smtp.client-ip=209.85.216.51 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="QXiCbL6N" Received: by mail-pj1-f51.google.com with SMTP id 98e67ed59e1d1-37fa06b39b4so161799a91.2 for ; Thu, 02 Jul 2026 22:16:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783055802; x=1783660602; 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=/g5oqTclMT+T1wVMz3AxcVhkZS5DAHk35im4ms5RkQY=; b=QXiCbL6Ntu7Fo1rw7bY0WUTfnJ+/xuSy75jOMdMTS4gGup6LQSBrLhu8nDNeGqQ3r/ L0nJMXEf8RYfUzJOSSn9CBCntXRmOorzJGykEkyVsDPLqluXPruWY30e92Q9dHalpnE0 ncc7XjnqVGLggNscvRK/83ZQwZyWA7TGRS8Ppg5v2IbouidKu+E8q7nzS0x+RJFWEy+t Si25FV2ftBFuAet9Om7Io+XjAcLUoWBUWMIQ6ktY0mFw/Li/9k3Vwi8taWnAn7PddRd1 xpEQdisBIUiiUSHt4tslWn7QjkruK3OEXWcL0HbcQvjpj7AumrTjs9pjmw8/RjxBwm/P Hu7g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783055802; x=1783660602; 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=/g5oqTclMT+T1wVMz3AxcVhkZS5DAHk35im4ms5RkQY=; b=Imfra+WfUxQeq3Vq+1DE2b89MxQVUwj5OIdnbS2r4ZqGAuD/s/gGIICl0EiD2XKByv rAjOXgEn+a4tjvmDxZE0r0VztAUp/veQ2GNtgdyAsFpnoX/JYlGp7Z6K7/BIbDe4LzEq NU08s4YhX2p0LPay1Bexm8J75+KCu4HvhaGqnFHUZ2jR93LlJTHW6iX6b80PFIg6n8pD amCIPNfZiWDOf3tOBoPuCGz7P1F9zKj3UwKZ/qx8e1NZTizqpV4QlZRLztBaxTtGHkhe vVoLxpNgOcDfS57EAhZG5QULaGgy2tlo75/9sIjSuGlfiQ/izZ9AriXSOfeAAdykWrI1 SvjA== X-Forwarded-Encrypted: i=1; AHgh+Rryj71Ul0NOeTq0RKhrm1qkINPq8ypo9Ka/0F9FomSNl2TbJn+uh8/yDhCZ3iJcJFEfp00uH7Y0lwVF+ak=@vger.kernel.org X-Gm-Message-State: AOJu0YzidGXrzw3m6Er+dsyzDuAWZPB/zGvyFqsS0ZAXIrQHByfzSoo0 M1M/SRjX+BiThsK2zj5bbFeYp7HiBF6ZnQuNPeWkdt7XbCo3rmgaak6JQw/fbI5J3gNoBg== X-Gm-Gg: AfdE7clRN/yAXpqHTBisG2Lyxz+V+F3Fs5xYOypx+EbMZb9NoBtFWeyyWIF/7L9lsYh CLOmpVfe9IXlTpaJ8IWW3EPGGduDc5zy2Q9sphVql9VyIxXB2CSWrfSkI9C5dcLup95HkwPYVP3 hSg9Ib2Yoi1I4ABDoOTffuVm+Lq0QzzLFCgJUgjkkXhVVQhv9CV/MfsI29UOmyoaTdE1ekwKdXg 9ycMk1xZT8/I3BaQ5t3mIGUYsAxhvLk5vNW7A86lHI9/LhZYE4rvOOsSk32rE5bbjBsUE7IgsBg oc+3zq+bX/efLz8FykgDsUnfzRpaVix5Yc6fvEBIT7YJUB9eG4yzo9/5hU+DjJVG30siS7qmw5u 7GU0jFM4LiZb2Qmcw6jdgUDFvufWOlPRJszN77cNOFfQo0+AfnqKYAhDi7Q9opUc2RJwB3+1BUf FrNqN8BExn1vZWEbRJoomJFqTL9yUBjMTL+m0nWiinrH2ItnpLw250zQfPeSyq1zgAqe/vbxZaL xtzLZVzPF656MmMmBhAsfdD1yqb5wePqt+6IAYwH4EN/6bYu/auxcgDgacOgAY6IbzrirsP7pos Xz7OSYyBQFCfwRhWG22yFXJWJpwZEs1TRg== X-Received: by 2002:a17:90b:3cce:b0:380:9ef6:e8cc with SMTP id 98e67ed59e1d1-380aa07e1f3mr9139156a91.5.1783055802297; Thu, 02 Jul 2026 22:16:42 -0700 (PDT) Received: from cs-1047136853211-default.asia-southeast1-a.c.d33bddc1d573818c7-tp.internal (213.28.87.34.bc.googleusercontent.com. [34.87.28.213]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38127cf2ba7sm412485a91.16.2026.07.02.22.16.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 22:16:41 -0700 (PDT) From: Aditya 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 v2] hfs: port HFS+ b-tree bitmap corruption check Date: Fri, 3 Jul 2026 05:16:17 +0000 Message-ID: <20260703051617.1832-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: 8bit From: Aditya Prakash Srivastava 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 the relevant map record indices. 2. Port hfs_bmap_get_map_page() and the necessary offset and length validation helpers. 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.com/T/#r446d0fed2a2900bd805534bbcb799d86619ae2ea Signed-off-by: Aditya Prakash Srivastava --- Changes in v2: - Port struct hfs_bmap_ctx and hfs_bmap_get_map_page() helpers to fs/hfs/btree.c, keeping HFS and HFS+ logic unified and easy to library-ize. - Re-implement hfs_bmap_test_bit() to take struct hfs_bnode and node_bit_idx, matching HFS+ perfectly. - In hfs_btree_open(), use hfs_bnode_find() to retrieve the header node and put it safely after the bitwise verification check. fs/hfs/btree.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ fs/hfs/btree.h | 2 ++ 2 files changed, 98 insertions(+) diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c index 2eb37a2f64e8..36dc8735d01a 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 *sb, u32 id, btree_keycmp ke kunmap_local(head); folio_unlock(folio); folio_put(folio); + + node = hfs_bnode_find(tree, 0); + if (IS_ERR(node)) + goto free_inode; + + if (!hfs_bmap_test_bit(node, 0)) { + pr_warn("(%s): %s (cnid 0x%x) map record invalid or bitmap corruption detected, forcing read-only.\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: @@ -356,6 +371,87 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) } } +struct hfs_bmap_ctx { + unsigned int page_idx; + unsigned int off; + u16 len; +}; + +#define HFS_BTREE_HDR_MAP_REC_INDEX 2 +#define HFS_BTREE_MAP_NODE_REC_INDEX 0 + +static inline bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off) +{ + return off < node->tree->node_size; +} + +static inline u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len) +{ + if (off >= node->tree->node_size) + return 0; + if ((u64)off + len > node->tree->node_size) + return node->tree->node_size - off; + return 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 == 0) { + 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]; +} + +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; +} + 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..86036e18095f 100644 --- a/fs/hfs/btree.h +++ b/fs/hfs/btree.h @@ -93,6 +93,8 @@ 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); +struct hfs_bmap_ctx; +extern bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx); /* bnode.c */ extern void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len); -- 2.47.3