Description for this pull request:

- Fix a NULL pointer dereference in ntfs_index_walk_down() by validating
    index block allocation.
  - Fix a memory leak of the symlink target string in
    ntfs_reparse_set_wsl_symlink() during error paths.
  - Prevent VCN overflow and validate lowest_vcn in
    ntfs_mapping_pairs_decompress() to avoid runlist corruption.
  - Fix a page reference leak in ntfs_write_iomap_end_resident() when
    attribute search context allocation fails.
  - Fix an invalid PTR_ERR() usage on a valid folio pointer in
    __ntfs_bitmap_set_bits_in_run().
  - Correct directory link counting by dropping nlink only when the MFT
    record link count reaches zero for WIN32/DOS aliases.
  - Fix an uninitialized variable usage in ntfs_mapping_pairs_decompress()
    by returning an error pointer directly.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmn1SX4WHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCLFtEACQou87tSAG0pjuOe4FDW2/ijTJ
 B4CWQ5AxSU/G8Mts1Or9bvjKMA2zI8A/N8Bx0kzZviB8G1TiIs2y8KWqJajLCXsX
 dEvLwu1UUvtYlclw3sVdo+7oA8lB9NQB5LNlaubTzkDeCXHpkfQ5/+zgbU2Bdpjf
 5qe34klrr8jU6KHIJnQlpiqJj8wYvNXizDRYkYZw0tMzNGlzM5csO8cZ4HNW8ENK
 +D7CAKBDW4JA8AaaBC9eGL3cpl/a8a1X46O1LoEoCeH14FKGEGAoSa5z5aWBDJpg
 X84v/19iP9Ti2poh2I5KZZfgKxFjsQodXYoPRofrXCGpVYUveTRmfEZ//qt33mr/
 Y+bX5iTBjP0H4OLr5o8TZNgHXqjsR5/kkbnz71VEZey53U3/fFLC6L0tt9S9vLnb
 mC2YghFgmcgQEIYz3S79F8K0JBEl4gSUsMNQtM8+vjqpYRsqFSSUYSEUqEJWgdaK
 1tnzbZlGMTgiiNO5EdqZXLIGqsJsckUfi0Qr3tnzdw2CWqj6Q0fCbBV0KVfeLYuY
 LtFfG6W2A8KUAvX+Nc6+MiQ887A9F8VYjR4sIC633IISiU05Kfd3OWP4Bx+05Yty
 wt6cccm+gCMBVMVacRDccfK+ovIDN50r+7Flbuw1jw28rxcbe5tVmoKrC3HQ/RYr
 hIXUXwqqCX5VMxsAOA==
 =MosZ
 -----END PGP SIGNATURE-----

Merge tag 'ntfs-for-7.1-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs

Pull ntfs fixes from Namjae Jeon:

 - Fix a NULL pointer dereference in ntfs_index_walk_down() by
   validating index block allocation

 - Fix a memory leak of the symlink target string in
   ntfs_reparse_set_wsl_symlink() during error paths

 - Prevent VCN overflow and validate lowest_vcn in
   ntfs_mapping_pairs_decompress() to avoid runlist corruption

 - Fix a page reference leak in ntfs_write_iomap_end_resident()
   when attribute search context allocation fails

 - Fix an invalid PTR_ERR() usage on a valid folio pointer in
   __ntfs_bitmap_set_bits_in_run()

 - Correct directory link counting by dropping nlink only when
   the MFT record link count reaches zero for WIN32/DOS aliases

 - Fix an uninitialized variable in ntfs_mapping_pairs_decompress()
   by returning an error pointer directly

* tag 'ntfs-for-7.1-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs:
  ntfs: Use return instead of goto in ntfs_mapping_pairs_decompress()
  ntfs: drop nlink once for WIN32/DOS aliases
  ntfs: fix invalid PTR_ERR() usage in __ntfs_bitmap_set_bits_in_run()
  ntfs: fix error handling in ntfs_write_iomap_end_resident()
  ntfs: fix VCN overflow in ntfs_mapping_pairs_decompress()
  ntfs: fix WSL symlink target leak on reparse failure
  ntfs: fix NULL dereference in ntfs_index_walk_down()
This commit is contained in:
Linus Torvalds 2026-05-02 12:25:57 -07:00
commit 4c2ed2a3db
7 changed files with 72 additions and 25 deletions

View File

@ -125,7 +125,7 @@ int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
struct address_space *mapping;
struct folio *folio;
u8 *kaddr;
int pos, len;
int pos, len, err;
u8 bit;
struct ntfs_inode *ni = NTFS_I(vi);
struct ntfs_volume *vol = ni->vol;
@ -201,8 +201,10 @@ int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
/* If we are not in the last page, deal with all subsequent pages. */
while (index < end_index) {
if (cnt <= 0)
if (cnt <= 0) {
err = -EIO;
goto rollback;
}
/* Update @index and get the next folio. */
folio_mark_dirty(folio);
@ -214,6 +216,7 @@ int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
ntfs_error(vi->i_sb,
"Failed to map subsequent page (error %li), aborting.",
PTR_ERR(folio));
err = PTR_ERR(folio);
goto rollback;
}
@ -265,7 +268,7 @@ int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
* - @count - @cnt is the number of bits that have been modified
*/
if (is_rollback)
return PTR_ERR(folio);
return err;
if (count != cnt)
pos = __ntfs_bitmap_set_bits_in_run(vi, start_bit, count - cnt,
value ? 0 : 1, true);
@ -274,14 +277,14 @@ int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
if (!pos) {
/* Rollback was successful. */
ntfs_error(vi->i_sb,
"Failed to map subsequent page (error %li), aborting.",
PTR_ERR(folio));
"Failed to map subsequent page (error %i), aborting.",
err);
} else {
/* Rollback failed. */
ntfs_error(vi->i_sb,
"Failed to map subsequent page (error %li) and rollback failed (error %i). Aborting and leaving inconsistent metadata. Unmount and run chkdsk.",
PTR_ERR(folio), pos);
"Failed to map subsequent page (error %i) and rollback failed (error %i). Aborting and leaving inconsistent metadata. Unmount and run chkdsk.",
err, pos);
NVolSetErrors(NTFS_SB(vi->i_sb));
}
return PTR_ERR(folio);
return err;
}

View File

