mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
hfs: rework MDB locking scheme
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> cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de> cc: Yangtao Li <frank.li@vivo.com> cc: linux-fsdevel@vger.kernel.org Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> Link: https://lore.kernel.org/r/20260720184414.195213-2-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
This commit is contained in:
parent
c86df5cbba
commit
f26da7e038
|
|
@ -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
|
||||
|
|
|
|||
84
fs/hfs/mdb.c
84
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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user