summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2024-05-20 15:23:37 +0200
committerJan Kara <jack@suse.cz>2024-06-05 10:36:46 +0200
commit8832fc1e502687869606bb0a7b79848ed3bf036f (patch)
tree2395facf6bde8c42f55d5bc1821af6edee9c9de0
parentc1f1b25a6089eb93a11a147e8de766c0d6ad29ac (diff)
udf: Fix lock ordering in udf_evict_inode()
udf_evict_inode() calls udf_setsize() to truncate deleted inode. However inode deletion through udf_evict_inode() can happen from inode reclaim context and udf_setsize() grabs mapping->invalidate_lock which isn't generally safe to acquire from fs reclaim context since we allocate pages under mapping->invalidate_lock for example in a page fault path. This is however not a real deadlock possibility as by the time udf_evict_inode() is called, nobody can be accessing the inode, even less work with its page cache. So this is just a lockdep triggering false positive. Fix the problem by moving mapping->invalidate_lock locking outsize of udf_setsize() into udf_setattr() as grabbing mapping->invalidate_lock from udf_evict_inode() is pointless. Reported-by: syzbot+0333a6f4b88bcd68a62f@syzkaller.appspotmail.com Fixes: b9a861fd527a ("udf: Protect truncate and file type conversion with invalidate_lock") Signed-off-by: Jan Kara <jack@suse.cz>
-rw-r--r--fs/udf/file.c2
-rw-r--r--fs/udf/inode.c11
2 files changed, 6 insertions, 7 deletions
diff --git a/fs/udf/file.c b/fs/udf/file.c
index 97c59585208c..3a4179de316b 100644
--- a/fs/udf/file.c
+++ b/fs/udf/file.c
@@ -232,7 +232,9 @@ static int udf_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if ((attr->ia_valid & ATTR_SIZE) &&
attr->ia_size != i_size_read(inode)) {
+ filemap_invalidate_lock(inode->i_mapping);
error = udf_setsize(inode, attr->ia_size);
+ filemap_invalidate_unlock(inode->i_mapping);
if (error)
return error;
}
diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index 9d39fdd26015..4726a4d014b6 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -1248,7 +1248,6 @@ int udf_setsize(struct inode *inode, loff_t newsize)
S_ISLNK(inode->i_mode)))
return -EINVAL;
- filemap_invalidate_lock(inode->i_mapping);
iinfo = UDF_I(inode);
if (newsize > inode->i_size) {
if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
@@ -1261,11 +1260,11 @@ int udf_setsize(struct inode *inode, loff_t newsize)
}
err = udf_expand_file_adinicb(inode);
if (err)
- goto out_unlock;
+ return err;
}
err = udf_extend_file(inode, newsize);
if (err)
- goto out_unlock;
+ return err;
set_size:
truncate_setsize(inode, newsize);
} else {
@@ -1283,14 +1282,14 @@ set_size:
err = block_truncate_page(inode->i_mapping, newsize,
udf_get_block);
if (err)
- goto out_unlock;
+ return err;
truncate_setsize(inode, newsize);
down_write(&iinfo->i_data_sem);
udf_clear_extent_cache(inode);
err = udf_truncate_extents(inode);
up_write(&iinfo->i_data_sem);
if (err)
- goto out_unlock;
+ return err;
}
update_time:
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
@@ -1298,8 +1297,6 @@ update_time:
udf_sync_inode(inode);
else
mark_inode_dirty(inode);
-out_unlock:
- filemap_invalidate_unlock(inode->i_mapping);
return err;
}