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:
Linus Torvalds
2026-07-10 18:20:15 -07:00
8 changed files with 110 additions and 51 deletions

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);