mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-28 12:53:14 +01: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 <[email protected]> Link: https://lore.kernel.org/all/CAEkJfYMB47v1yOWHB8q2dc8kf=uj-rLO=+yMyudwPguJ8Kd3jA@mail.gmail.com/ Signed-off-by: Yue Sun <[email protected]> cc: John Paul Adrian Glaubitz <[email protected]> cc: Yangtao Li <[email protected]> cc: [email protected] Signed-off-by: Viacheslav Dubeyko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Viacheslav Dubeyko <[email protected]>
This commit is contained in:
1 parent
c86df5cbba
commit
f26da7e038
3 files changed
+87
-23
No files matched your search
+10
-2
@@ -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
|
||||
|
||||
+65
-19
@@ -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);
|
||||
|
||||
+12
-2
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user