From: Viacheslav Dubeyko <slava@dubeyko.com>
To: glaubitz@physik.fu-berlin.de, linux-fsdevel@vger.kernel.org,
frank.li@vivo.com
Cc: samsun1006219@gmail.com, Viacheslav Dubeyko <slava@dubeyko.com>
Subject: [PATCH] hfs: rework MDB locking scheme
Date: Mon, 20 Jul 2026 11:44:15 -0700 [thread overview]
Message-ID: <20260720184414.195213-2-slava@dubeyko.com> (raw)
hfs_mdb_commit() used to hold the primary MDB buffer_head (mdb_bh)
locked for the whole commit, including writing the alternate MDB and
the volume bitmap. A corrupted image can set drVBMSt so the bitmap
block aliases mdb_bh. The bitmap writeback path then calls
lock_buffer() on that same buffer_head while it is already locked by
hfs_mdb_commit(). Finally, we have a deadlock during flushing the MDB
to the file system volume. However, even for valid images, the locking
scheme of holding the mdb_bh locked across this much unrelated I/O is
fragile anyway.
This patch adds a dedicated sbi->mdb_lock and take it around every
hfs_mdb_commit() caller (hfs_sync_fs(), flush_mdb(), and the initial
hfs_mdb_get() in hfs_fill_super()).
Additionally, this patch makes sbi->mdb/sbi->alt_mdb independent
in-memory copies (allocated with kmemdup()) instead of pointers into
mdb_bh's/alt_mdb_bh's page cache data. Every MDB field read or write in
hfs_mdb_commit(), hfs_mdb_close() and hfs_mdb_get() now operates on
these copies. Also, hfs_mdb_publish() and hfs_alt_mdb_publish() are
the only places left that touch the buffer_heads. The hfs_mdb_put()
frees the two copies.
Reported-by: Yue Sun <samsun1006219@gmail.com>
Link: https://lore.kernel.org/all/CAEkJfYMB47v1yOWHB8q2dc8kf=uj-rLO=+yMyudwPguJ8Kd3jA@mail.gmail.com/
Signed-off-by: Yue Sun <samsun1006219@gmail.com>
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
---
fs/hfs/hfs_fs.h | 12 +++++--
fs/hfs/mdb.c | 84 ++++++++++++++++++++++++++++++++++++++-----------
fs/hfs/super.c | 14 +++++++--
3 files changed, 87 insertions(+), 23 deletions(-)
diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h
index 3df23f1f091b..e250f87a5e33 100644
--- a/fs/hfs/hfs_fs.h
+++ b/fs/hfs/hfs_fs.h
@@ -64,14 +64,22 @@ struct hfs_inode_info {
* The HFS-specific part of a Linux (struct super_block)
*/
struct hfs_sb_info {
+ struct mutex mdb_lock; /* MDB operations lock */
struct buffer_head *mdb_bh; /* The hfs_buffer
holding the real
superblock (aka VIB
or MDB) */
- struct hfs_mdb *mdb;
+ unsigned int mdb_offset; /* byte offset of the MDB
+ sector within mdb_bh's
+ data */
+ struct hfs_mdb *mdb; /* in-memory copy of the MDB */
struct buffer_head *alt_mdb_bh; /* The hfs_buffer holding
the alternate superblock */
- struct hfs_mdb *alt_mdb;
+ unsigned int alt_mdb_offset; /* byte offset of the alternate
+ MDB sector within
+ alt_mdb_bh's data */
+ struct hfs_mdb *alt_mdb; /* in-memory copy of the
+ alternate MDB */
__be32 *bitmap; /* The page holding the
allocation bitmap */
struct hfs_btree *ext_tree; /* Information about
diff --git a/fs/hfs/mdb.c b/fs/hfs/mdb.c
index f4578f634302..277de712f9d4 100644
--- a/fs/hfs/mdb.c
+++ b/fs/hfs/mdb.c
@@ -85,6 +85,39 @@ bool is_hfs_cnid_counts_valid(struct super_block *sb)
return !corrupted;
}
+/*
+ * hfs_sect_offset() - get byte offset within the buffer_head.
+ */
+static unsigned int hfs_sect_offset(struct super_block *sb, sector_t sec)
+{
+ loff_t start = (loff_t)sec << HFS_SECTOR_SIZE_BITS;
+
+ return start & (sb->s_blocksize - 1);
+}
+
+/*
+ * hfs_mdb_publish() - copy the in-memory primary MDB to the on-disk buffer.
+ */
+static void hfs_mdb_publish(struct hfs_sb_info *sbi)
+{
+ lock_buffer(sbi->mdb_bh);
+ memcpy(sbi->mdb_bh->b_data + sbi->mdb_offset, sbi->mdb, HFS_SECTOR_SIZE);
+ mark_buffer_dirty(sbi->mdb_bh);
+ unlock_buffer(sbi->mdb_bh);
+}
+
+/*
+ * hfs_alt_mdb_publish() - copy the in-memory alternate MDB to its buffer.
+ */
+static void hfs_alt_mdb_publish(struct hfs_sb_info *sbi)
+{
+ lock_buffer(sbi->alt_mdb_bh);
+ memcpy(sbi->alt_mdb_bh->b_data + sbi->alt_mdb_offset, sbi->alt_mdb,
+ HFS_SECTOR_SIZE);
+ mark_buffer_dirty(sbi->alt_mdb_bh);
+ unlock_buffer(sbi->alt_mdb_bh);
+}
+
/*
* hfs_mdb_get()
*
@@ -94,7 +127,7 @@ bool is_hfs_cnid_counts_valid(struct super_block *sb)
int hfs_mdb_get(struct super_block *sb)
{
struct buffer_head *bh;
- struct hfs_mdb *mdb, *mdb2;
+ struct hfs_mdb *mdb, *alt_mdb;
unsigned int block;
char *ptr;
int off2, len, size, sect;
@@ -158,7 +191,14 @@ int hfs_mdb_get(struct super_block *sb)
return -EIO;
}
+ mdb = kmemdup(mdb, HFS_SECTOR_SIZE, GFP_KERNEL);
+ if (!mdb) {
+ brelse(bh);
+ return -ENOMEM;
+ }
+
HFS_SB(sb)->mdb_bh = bh;
+ HFS_SB(sb)->mdb_offset = hfs_sect_offset(sb, part_start + HFS_MDB_BLK);
HFS_SB(sb)->mdb = mdb;
/* These parameters are read from the MDB, and never written */
@@ -187,11 +227,18 @@ int hfs_mdb_get(struct super_block *sb)
/* TRY to get the alternate (backup) MDB. */
sect = part_start + part_size - 2;
- bh = sb_bread512(sb, sect, mdb2);
+ bh = sb_bread512(sb, sect, alt_mdb);
if (bh) {
- if (mdb2->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC)) {
- HFS_SB(sb)->alt_mdb_bh = bh;
- HFS_SB(sb)->alt_mdb = mdb2;
+ if (alt_mdb->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC)) {
+ alt_mdb = kmemdup(alt_mdb, HFS_SECTOR_SIZE, GFP_KERNEL);
+ if (alt_mdb) {
+ HFS_SB(sb)->alt_mdb_bh = bh;
+ HFS_SB(sb)->alt_mdb_offset =
+ hfs_sect_offset(sb, sect);
+ HFS_SB(sb)->alt_mdb = alt_mdb;
+ } else {
+ brelse(bh);
+ }
} else
brelse(bh);
}
@@ -253,7 +300,7 @@ int hfs_mdb_get(struct super_block *sb)
be32_add_cpu(&mdb->drWrCnt, 1);
mdb->drLsMod = hfs_mtime();
- mark_buffer_dirty(HFS_SB(sb)->mdb_bh);
+ hfs_mdb_publish(HFS_SB(sb));
sync_dirty_buffer(HFS_SB(sb)->mdb_bh);
}
@@ -300,7 +347,6 @@ int hfs_mdb_commit(struct super_block *sb)
return -EIO;
}
- lock_buffer(HFS_SB(sb)->mdb_bh);
if (test_and_clear_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags)) {
/* These parameters may have been modified, so write them back */
mdb->drLsMod = hfs_mtime();
@@ -314,8 +360,14 @@ int hfs_mdb_commit(struct super_block *sb)
mdb->drDirCnt =
cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->folder_count));
+ hfs_inode_write_fork(HFS_SB(sb)->ext_tree->inode, mdb->drXTExtRec,
+ &mdb->drXTFlSize, NULL);
+ hfs_inode_write_fork(HFS_SB(sb)->cat_tree->inode, mdb->drCTExtRec,
+ &mdb->drCTFlSize, NULL);
+
/* write MDB to disk */
- mark_buffer_dirty(HFS_SB(sb)->mdb_bh);
+ hfs_mdb_publish(HFS_SB(sb));
+ sync_dirty_buffer(HFS_SB(sb)->mdb_bh);
}
/* write the backup MDB, not returning until it is written.
@@ -330,18 +382,11 @@ int hfs_mdb_commit(struct super_block *sb)
goto out;
}
- hfs_inode_write_fork(HFS_SB(sb)->ext_tree->inode, mdb->drXTExtRec,
- &mdb->drXTFlSize, NULL);
- hfs_inode_write_fork(HFS_SB(sb)->cat_tree->inode, mdb->drCTExtRec,
- &mdb->drCTFlSize, NULL);
-
- lock_buffer(HFS_SB(sb)->alt_mdb_bh);
- memcpy(HFS_SB(sb)->alt_mdb, HFS_SB(sb)->mdb, HFS_SECTOR_SIZE);
+ memcpy(HFS_SB(sb)->alt_mdb, mdb, HFS_SECTOR_SIZE);
HFS_SB(sb)->alt_mdb->drAtrb |= cpu_to_be16(HFS_SB_ATTRIB_UNMNT);
HFS_SB(sb)->alt_mdb->drAtrb &= cpu_to_be16(~HFS_SB_ATTRIB_INCNSTNT);
- unlock_buffer(HFS_SB(sb)->alt_mdb_bh);
- mark_buffer_dirty(HFS_SB(sb)->alt_mdb_bh);
+ hfs_alt_mdb_publish(HFS_SB(sb));
sync_dirty_buffer(HFS_SB(sb)->alt_mdb_bh);
}
@@ -377,7 +422,6 @@ int hfs_mdb_commit(struct super_block *sb)
}
}
out:
- unlock_buffer(HFS_SB(sb)->mdb_bh);
return ret;
}
@@ -392,7 +436,7 @@ void hfs_mdb_close(struct super_block *sb)
HFS_SB(sb)->mdb->drAtrb |= cpu_to_be16(HFS_SB_ATTRIB_UNMNT);
HFS_SB(sb)->mdb->drAtrb &= cpu_to_be16(~HFS_SB_ATTRIB_INCNSTNT);
- mark_buffer_dirty(HFS_SB(sb)->mdb_bh);
+ hfs_mdb_publish(HFS_SB(sb));
}
/*
@@ -408,6 +452,8 @@ void hfs_mdb_put(struct super_block *sb)
/* free the buffers holding the primary and alternate MDBs */
brelse(HFS_SB(sb)->mdb_bh);
brelse(HFS_SB(sb)->alt_mdb_bh);
+ kfree(HFS_SB(sb)->mdb);
+ kfree(HFS_SB(sb)->alt_mdb);
unload_nls(HFS_SB(sb)->nls_io);
unload_nls(HFS_SB(sb)->nls_disk);
diff --git a/fs/hfs/super.c b/fs/hfs/super.c
index f203e3240fae..0713969b8af2 100644
--- a/fs/hfs/super.c
+++ b/fs/hfs/super.c
@@ -34,8 +34,14 @@ MODULE_LICENSE("GPL");
static int hfs_sync_fs(struct super_block *sb, int wait)
{
+ int ret;
+
+ mutex_lock(&HFS_SB(sb)->mdb_lock);
is_hfs_cnid_counts_valid(sb);
- return hfs_mdb_commit(sb);
+ ret = hfs_mdb_commit(sb);
+ mutex_unlock(&HFS_SB(sb)->mdb_lock);
+
+ return ret;
}
/*
@@ -65,9 +71,10 @@ static void flush_mdb(struct work_struct *work)
sbi->work_queued = 0;
spin_unlock(&sbi->work_lock);
+ mutex_lock(&sbi->mdb_lock);
is_hfs_cnid_counts_valid(sb);
-
hfs_mdb_commit(sb);
+ mutex_unlock(&sbi->mdb_lock);
}
void hfs_mark_mdb_dirty(struct super_block *sb)
@@ -338,9 +345,12 @@ static int hfs_fill_super(struct super_block *sb, struct fs_context *fc)
sb->s_op = &hfs_super_operations;
sb->s_xattr = hfs_xattr_handlers;
sb->s_flags |= SB_NOATIME | SB_NODIRATIME;
+ mutex_init(&sbi->mdb_lock);
mutex_init(&sbi->bitmap_lock);
+ mutex_lock(&sbi->mdb_lock);
res = hfs_mdb_get(sb);
+ mutex_unlock(&sbi->mdb_lock);
if (res) {
if (!silent)
pr_warn("can't find a HFS filesystem on dev %s\n",
--
2.43.0
reply other threads:[~2026-07-20 18:44 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260720184414.195213-2-slava@dubeyko.com \
--to=slava@dubeyko.com \
--cc=frank.li@vivo.com \
--cc=glaubitz@physik.fu-berlin.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=samsun1006219@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox