Linux filesystem development
 help / color / mirror / Atom feed
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