@ -911,8 +911,8 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
if (next->flags & INDEX_ENTRY_NODE) {
next = ntfs_index_walk_down(next, ictx);
if (!next) {
err = -EIO;
if (IS_ERR(next)) {
err = PTR_ERR(next);
goto out;
}
}
@ -920,7 +920,14 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
if (next && !(next->flags & INDEX_ENTRY_END))
goto nextdir;
while ((next = ntfs_index_next(next, ictx)) != NULL) {
while (1) {
next = ntfs_index_next(next, ictx);
if (IS_ERR(next)) {
err = PTR_ERR(next);
goto out;
}
if (!next)
break;
nextdir:
/* Check the consistency of an index entry */
if (ntfs_index_entry_inconsistent(ictx, vol, next, COLLATION_FILE_NAME,

View File

@ -1969,15 +1969,19 @@ int ntfs_index_remove(struct ntfs_inode *dir_ni, const void *key, const u32 keyl
struct index_entry *ntfs_index_walk_down(struct index_entry *ie, struct ntfs_index_context *ictx)
{
struct index_entry *entry;
struct index_block *ib;
s64 vcn;
entry = ie;
do {
vcn = ntfs_ie_get_vcn(entry);
if (ictx->is_in_root) {
ib = kvzalloc(ictx->block_size, GFP_NOFS);
if (!ib)
return ERR_PTR(-ENOMEM);
/* down from level zero */
ictx->ir = NULL;
ictx->ib = kvzalloc(ictx->block_size, GFP_NOFS);
ictx->ib = ib;
ictx->pindex = 1;
ictx->is_in_root = false;
} else {
@ -1991,8 +1995,8 @@ struct index_entry *ntfs_index_walk_down(struct index_entry *ie, struct ntfs_ind
ictx->entry = ntfs_ie_get_first(&ictx->ib->index);
entry = ictx->entry;
} else
entry = NULL;
} while (entry && (entry->flags & INDEX_ENTRY_NODE));
entry = ERR_PTR(-EIO);
} while (!IS_ERR(entry) && (entry->flags & INDEX_ENTRY_NODE));
return entry;
}
@ -2097,10 +2101,15 @@ struct index_entry *ntfs_index_next(struct index_entry *ie, struct ntfs_index_co
/* walk down if it has a subnode */
if (flags & INDEX_ENTRY_NODE) {
if (!ictx->ia_ni)
if (!ictx->ia_ni) {
ictx->ia_ni = ntfs_ia_open(ictx, ictx->idx_ni);
if (!ictx->ia_ni)
return ERR_PTR(-EIO);
}
next = ntfs_index_walk_down(next, ictx);
if (IS_ERR(next))
return next;
} else {
/* walk up it has no subnode, nor data */

View File

@ -788,8 +788,7 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx) {
written = -ENOMEM;
mutex_unlock(&ni->mrec_lock);
return written;
goto err_out;
}
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
@ -810,7 +809,8 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
memcpy(kattr + pos, iomap_inline_data(iomap, pos), written);
mark_mft_record_dirty(ctx->ntfs_ino);
err_out:
ntfs_attr_put_search_ctx(ctx);
if (ctx)
ntfs_attr_put_search_ctx(ctx);
put_page(ipage);
mutex_unlock(&ni->mrec_lock);
return written;

View File

@ -945,7 +945,8 @@ static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
ni_mrec = actx->base_mrec ? actx->base_mrec : actx->mrec;
ni_mrec->link_count = cpu_to_le16(le16_to_cpu(ni_mrec->link_count) - 1);
drop_nlink(VFS_I(ni));
if (!S_ISDIR(VFS_I(ni)->i_mode))
drop_nlink(VFS_I(ni));
mark_mft_record_dirty(ni);
if (looking_for_dos_name) {
@ -955,6 +956,13 @@ static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
goto search;
}
/*
* For directories, Drop VFS nlink only when mft record link count
* becomes zero. Because we fixes VFS nlink to 1 for directories.
*/
if (S_ISDIR(VFS_I(ni)->i_mode) && !le16_to_cpu(ni_mrec->link_count))
drop_nlink(VFS_I(ni));
/*
* If hard link count is not equal to zero then we are done. In other
* case there are no reference to this inode left, so we should free all
@ -1221,7 +1229,8 @@ static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
}
/* Increment hard links count. */
ni_mrec->link_count = cpu_to_le16(le16_to_cpu(ni_mrec->link_count) + 1);
inc_nlink(VFS_I(ni));
if (!S_ISDIR(vi->i_mode))
inc_nlink(VFS_I(ni));
/* Done! */
mark_mft_record_dirty(ni);

View File

@ -505,7 +505,6 @@ int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
struct reparse_point *reparse;
struct wsl_link_reparse_data *data;
utarget = (char *)NULL;
len = ntfs_ucstonls(ni->vol, target, target_len, &utarget, 0);
if (len <= 0)
return -EINVAL;
@ -514,7 +513,7 @@ int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
reparse = kvzalloc(reparse_len, GFP_NOFS);
if (!reparse) {
err = -ENOMEM;
kvfree(utarget);
kfree(utarget);
} else {
data = (struct wsl_link_reparse_data *)reparse->reparse_data;
reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
@ -528,6 +527,8 @@ int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
kvfree(reparse);
if (!err)
ni->target = utarget;
else
kfree(utarget);
}
return err;
}

View File

@ -15,6 +15,8 @@
* Copyright (c) 2007-2022 Jean-Pierre Andre
*/
#include <linux/overflow.h>
#include "ntfs.h"
#include "attrib.h"
@ -739,6 +741,7 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
int rlsize; /* Size of runlist buffer. */
u16 rlpos; /* Current runlist position in units of struct runlist_elements. */
u8 b; /* Current byte offset in buf. */
u64 lowest_vcn; /* Raw on-disk lowest_vcn. */
#ifdef DEBUG
/* Make sure attr exists and is non-resident. */
@ -747,8 +750,14 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
return ERR_PTR(-EINVAL);
}
#endif
lowest_vcn = le64_to_cpu(attr->data.non_resident.lowest_vcn);
/* Validate lowest_vcn from on-disk metadata to ensure it is sane. */
if (overflows_type(lowest_vcn, vcn)) {
ntfs_error(vol->sb, "Invalid lowest_vcn in mapping pairs.");
return ERR_PTR(-EIO);
}
/* Start at vcn = lowest_vcn and lcn 0. */
vcn = le64_to_cpu(attr->data.non_resident.lowest_vcn);
vcn = lowest_vcn;
lcn = 0;
/* Get start of the mapping pairs array. */
buf = (u8 *)attr +
@ -823,8 +832,17 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
* element.
*/
rl[rlpos].length = deltaxcn;
/* Increment the current vcn by the current run length. */
vcn += deltaxcn;
/*
* Increment the current vcn by the current run length.
* Guard against s64 overflow from a crafted mapping
* pairs array to preserve the monotonically-increasing
* vcn invariant.
*/
if (unlikely(check_add_overflow(vcn, deltaxcn, &vcn))) {
ntfs_error(vol->sb, "VCN overflow in mapping pairs array.");
goto err_out;
}
/*
* There might be no lcn change at all, as is the case for
* sparse clusters on NTFS 3.0+, in which case we set the lcn