mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-16 14:30:06 -04:00
Merge tag 'ntfs-for-7.2-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs
Pull ntfs fixes from Namjae Jeon: - fix stale runlist element dereferences in MFT writeback and fallocate - fix mrec_lock ABBA deadlock in rename - prevent userspace modification of NTFS system files - avoid inode eviction/writeback self-deadlocks - reject malformed resident attributes in non-resident runlist mapping - avoid post_write_mst_fixup() on invalid index blocks - fix a hole runlist leak in insert-range error handling - sanitize directory lookup MFT references from disk - fail attribute-list updates after SB_ACTIVE is cleared during teardown * tag 'ntfs-for-7.2-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs: ntfs: fail attrlist updates when the superblock is inactive ntfs: sanitize MFT references returned from ntfs_lookup_inode_by_name() ntfs: fix hole runlist memory leak in insert range error path ntfs: avoid calling post_write_mst_fixup() for invalid index_block ntfs: fix WARN_ON for resident attribute in ntfs_map_runlist_nolock() ntfs: avoid self-deadlock during inode eviction ntfs: make system files immutable to prevent corruption ntfs: fix mrec_lock ABBA deadlock in rename ntfs: avoid stale runlist element dereference in fallocate ntfs: avoid stale runlist element dereference in MFT writeback
This commit is contained in:
@@ -249,6 +249,8 @@ static int ntfs_writepages(struct address_space *mapping,
|
||||
.wbc = wbc,
|
||||
.ops = &ntfs_writeback_ops,
|
||||
};
|
||||
bool need_iput = false;
|
||||
int ret;
|
||||
|
||||
if (NVolShutdown(ni->vol))
|
||||
return -EIO;
|
||||
@@ -265,7 +267,20 @@ static int ntfs_writepages(struct address_space *mapping,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return iomap_writepages(&wpc);
|
||||
/*
|
||||
* Prevent eviction in writeback to avoid deadlock in
|
||||
* ntfs_drop_big_inode().
|
||||
*/
|
||||
if ((ni->type == AT_DATA || ni->type == AT_INDEX_ALLOCATION) &&
|
||||
igrab(inode))
|
||||
need_iput = true;
|
||||
|
||||
ret = iomap_writepages(&wpc);
|
||||
|
||||
if (need_iput)
|
||||
iput(inode);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ntfs_swap_activate(struct swap_info_struct *sis,
|
||||
|
||||
@@ -175,7 +175,10 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
|
||||
err = -EIO;
|
||||
goto err_out;
|
||||
}
|
||||
WARN_ON(!ctx->attr->non_resident);
|
||||
if (unlikely(!ctx->attr->non_resident)) {
|
||||
err = -EIO;
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
a = ctx->attr;
|
||||
/*
|
||||
@@ -5325,6 +5328,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
|
||||
ret = ntfs_attr_map_whole_runlist(ni);
|
||||
if (ret) {
|
||||
up_write(&ni->runlist.lock);
|
||||
kfree(hole_rl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -5536,6 +5540,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
|
||||
s64 old_data_size;
|
||||
s64 vcn_start, vcn_end, vcn_uninit, vcn, try_alloc_cnt;
|
||||
s64 lcn, alloc_cnt;
|
||||
s64 rl_lcn, rl_length, rl_vcn;
|
||||
int err = 0;
|
||||
struct runlist_element *rl;
|
||||
bool balloc;
|
||||
@@ -5615,19 +5620,23 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
|
||||
while (vcn < vcn_uninit) {
|
||||
down_read(&ni->runlist.lock);
|
||||
rl = ntfs_attr_find_vcn_nolock(ni, vcn, NULL);
|
||||
up_read(&ni->runlist.lock);
|
||||
if (IS_ERR(rl)) {
|
||||
up_read(&ni->runlist.lock);
|
||||
err = PTR_ERR(rl);
|
||||
goto out;
|
||||
}
|
||||
rl_lcn = rl->lcn;
|
||||
rl_length = rl->length;
|
||||
rl_vcn = rl->vcn;
|
||||
up_read(&ni->runlist.lock);
|
||||
|
||||
if (rl->lcn > 0) {
|
||||
vcn += rl->length - (vcn - rl->vcn);
|
||||
} else if (rl->lcn == LCN_DELALLOC || rl->lcn == LCN_HOLE) {
|
||||
try_alloc_cnt = min(rl->length - (vcn - rl->vcn),
|
||||
if (rl_lcn > 0) {
|
||||
vcn += rl_length - (vcn - rl_vcn);
|
||||
} else if (rl_lcn == LCN_DELALLOC || rl_lcn == LCN_HOLE) {
|
||||
try_alloc_cnt = min(rl_length - (vcn - rl_vcn),
|
||||
vcn_uninit - vcn);
|
||||
|
||||
if (rl->lcn == LCN_DELALLOC) {
|
||||
if (rl_lcn == LCN_DELALLOC) {
|
||||
vcn += try_alloc_cnt;
|
||||
continue;
|
||||
}
|
||||
@@ -5642,11 +5651,14 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = ntfs_dio_zero_range(VFS_I(ni),
|
||||
lcn << vol->cluster_size_bits,
|
||||
alloc_cnt << vol->cluster_size_bits);
|
||||
if (err > 0)
|
||||
goto out;
|
||||
if (balloc) {
|
||||
err = ntfs_dio_zero_range(VFS_I(ni),
|
||||
lcn << vol->cluster_size_bits,
|
||||
alloc_cnt <<
|
||||
vol->cluster_size_bits);
|
||||
if (err > 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (signal_pending(current))
|
||||
goto out;
|
||||
|
||||
@@ -57,6 +57,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
|
||||
struct ntfs_inode *attr_ni;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* generic_shutdown_super() clears SB_ACTIVE before evicting cached
|
||||
* inodes. Do not look up the attribute-list inode after SB_ACTIVE has
|
||||
* been cleared; it may already be I_FREEING, and waiting on it can
|
||||
* self-deadlock.
|
||||
*/
|
||||
if (!(VFS_I(base_ni)->i_sb->s_flags & SB_ACTIVE))
|
||||
return -EIO;
|
||||
|
||||
attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0);
|
||||
if (IS_ERR(attr_vi)) {
|
||||
err = PTR_ERR(attr_vi);
|
||||
|
||||
@@ -23,6 +23,13 @@
|
||||
__le16 I30[5] = { cpu_to_le16('$'), cpu_to_le16('I'),
|
||||
cpu_to_le16('3'), cpu_to_le16('0'), 0 };
|
||||
|
||||
static inline u64 ntfs_check_mref(u64 mref)
|
||||
{
|
||||
if (IS_ERR_MREF(mref))
|
||||
return ERR_MREF(-EIO);
|
||||
return mref;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_lookup_inode_by_name - find an inode in a directory given its name
|
||||
* @dir_ni: ntfs inode of the directory in which to search for the name
|
||||
@@ -178,7 +185,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
mref = le64_to_cpu(ie->data.dir.indexed_file);
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
unmap_mft_record(dir_ni);
|
||||
return mref;
|
||||
return ntfs_check_mref(mref);
|
||||
}
|
||||
/*
|
||||
* For a case insensitive mount, we also perform a case
|
||||
@@ -273,7 +280,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
if (name) {
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
unmap_mft_record(dir_ni);
|
||||
return name->mref;
|
||||
return ntfs_check_mref(name->mref);
|
||||
}
|
||||
ntfs_debug("Entry not found.");
|
||||
err = -ENOENT;
|
||||
@@ -413,7 +420,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
mref = le64_to_cpu(ie->data.dir.indexed_file);
|
||||
kfree(kaddr);
|
||||
iput(ia_vi);
|
||||
return mref;
|
||||
return ntfs_check_mref(mref);
|
||||
}
|
||||
/*
|
||||
* For a case insensitive mount, we also perform a case
|
||||
@@ -538,7 +545,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
if (name) {
|
||||
kfree(kaddr);
|
||||
iput(ia_vi);
|
||||
return name->mref;
|
||||
return ntfs_check_mref(name->mref);
|
||||
}
|
||||
ntfs_debug("Entry not found.");
|
||||
err = -ENOENT;
|
||||
|
||||
@@ -110,6 +110,10 @@ static int ntfs_ib_write(struct ntfs_index_context *icx, struct index_block *ib)
|
||||
ret = ntfs_inode_attr_pwrite(VFS_I(icx->ia_ni),
|
||||
ntfs_ib_vcn_to_pos(icx, vcn), icx->block_size,
|
||||
(u8 *)ib, icx->sync_write);
|
||||
|
||||
/* Perform data restoration before returning */
|
||||
post_write_mst_fixup((struct ntfs_record *)ib);
|
||||
|
||||
if (ret != icx->block_size) {
|
||||
ntfs_debug("Failed to write index block %lld, inode %llu",
|
||||
vcn, (unsigned long long)icx->idx_ni->mft_no);
|
||||
@@ -147,7 +151,6 @@ int ntfs_icx_ib_sync_write(struct ntfs_index_context *icx)
|
||||
icx->ib = NULL;
|
||||
icx->ib_dirty = false;
|
||||
} else {
|
||||
post_write_mst_fixup((struct ntfs_record *)icx->ib);
|
||||
icx->sync_write = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1191,6 +1191,15 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
!S_ISFIFO(vi->i_mode) && !S_ISSOCK(vi->i_mode) && !S_ISLNK(vi->i_mode))
|
||||
vi->i_flags |= S_IMMUTABLE;
|
||||
|
||||
/*
|
||||
* System files such as $Bitmap and $MFT are maintained by the driver
|
||||
* itself, and writing them from userspace corrupts the volume.
|
||||
* Always make them immutable regardless of the sys_immutable option.
|
||||
* Directories are skipped so the root and $Extend stay usable.
|
||||
*/
|
||||
if (ni->mft_no < FILE_first_user && S_ISREG(vi->i_mode))
|
||||
vi->i_flags |= S_IMMUTABLE;
|
||||
|
||||
/*
|
||||
* The number of 512-byte blocks used on disk (for stat). This is in so
|
||||
* far inaccurate as it doesn't account for any named streams or other
|
||||
|
||||
@@ -2637,7 +2637,6 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
|
||||
s64 vcn = ntfs_pidx_to_cluster(vol, folio->index);
|
||||
s64 end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size);
|
||||
unsigned int folio_sz;
|
||||
struct runlist_element *rl = NULL;
|
||||
loff_t i_size = i_size_read(vi);
|
||||
|
||||
ntfs_debug("Entering for inode 0x%llx, attribute type 0x%x, folio index 0x%lx.",
|
||||
@@ -2682,6 +2681,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
|
||||
&tni, &ref_inos[nr_ref_inos])) {
|
||||
unsigned int mft_record_off = 0;
|
||||
s64 vcn_off = vcn;
|
||||
s64 rl_len = 0;
|
||||
|
||||
/*
|
||||
* The record should be written. If a locked ntfs
|
||||
@@ -2701,8 +2701,12 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
|
||||
}
|
||||
|
||||
if (vol->cluster_size < folio_size(folio)) {
|
||||
struct runlist_element *rl;
|
||||
|
||||
down_write(&ni->runlist.lock);
|
||||
rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
|
||||
if (!IS_ERR(rl))
|
||||
rl_len = rl->length - (vcn_off - rl->vcn);
|
||||
up_write(&ni->runlist.lock);
|
||||
if (IS_ERR(rl) || lcn < 0) {
|
||||
err = -EIO;
|
||||
@@ -2733,7 +2737,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
|
||||
|
||||
if (vol->cluster_size == NTFS_BLOCK_SIZE &&
|
||||
(mft_record_off ||
|
||||
(rl && rl->length - (vcn_off - rl->vcn) == 1) ||
|
||||
rl_len == 1 ||
|
||||
mft_ofs + NTFS_BLOCK_SIZE >= PAGE_SIZE))
|
||||
folio_sz = NTFS_BLOCK_SIZE;
|
||||
else
|
||||
|
||||
@@ -1266,6 +1266,7 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
struct ntfs_volume *vol = NTFS_SB(sb);
|
||||
struct ntfs_inode *old_ni, *new_ni = NULL;
|
||||
struct ntfs_inode *old_dir_ni = NTFS_I(old_dir), *new_dir_ni = NTFS_I(new_dir);
|
||||
bool new_dir_first = false;
|
||||
|
||||
if (NVolShutdown(old_dir_ni->vol))
|
||||
return -EIO;
|
||||
@@ -1301,36 +1302,39 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
old_inode = old_dentry->d_inode;
|
||||
new_inode = new_dentry->d_inode;
|
||||
old_ni = NTFS_I(old_inode);
|
||||
if (new_inode)
|
||||
new_ni = NTFS_I(new_inode);
|
||||
if (old_dir != new_dir)
|
||||
new_dir_first = is_subdir(new_dentry->d_parent,
|
||||
old_dentry->d_parent);
|
||||
|
||||
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
|
||||
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
|
||||
|
||||
mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
|
||||
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
|
||||
if (new_ni)
|
||||
mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
|
||||
|
||||
if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) {
|
||||
if (old_dir == new_dir) {
|
||||
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
|
||||
} else if (new_dir_first) {
|
||||
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
|
||||
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
|
||||
} else {
|
||||
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
|
||||
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
|
||||
}
|
||||
|
||||
if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni) ||
|
||||
(new_ni && NInoBeingDeleted(new_ni)) ||
|
||||
(old_dir != new_dir && NInoBeingDeleted(new_dir_ni))) {
|
||||
err = -ENOENT;
|
||||
goto unlock_old;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
is_dir = S_ISDIR(old_inode->i_mode);
|
||||
|
||||
if (new_inode) {
|
||||
new_ni = NTFS_I(new_inode);
|
||||
mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
|
||||
if (old_dir != new_dir) {
|
||||
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
|
||||
if (NInoBeingDeleted(new_dir_ni)) {
|
||||
err = -ENOENT;
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
|
||||
if (NInoBeingDeleted(new_ni)) {
|
||||
err = -ENOENT;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (is_dir) {
|
||||
struct mft_record *ni_mrec;
|
||||
|
||||
@@ -1348,14 +1352,6 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
err = ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false);
|
||||
if (err)
|
||||
goto err_out;
|
||||
} else {
|
||||
if (old_dir != new_dir) {
|
||||
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
|
||||
if (NInoBeingDeleted(new_dir_ni)) {
|
||||
err = -ENOENT;
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len);
|
||||
@@ -1386,13 +1382,17 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
inode_inc_iversion(new_dir);
|
||||
|
||||
err_out:
|
||||
if (old_dir != new_dir)
|
||||
if (old_dir == new_dir) {
|
||||
mutex_unlock(&old_dir_ni->mrec_lock);
|
||||
} else if (new_dir_first) {
|
||||
mutex_unlock(&old_dir_ni->mrec_lock);
|
||||
mutex_unlock(&new_dir_ni->mrec_lock);
|
||||
if (new_inode)
|
||||
} else {
|
||||
mutex_unlock(&new_dir_ni->mrec_lock);
|
||||
mutex_unlock(&old_dir_ni->mrec_lock);
|
||||
}
|
||||
if (new_ni)
|
||||
mutex_unlock(&new_ni->mrec_lock);
|
||||
|
||||
unlock_old:
|
||||
mutex_unlock(&old_dir_ni->mrec_lock);
|
||||
mutex_unlock(&old_ni->mrec_lock);
|
||||
if (uname_new)
|
||||
kmem_cache_free(ntfs_name_cache, uname_new);
|
||||
|
||||
Reference in New Issue
Block a user