* [PATCH 0/2] hfs/hfsplus: harden btree node 0 bitmap corruption handling
@ 2026-08-14 2:31 Tao Yu
2026-08-14 2:31 ` [PATCH 1/2] hfs: detect node 0 btree map corruption at mount time Tao Yu
2026-08-14 2:31 ` [PATCH 2/2] hfs/hfsplus: stop btree allocators from reusing node 0 Tao Yu
0 siblings, 2 replies; 5+ messages in thread
From: Tao Yu @ 2026-08-14 2:31 UTC (permalink / raw)
To: linux-fsdevel
Cc: slava, glaubitz, frank.li, linux-kernel,
syzbot+2bf21610eea63cb2ce93, Tao Yu
Hi,
This series addresses btree bitmap corruption around node 0 in HFS and
HFS+.
Node 0 is the reserved btree header node. If the on-disk bitmap ever
presents node 0 as free, the filesystem is already corrupted and should
not continue normal allocation from that state.
The series does two things:
1. Port the existing HFS+ style mount-time node 0 bitmap validation to
HFS, so HFS detects the corrupted btree map earlier and forces the
filesystem read-only.
2. Harden the HFS and HFS+ btree allocators so that attempts to
allocate node 0 are treated as bitmap corruption. Instead of
continuing into the reserved header node and later tripping the
"new node 0 already hashed?" warning, the allocator now emits a
repair hint, forces the filesystem read-only, and aborts the
allocation.
This keeps the existing warning as a last-resort invariant check, while
moving the actual corruption handling to earlier and more appropriate
control points.
Patch 1 ports the mount-time validation to HFS.
Patch 2 adds the runtime allocator guard to both HFS and HFS+.
Comments are welcome.
Tao Yu (2):
hfs: detect node 0 btree map corruption at mount time
hfs/hfsplus: stop btree allocators from reusing node 0
fs/hfs/btree.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++
fs/hfsplus/btree.c | 11 +++++++
2 files changed, 82 insertions(+)
Thanks,
Tao
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 1/2] hfs: detect node 0 btree map corruption at mount time 2026-08-14 2:31 [PATCH 0/2] hfs/hfsplus: harden btree node 0 bitmap corruption handling Tao Yu @ 2026-08-14 2:31 ` Tao Yu 2026-08-14 4:10 ` Viacheslav Dubeyko 2026-08-14 2:31 ` [PATCH 2/2] hfs/hfsplus: stop btree allocators from reusing node 0 Tao Yu 1 sibling, 1 reply; 5+ messages in thread From: Tao Yu @ 2026-08-14 2:31 UTC (permalink / raw) To: linux-fsdevel Cc: slava, glaubitz, frank.li, linux-kernel, syzbot+2bf21610eea63cb2ce93, Tao Yu HFS+ already validates that the btree map keeps node 0 marked as in-use when the tree is opened. HFS lacks the same check, so a corrupted volume can proceed past mount with a broken node bitmap and only fail later in write paths. Port the node 0 bitmap validation to HFS. If the header node is not marked as allocated in the on-disk map, warn that the btree bitmap is corrupted and force the filesystem read-only so users can repair it with fsck.hfs. This keeps the HFS mount-time behavior aligned with HFS+ and catches the corruption closer to where it becomes observable. Signed-off-by: Tao Yu <tao1.yu@intel.com> --- fs/hfs/btree.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c index 2eb37a2f64e86..14114318ec724 100644 --- a/fs/hfs/btree.c +++ b/fs/hfs/btree.c @@ -15,12 +15,59 @@ #include "btree.h" +static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx) +{ + u16 rec_idx, off, len; + u32 byte_offset; + u8 byte, mask; + + if (node->this == 0) { + if (node->type != HFS_NODE_HEADER) { + pr_err("hfs: invalid btree header node\n"); + return false; + } + rec_idx = 2; + } else { + if (node->type != HFS_NODE_MAP) { + pr_err("hfs: invalid btree map node\n"); + return false; + } + rec_idx = 0; + } + + len = hfs_brec_lenoff(node, rec_idx, &off); + if (!len) + return false; + + byte_offset = node_bit_idx / BITS_PER_BYTE; + if (byte_offset >= len) + return false; + + byte = hfs_bnode_read_u8(node, off + byte_offset); + mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE)); + + return byte & mask; +} + +static const char *hfs_btree_name(u32 cnid) +{ + switch (cnid) { + case HFS_EXT_CNID: + return "Extents Overflow File"; + case HFS_CAT_CNID: + return "Catalog File"; + default: + return "Unknown B-tree"; + } +} + /* 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) { struct hfs_btree *tree; struct hfs_btree_header_rec *head; struct address_space *mapping; + struct hfs_bnode *node; struct folio *folio; struct buffer_head *bh; unsigned int size; @@ -155,6 +202,19 @@ 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, hfs_btree_name(id), id); + pr_warn("Run fsck.hfs to repair.\n"); + sb->s_flags |= SB_RDONLY; + } + + hfs_bnode_put(node); return tree; fail_folio: -- 2.34.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] hfs: detect node 0 btree map corruption at mount time 2026-08-14 2:31 ` [PATCH 1/2] hfs: detect node 0 btree map corruption at mount time Tao Yu @ 2026-08-14 4:10 ` Viacheslav Dubeyko 0 siblings, 0 replies; 5+ messages in thread From: Viacheslav Dubeyko @ 2026-08-14 4:10 UTC (permalink / raw) To: Tao Yu, linux-fsdevel Cc: glaubitz, frank.li, linux-kernel, syzbot+2bf21610eea63cb2ce93 On Fri, 2026-08-14 at 10:31 +0800, Tao Yu wrote: > HFS+ already validates that the btree map keeps node 0 marked as in- > use > when the tree is opened. HFS lacks the same check, so a corrupted > volume > can proceed past mount with a broken node bitmap and only fail later > in > write paths. > > Port the node 0 bitmap validation to HFS. If the header node is not > marked as allocated in the on-disk map, warn that the btree bitmap is > corrupted and force the filesystem read-only so users can repair it > with > fsck.hfs. > > This keeps the HFS mount-time behavior aligned with HFS+ and catches > the > corruption closer to where it becomes observable. > > Signed-off-by: Tao Yu <tao1.yu@intel.com> > --- > fs/hfs/btree.c | 60 > ++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 60 insertions(+) > > diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c > index 2eb37a2f64e86..14114318ec724 100644 > --- a/fs/hfs/btree.c > +++ b/fs/hfs/btree.c > @@ -15,12 +15,59 @@ > > #include "btree.h" > > +static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 > node_bit_idx) > +{ > + u16 rec_idx, off, len; > + u32 byte_offset; > + u8 byte, mask; > + > + if (node->this == 0) { > + if (node->type != HFS_NODE_HEADER) { > + pr_err("hfs: invalid btree header node\n"); > + return false; > + } > + rec_idx = 2; > + } else { > + if (node->type != HFS_NODE_MAP) { > + pr_err("hfs: invalid btree map node\n"); > + return false; > + } > + rec_idx = 0; > + } > + > + len = hfs_brec_lenoff(node, rec_idx, &off); > + if (!len) > + return false; > + > + byte_offset = node_bit_idx / BITS_PER_BYTE; > + if (byte_offset >= len) > + return false; > + > + byte = hfs_bnode_read_u8(node, off + byte_offset); > + mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE)); > + > + return byte & mask; > +} > + > +static const char *hfs_btree_name(u32 cnid) > +{ > + switch (cnid) { > + case HFS_EXT_CNID: > + return "Extents Overflow File"; > + case HFS_CAT_CNID: > + return "Catalog File"; > + default: > + return "Unknown B-tree"; > + } > +} > + > /* 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) > { > struct hfs_btree *tree; > struct hfs_btree_header_rec *head; > struct address_space *mapping; > + struct hfs_bnode *node; > struct folio *folio; > struct buffer_head *bh; > unsigned int size; > @@ -155,6 +202,19 @@ 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, hfs_btree_name(id), id); > + pr_warn("Run fsck.hfs to repair.\n"); > + sb->s_flags |= SB_RDONLY; > + } > + > + hfs_bnode_put(node); > return tree; > > fail_folio: Sorry, but this patch [1] already has implemented this functionality. And this patch is in HFS/HFS+ tree (for-next branch). Thanks, Slava. [1] https://lore.kernel.org/r/20260716074150.1660-1-aditya.ansh182@gmail.com ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/2] hfs/hfsplus: stop btree allocators from reusing node 0 2026-08-14 2:31 [PATCH 0/2] hfs/hfsplus: harden btree node 0 bitmap corruption handling Tao Yu 2026-08-14 2:31 ` [PATCH 1/2] hfs: detect node 0 btree map corruption at mount time Tao Yu @ 2026-08-14 2:31 ` Tao Yu 2026-08-14 4:19 ` Viacheslav Dubeyko 1 sibling, 1 reply; 5+ messages in thread From: Tao Yu @ 2026-08-14 2:31 UTC (permalink / raw) To: linux-fsdevel Cc: slava, glaubitz, frank.li, linux-kernel, syzbot+2bf21610eea63cb2ce93, Tao Yu The btree header node is permanently reserved as node 0. If the on-disk bitmap ever presents node 0 as free, the filesystem is already corrupted and the allocator must not try to instantiate it again. Both HFS and HFS+ currently keep scanning the bitmap, set the bit, and hand node 0 to hfs_bnode_create()/hfsplus_bnode_create(). HFS+ then hits the "new node 0 already hashed?" warning reported by syzbot, while HFS risks continuing after the same corruption pattern. Teach both allocators to treat attempts to allocate node 0 as btree map corruption. Force the filesystem read-only, emit the existing repair hint, and abort the allocation before the code reaches the hashed-node warning. Reported-by: syzbot+2bf21610eea63cb2ce93@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2bf21610eea63cb2ce93 Signed-off-by: Tao Yu <tao1.yu@intel.com> --- fs/hfs/btree.c | 11 +++++++++++ fs/hfsplus/btree.c | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c index 14114318ec724..9b0b7418ddbd5 100644 --- a/fs/hfs/btree.c +++ b/fs/hfs/btree.c @@ -376,6 +376,17 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) if (byte != 0xff) { for (m = 0x80, i = 0; i < 8; m >>= 1, i++) { if (!(byte & m)) { + if (unlikely(!(idx + i))) { + pr_warn("(%s): %s (cnid 0x%x) map record invalid or bitmap corruption detected, forcing read-only.\n", + tree->sb->s_id, + hfs_btree_name(tree->cnid), + tree->cnid); + pr_warn("Run fsck.hfs to repair.\n"); + tree->sb->s_flags |= SB_RDONLY; + kunmap_local(data); + hfs_bnode_put(node); + return ERR_PTR(-EIO); + } idx += i; data[off] |= m; set_page_dirty(*pagep); diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c index 394542a47e600..3ee92248b2409 100644 --- a/fs/hfsplus/btree.c +++ b/fs/hfsplus/btree.c @@ -561,6 +561,17 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) if (byte != 0xff) { for (m = 0x80, i = 0; i < 8; m >>= 1, i++) { if (!(byte & m)) { + if (unlikely(!(idx + i))) { + pr_warn("(%s): %s (cnid 0x%x) map record invalid or bitmap corruption detected, forcing read-only.\n", + tree->sb->s_id, + hfs_btree_name(tree->cnid), + tree->cnid); + pr_warn("Run fsck.hfsplus to repair.\n"); + tree->sb->s_flags |= SB_RDONLY; + kunmap_local(data); + hfs_bnode_put(node); + return ERR_PTR(-EIO); + } idx += i; data[ctx.off] |= m; set_page_dirty(page); -- 2.34.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 2/2] hfs/hfsplus: stop btree allocators from reusing node 0 2026-08-14 2:31 ` [PATCH 2/2] hfs/hfsplus: stop btree allocators from reusing node 0 Tao Yu @ 2026-08-14 4:19 ` Viacheslav Dubeyko 0 siblings, 0 replies; 5+ messages in thread From: Viacheslav Dubeyko @ 2026-08-14 4:19 UTC (permalink / raw) To: Tao Yu, linux-fsdevel Cc: glaubitz, frank.li, linux-kernel, syzbot+2bf21610eea63cb2ce93 On Fri, 2026-08-14 at 10:31 +0800, Tao Yu wrote: > The btree header node is permanently reserved as node 0. If the on- > disk > bitmap ever presents node 0 as free, the filesystem is already > corrupted and the allocator must not try to instantiate it again. > > Both HFS and HFS+ currently keep scanning the bitmap, set the bit, > and > hand node 0 to hfs_bnode_create()/hfsplus_bnode_create(). HFS+ then > hits the "new node 0 already hashed?" warning reported by syzbot, > while > HFS risks continuing after the same corruption pattern. > > Teach both allocators to treat attempts to allocate node 0 as btree > map > corruption. Force the filesystem read-only, emit the existing repair > hint, and abort the allocation before the code reaches the hashed- > node > warning. > > Reported-by: syzbot+2bf21610eea63cb2ce93@syzkaller.appspotmail.com > Closes: https://syzkaller.appspot.com/bug?extid=2bf21610eea63cb2ce93 > Signed-off-by: Tao Yu <tao1.yu@intel.com> > --- > fs/hfs/btree.c | 11 +++++++++++ > fs/hfsplus/btree.c | 11 +++++++++++ > 2 files changed, 22 insertions(+) > > diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c > index 14114318ec724..9b0b7418ddbd5 100644 > --- a/fs/hfs/btree.c > +++ b/fs/hfs/btree.c > @@ -376,6 +376,17 @@ struct hfs_bnode *hfs_bmap_alloc(struct > hfs_btree *tree) > if (byte != 0xff) { > for (m = 0x80, i = 0; i < 8; m >>= > 1, i++) { > if (!(byte & m)) { > + if (unlikely(!(idx + > i))) { > + pr_warn("(%s > ): %s (cnid 0x%x) map record invalid or bitmap corruption detected, > forcing read-only.\n", > + tree > ->sb->s_id, > + hfs_ > btree_name(tree->cnid), > + tree > ->cnid); > + pr_warn("Run > fsck.hfs to repair.\n"); > + tree->sb- > >s_flags |= SB_RDONLY; > + kunmap_local > (data); > + hfs_bnode_pu > t(node); > + return > ERR_PTR(-EIO); > + } > idx += i; > data[off] |= m; > set_page_dirty(*page > p); > diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c > index 394542a47e600..3ee92248b2409 100644 > --- a/fs/hfsplus/btree.c > +++ b/fs/hfsplus/btree.c > @@ -561,6 +561,17 @@ struct hfs_bnode *hfs_bmap_alloc(struct > hfs_btree *tree) > if (byte != 0xff) { > for (m = 0x80, i = 0; i < 8; m >>= > 1, i++) { > if (!(byte & m)) { > + if (unlikely(!(idx + > i))) { > + pr_warn("(%s > ): %s (cnid 0x%x) map record invalid or bitmap corruption detected, > forcing read-only.\n", > + tree > ->sb->s_id, > + hfs_ > btree_name(tree->cnid), > + tree > ->cnid); > + pr_warn("Run > fsck.hfsplus to repair.\n"); > + tree->sb- > >s_flags |= SB_RDONLY; > + kunmap_local > (data); > + hfs_bnode_pu > t(node); > + return > ERR_PTR(-EIO); > + } > idx += i; > data[ctx.off] |= m; > set_page_dirty(page) > ; We already have check [1] in hfs_btree_open(): 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, hfs_btree_name(id), id); pr_warn("Run fsck.hfsplus to repair.\n"); sb->s_flags |= SB_RDONLY; } The likewise check has been implemented by patch [2] for HFS. Thanks, Slava. [1] https://elixir.bootlin.com/linux/v7.2-rc6/source/fs/hfsplus/btree.c#L388 [2] https://lore.kernel.org/r/20260716074150.1660-1-aditya.ansh182@gmail.com ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-14 4:19 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-14 2:31 [PATCH 0/2] hfs/hfsplus: harden btree node 0 bitmap corruption handling Tao Yu 2026-08-14 2:31 ` [PATCH 1/2] hfs: detect node 0 btree map corruption at mount time Tao Yu 2026-08-14 4:10 ` Viacheslav Dubeyko 2026-08-14 2:31 ` [PATCH 2/2] hfs/hfsplus: stop btree allocators from reusing node 0 Tao Yu 2026-08-14 4:19 ` Viacheslav Dubeyko
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.