Commit Graph

589 Commits

Author SHA1 Message Date
Linus Torvalds
61c03dfde8 Description for this pull request:
- 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
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmpRiDMWHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCD43EADKiCMxaenYjS7BveKvxV1eCMUW
 LY5OaS8pEAILxX1Et5XFH96eqkHYlrGvneOd9txZpO48j/Ieyep0/cqeZFK4hw31
 JyIItrSM1VwbPUK75bMPSXCpJn/BbzCDf+PsQwp44SE6VQWwZfWQfmj3neSU1hIU
 S8jI3GZaf6Ry5s5BQOiLP4gpfOcPKFhlTDLFycHg8Rn5Uqt3LwDoFTsfHh/Bs9ls
 MMcoyEmEJ7twF4+PpEZtCw/Am6FY3t2rSFTtfw3GuNNEKCemKm1V1L8CdFCbu0xo
 fC3cyq+aWBWiEFTueotszbLl4MHYODPZKfwtH/FgT+MUFPKQidsuAFsuRYHS+V28
 lRPb+XTxfCeerLTlTlseX3Tes3Auw3XW9nojduvGKxK5w0Bsu/2c638fviOxd9Uk
 RmZ7EOPB/ROwXFswQF+xk9YAFWLN2FwJtdnWyj4f5XLF6vxFDxzU5lvrc/BW6TQM
 ePUCr+0ngKe93Nl+4xCcITTkSjNy5jM7iQCy26/8946+nYtYZ4Nd9tbcDpYpSoiT
 gvyIK/MauqJ1Uuxs9VvmZZj4SMcWdJHMPjDSLNyalTgSSzS0LO6zEvgLJo0n4+Tj
 wbhR0A8rqEWAT42Owj2SpMNzwd/wC7lTfgIPePAVF2uGI8n9tWC+xT9iUTf19MN1
 w+YM/ZPQmD0eDPgU9w==
 =TC/T
 -----END PGP SIGNATURE-----

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
2026-07-10 18:20:15 -07:00
Peiyang He
0ebe8f625a ntfs: fail attrlist updates when the superblock is inactive
generic_shutdown_super() clears SB_ACTIVE before evicting cached inodes.
If eviction selects the fake inode for a base inode's unnamed
$ATTRIBUTE_LIST attribute, ntfs_evict_big_inode() drops the fake inode's
reference on the base inode while the fake inode is still hashed and marked
I_FREEING.

That iput can synchronously write back the base inode. The writeback path
may update mapping pairs and call ntfs_attrlist_update(), which
unconditionally calls ntfs_attr_iget() for the same $ATTRIBUTE_LIST fake
inode. VFS then finds the I_FREEING inode and waits for eviction to finish,
but the current task is still inside that eviction path, causing a
self-deadlock in find_inode().

Fix this by mirroring the teardown guard used by __ntfs_write_inode():
once SB_ACTIVE has been cleared, do not try to iget the attribute-list
fake inode. Return -EIO so teardown aborts the update instead of waiting on
the inode it is evicting.

Reported-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Closes: https://lore.kernel.org/all/AB8D5E603E6EA856+ae5f622a-dd3a-4e38-bdd2-42276ae0e1a8@smail.nju.edu.cn/
Fixes: 495e90fa33 ("ntfs: update attrib operations")
Cc: stable@vger.kernel.org
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Assisted-by: Codex:gpt-5.5
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-07-06 20:27:18 +09:00
Namjae Jeon
d97a36bae8 ntfs: sanitize MFT references returned from ntfs_lookup_inode_by_name()
ntfs_lookup_inode_by_name() returns MFT references read from directory
index entries on disk. These values are untrusted, but the function can
currently return an error-marked MFT reference to its callers without
validating it.

Callers later decode lookup failures with MREF_ERR(). A crafted NTFS image
can set the MREF error bit while leaving the low bits as an arbitrary
value, causing callers to consume a bogus pseudo-errno instead of treating
the lookup result as corrupted on-disk metadata.

Fix this at the source by normalizing every error-marked MFT reference
returned from ntfs_lookup_inode_by_name() to ERR_MREF(-EIO). Apply this to
all four directory lookup return paths so every caller gets a validated
result without needing additional checks or an API change.

This keeps the sanitization in the common lookup helper, which is cleaner
than duplicating validation in each caller.

Fixes: 1e9ea7e044 ("Revert "fs: Remove NTFS classic"")
Cc: stable@vger.kernel.org
Reported-by: Hongling Zeng <zenghongling@kylinos.cn>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-07-06 20:27:16 +09:00
Peiyang He
06769b8f23 ntfs: fix hole runlist memory leak in insert range error path
ntfs_non_resident_attr_insert_range() allocates hole_rl before mapping the
whole runlist. If ntfs_attr_map_whole_runlist() fails, the error path drops
ni->runlist.lock and returns without freeing hole_rl. This leaks memory
of sizeof(*hole_rl) * 2 bytes.

Fix this memory leak by freeing hole_rl before returning from
that error path, matching the later error paths in the same function.

Fixes: 495e90fa33 ("ntfs: update attrib operations")
Cc: stable@vger.kernel.org
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-07-06 20:27:15 +09:00
Valeriy Yashnikov
5b6eedd7cc ntfs: avoid calling post_write_mst_fixup() for invalid index_block
ntfs_icx_ib_sync_write() calls post_write_mst_fixup() when ntfs_ib_write()
returns an error, intending to restore the buffer after a failed write.

However, ntfs_ib_write() returns an error immediately if
pre_write_mst_fixup() validation fails. The caller,
ntfs_icx_ib_sync_write(), interprets any error as a write failure
requiring rollback. It does not differentiate between I/O errors and
validation failures, and calls post_write_mst_fixup() anyway.

Since post_write_mst_fixup() assumes that the index_block contents is
correct, it doesn't perform the boundary checks, which results in
out-of-bounds memory access.

An attacker can craft a malicious NTFS image with:
  - large index_block.usa_ofs offset, pointing outside the ntfs_record
  - index_block.usa_count = 0, causing integer underflow
  - or index_block.usa_count larger than actual number of sectors in the
    ntfs_record, causing out-of-bounds access

KASAN reports describing the memory corruption:
  ==================================================================
  BUG: KASAN: slab-out-of-bounds in post_write_mst_fixup+0x19c/0x1d0
  Read of size 2 at addr ffff8881586c9018 by task p/9428
  Call Trace:
   <TASK>
   dump_stack_lvl+0x100/0x190
   print_report+0x139/0x4ad
   ? post_write_mst_fixup+0x19c/0x1d0
   ? __virt_addr_valid+0x262/0x500
   ? post_write_mst_fixup+0x19c/0x1d0
   kasan_report+0xe4/0x1d0
   ? post_write_mst_fixup+0x19c/0x1d0
   post_write_mst_fixup+0x19c/0x1d0
   ntfs_icx_ib_sync_write+0x179/0x220
   ntfs_inode_sync_filename+0x83d/0x1080
   __ntfs_write_inode+0x1049/0x1480
   ntfs_file_fsync+0x131/0x9b0
  ==================================================================
  BUG: KASAN: slab-out-of-bounds in post_write_mst_fixup+0x1aa/0x1d0
  Write of size 2 at addr ffff8881586c91fe by task p/9428
  Call Trace:
   <TASK>
   dump_stack_lvl+0x100/0x190
   print_report+0x139/0x4ad
   ? post_write_mst_fixup+0x1aa/0x1d0
   ? __virt_addr_valid+0x262/0x500
   ? post_write_mst_fixup+0x1aa/0x1d0
   kasan_report+0xe4/0x1d0
   ? post_write_mst_fixup+0x1aa/0x1d0
   post_write_mst_fixup+0x1aa/0x1d0
   ntfs_icx_ib_sync_write+0x179/0x220
   ntfs_inode_sync_filename+0x83d/0x1080
   __ntfs_write_inode+0x1049/0x1480
   ntfs_file_fsync+0x131/0x9b0
  ==================================================================

Let's move the post_write_mst_fixup() call to ntfs_ib_write().
The ntfs_ib_write() function calls pre_write_mst_fixup() at the beginning.
If the index_block contents is invalid, pre_write_mst_fixup() fails and
ntfs_ib_write() returns early without calling post_write_mst_fixup() on
bad index_block.

Fixes: 0a8ac0c1fa ("ntfs: update directory operations")
Cc: stable@vger.kernel.org
Signed-off-by: Valeriy Yashnikov <yashnikov.valeriy@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-07-06 20:27:13 +09:00
Namjae Jeon
b8d6c528e9 ntfs: fix WARN_ON for resident attribute in ntfs_map_runlist_nolock()
When ntfs_map_runlist_nolock() needs to look up the attribute extent
containing a target VCN (ctx_needs_reset == true), it calls
ntfs_attr_lookup() and then expects the result to be a non-resident
attribute, since only non-resident attributes have a mapping pairs
array to decompress.

A crafted NTFS image can place a resident attribute where a non-resident
one is expected, causing ntfs_attr_lookup() to succeed but return a
resident attribute record.  Previously this was caught only by a
WARN_ON(), which does not stop execution.  The code then falls through to
read a->data.non_resident.highest_vcn from what is actually a resident
attribute, accessing the wrong union member and corrupting the VCN range
check.

The caller path triggering this warning during mount is:

  ntfs_map_runlist_nolock
  ntfs_empty_logfile
  load_system_files
  ntfs_fill_super

In this path ctx is NULL, so ntfs_map_runlist_nolock() allocates a
temporary search context internally and sets ctx_needs_reset = true.
The existing resident-attribute guard in the ctx != NULL branch already
returns -EIO silently for the same condition; make the ctx_needs_reset
path consistent by replacing the WARN_ON() with the same -EIO error
return.

This causes the crafted image to be rejected with a mount error instead
of triggering a kernel warning.

Fixes: 495e90fa33 ("ntfs: update attrib operations")
Cc: stable@vger.kernel.org
Reported-by: Sangho Lee <kudo3228@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-07-06 20:26:08 +09:00
Hyunchul Lee
77dc384207 ntfs: avoid self-deadlock during inode eviction
An attribute-list update performed while allocating clusters can drop the
last reference to the temporary attribute inode. Evicting that inode
drops its reference to the base inode and can invoke ntfs_drop_big_inode()
for the base inode from within the base inode's own writeback path.

If the base inode is unlinked, ntfs_drop_big_inode() calls
truncate_setsize(), which waits for the inode's folio writeback to
complete. The same writeback worker is responsible for completing that
writeback, so it waits for itself indefinitely.

Prevent this self-deadlock by grabbing a reference to the base inode at the
beginning of ntfs_writepages() and releasing it at the end of the function.
This defers eviction until all bios have been submitted, allowing the wait
for folio writeback to complete safely.

Fixes: b041ca5625 ("ntfs: update iomap and address space operations")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-07-06 20:26:05 +09:00
Namjae Jeon
f72df3a4c3 ntfs: make system files immutable to prevent corruption
When a system file such as $Bitmap is exposed via show_sys_files and
written from userspace, the volume is corrupted and, because the cluster
allocator scans $Bitmap through the same inode's page cache, a write to
$Bitmap also deadlocks writeback against the folio it already holds locked.

These files are maintained by the driver itself and have no valid reason
to be written through the file interface. Mark base metadata files
(mft_no < FILE_first_user) as immutable during inode read so the VFS
rejects write, mmap, truncate and unlink with -EPERM. Directories are
skipped so the root and $Extend remain usable. Internal metadata updates
do not go through the VFS write path and are unaffected.

Fixes: af0db57d42 ("ntfs: update inode operations")
Cc: stable@vger.kernel.org
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-07-06 20:26:03 +09:00
Christoph Hellwig
044472d5ee
iomap: consolidate bio submission
Add a iomap_bio_submit_read_endio helper factored out of
iomap_bio_submit_read to that all ->submit_read implementations for
iomap_read_ops that use iomap_bio_read_folio_range can shared the
logic.

Right now that logic is mostly trivial, but already has a bug for XFS
because the XFS version is too trivial:  file system integrity validation
needs a workqueue context and thus can't happen from the default iomap
bi_end_io I/O handler.  Unfortunately the iomap refactoring just before
fs integrity landed moved code around here and the call go misplaced,
meaning it never got called.  The PI information still is verified by
the block layer, but the offloading is less efficient (and the future
userspace interface can't get at it).

Fixes: 0b10a37052 ("iomap: support T10 protection information")
Cc: stable@vger.kernel.org # v7.1
Signed-off-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20260629121750.3392300-2-hch@lst.de
Acked-by: Namjae Jeon <linkinjeon@kernel.org>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-07-01 15:26:47 +02:00
Peiyang He
eb94f5a41a ntfs: fix mrec_lock ABBA deadlock in rename
ntfs_file_fsync(), ntfs_dir_fsync() and __ntfs_write_inode() lock an
inode's mrec_lock before taking the mrec_lock of its parent directory.

ntfs_rename() takes old_ni->mrec_lock and old_dir_ni->mrec_lock
before taking new_ni->mrec_lock for an existing target, or
new_dir_ni->mrec_lock for a cross-directory rename.
This can deadlock when ntfs_file_fsync() or __ntfs_write_inode() holds
the target inode, or when ntfs_dir_fsync() holds a child target
directory, while rename() holds the parent directory and waits for the
target.

Fix this by locking the existing target inode before taking any parent
directory mrec_lock. For cross-directory renames where the target parent
is a descendant of the source parent, lock the target parent before the
source parent so the directory order matches the child-to-parent order used
by ntfs_file_fsync(), ntfs_dir_fsync(), and __ntfs_write_inode().

Reported-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Closes: https://lore.kernel.org/all/C4D296F0E9F3D66C+9397ffbc-eb55-44bb-9b3f-5da4809e7955@smail.nju.edu.cn/
Fixes: af0db57d42 ("ntfs: update inode operations")
Cc: stable@vger.kernel.org
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Assisted-by: Codex:gpt-5.5
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-30 23:22:34 +09:00
Cen Zhang
88496c4ac5 ntfs: avoid stale runlist element dereference in fallocate
ntfs_attr_fallocate() allocates holes and delayed allocations inside
initialized size by looking up the current runlist element under
ni->runlist.lock. The returned struct runlist_element is only a borrowed
pointer into ni->runlist.rl. A writer can replace and free that array
after the read lock is dropped, so later reads of rl->lcn, rl->length and
rl->vcn can touch freed memory.

The buggy scenario involves two paths, with each column showing the order
within that path:

ntfs_attr_fallocate():
  1. Take ni->runlist.lock for read.
  2. Get rl from ntfs_attr_find_vcn_nolock().
  3. Drop ni->runlist.lock.
  4. Read rl->lcn, rl->length and rl->vcn.

mmap page_mkwrite:
  1. Enter ntfs_filemap_page_mkwrite().
  2. Reach __ntfs_write_iomap_begin() and ntfs_attr_map_cluster().
  3. Merge allocation state with ntfs_runlists_merge().
  4. Reallocate ni->runlist.rl in ntfs_rl_realloc(), freeing the old array.

Validation reproduced this kernel report:
BUG: KASAN: slab-use-after-free in ntfs_attr_fallocate+0xbb8/0xd00

Call Trace:
 <TASK>
 dump_stack_lvl+0x66/0xa0
 print_report+0xce/0x630
 ? ntfs_attr_fallocate+0xbb8/0xd00
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? __virt_addr_valid+0x20d/0x410
 ? ntfs_attr_fallocate+0xbb8/0xd00
 kasan_report+0xe0/0x110
 ? ntfs_attr_fallocate+0xbb8/0xd00
 ntfs_attr_fallocate+0xbb8/0xd00
 ? lock_acquire+0x2b8/0x2f0
 ? __pfx_ntfs_attr_fallocate+0x10/0x10
 ? 0xffffffffc0000095
 ? down_write+0x10d/0x1e0
 ntfs_fallocate+0x5c9/0x1d00
 ? __pfx_ntfs_fallocate+0x10/0x10
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? lock_acquire+0x2b8/0x2f0
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? selinux_file_permission+0x3a7/0x510
 vfs_fallocate+0x29d/0xd30
 __x64_sys_fallocate+0xc7/0x150
 ? do_syscall_64+0x81/0x6a0
 do_syscall_64+0x115/0x6a0
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Allocated by task 410:
 kasan_save_stack+0x33/0x60
 kasan_save_track+0x14/0x30
 __kasan_kmalloc+0xaa/0xb0
 __kvmalloc_node_noprof+0x353/0x920
 ntfs_rl_realloc+0x3f/0x110
 ntfs_runlists_merge+0xaa3/0x3010
 ntfs_attr_map_cluster+0x4e5/0xf80
 ntfs_attr_fallocate+0x53f/0xd00
 ntfs_fallocate+0x5c9/0x1d00
 vfs_fallocate+0x29d/0xd30
 __x64_sys_fallocate+0xc7/0x150
 do_syscall_64+0x115/0x6a0
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 424:
 kasan_save_stack+0x33/0x60
 kasan_save_track+0x14/0x30
 kasan_save_free_info+0x3b/0x60
 __kasan_slab_free+0x5f/0x80
 kfree+0x307/0x580
 ntfs_rl_realloc+0x6f/0x110
 ntfs_runlists_merge+0x7b1/0x3010
 ntfs_attr_map_cluster+0x4e5/0xf80
 __ntfs_write_iomap_begin+0x8cd/0x2280
 iomap_iter+0x6de/0x11e0
 iomap_page_mkwrite+0x391/0x650
 ntfs_filemap_page_mkwrite+0x1ac/0x400
 do_page_mkwrite+0x15c/0x280
 __handle_mm_fault+0xd6d/0x1ca0
 handle_mm_fault+0x19c/0x470
 do_user_addr_fault+0x23b/0x9c0
 exc_page_fault+0x5c/0xc0
 asm_exc_page_fault+0x26/0x30

Fix this by copying the needed runlist fields while the read lock is still
held and using only those scalar snapshots after unlocking.

After the snapshot, ntfs_attr_map_cluster() can also find that the range
is already mapped and return balloc=false. Only call ntfs_dio_zero_range()
when new clusters were allocated, matching the write iomap path and
preserving the zero-newly-allocated-holes behavior.

Fixes: 495e90fa33 ("ntfs: update attrib operations")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-30 23:19:36 +09:00
Cen Zhang
81fe702ff1 ntfs: avoid stale runlist element dereference in MFT writeback
ntfs_write_mft_block() maps each $MFT record through the $MFT data
runlist. For sub-folio clusters it looks up a struct runlist_element under
ni->runlist.lock, drops the lock, and later uses rl->length and rl->vcn
when choosing folio_sz.

That pointer is only borrowed from ni->runlist.rl. Concurrent $MFT
allocation extension can merge a replacement runlist under the same lock,
and ntfs_rl_realloc() can free the old backing array. If that happens
between the lookup and the later folio_sz decision, writeback can
dereference freed runlist storage.

The buggy scenario involves two paths, with each column showing the order
within that path:

MFT writeback path:               $MFT allocation extension:
1. Look up rl under               1. Extend the $MFT data allocation.
   ni->runlist.lock.              2. Publish a replacement runlist.
2. Drop ni->runlist.lock.         3. Free the old runlist array.
3. Read rl->length and rl->vcn
   to choose folio_sz.

Compute the remaining run length while ni->runlist.lock is still held, and
use that scalar after unlock. This preserves the existing folio sizing
decision without carrying a borrowed runlist_element across the lock
boundary.

Validation reproduced this kernel report:
BUG: KASAN: slab-use-after-free in ntfs_mft_writepages+0x1c8d/0x1fb0

Call Trace:
 <TASK>
 dump_stack_lvl+0x66/0xa0
 print_report+0xce/0x630
 ? ntfs_mft_writepages+0x1c8d/0x1fb0
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? __virt_addr_valid+0x20d/0x410
 ? ntfs_mft_writepages+0x1c8d/0x1fb0
 kasan_report+0xe0/0x110
 ? ntfs_mft_writepages+0x1c8d/0x1fb0
 ntfs_mft_writepages+0x1c8d/0x1fb0
 ? __pfx_ntfs_mft_writepages+0x10/0x10
 ? __pfx___mutex_unlock_slowpath+0x10/0x10
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? iput+0x92/0xa80
 do_writepages+0x219/0x530
 ? __pfx_do_writepages+0x10/0x10
 __writeback_single_inode+0x117/0xf50
 ? do_raw_spin_lock+0x130/0x270
 ? __pfx_do_raw_spin_lock+0x10/0x10
 ? __pfx___writeback_single_inode+0x10/0x10
 ? srso_alias_return_thunk+0x5/0xfbef5
 writeback_sb_inodes+0x65b/0x1810
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? lock_acquire+0x2b8/0x2f0
 ? __pfx_writeback_sb_inodes+0x10/0x10
 ? lock_release+0x1e0/0x280
 ? _raw_spin_unlock+0x23/0x40
 ? move_expired_inodes+0x2b8/0x850
 __writeback_inodes_wb+0xf4/0x270
 ? __pfx___writeback_inodes_wb+0x10/0x10
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? queue_io+0x2e4/0x410
 wb_writeback+0x666/0x880
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? __pfx_wb_writeback+0x10/0x10
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? get_nr_dirty_inodes+0x1c/0x170
 wb_workfn+0x75e/0xbb0
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? _raw_spin_unlock_irqrestore+0x27/0x60
 ? __pfx_wb_workfn+0x10/0x10
 ? __pfx_debug_object_deactivate+0x10/0x10
 ? lock_acquire+0x2b8/0x2f0
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? lock_release+0x1e0/0x280
 process_one_work+0x8d0/0x1870
 ? __pfx_process_one_work+0x10/0x10
 ? srso_alias_return_thunk+0x5/0xfbef5
 worker_thread+0x575/0xf80
 ? __pfx_worker_thread+0x10/0x10
 kthread+0x2e7/0x3c0
 ? __pfx_kthread+0x10/0x10
 ret_from_fork+0x576/0x810
 ? __pfx_ret_from_fork+0x10/0x10
 ? srso_alias_return_thunk+0x5/0xfbef5
 ? __switch_to+0x57e/0xe10
 ? __switch_to_asm+0x33/0x70
 ? __pfx_kthread+0x10/0x10
 ret_from_fork_asm+0x1a/0x30
 </TASK>

Allocated by task 970:
 kasan_save_stack+0x33/0x60
 kasan_save_track+0x14/0x30
 __kasan_kmalloc+0xaa/0xb0
 __kvmalloc_node_noprof+0x353/0x920
 ntfs_rl_realloc+0x3c/0x80
 ntfs_runlists_merge+0x1212/0x3010
 ntfs_mft_data_extend_allocation_nolock+0x3e0/0x1f40
 ntfs_mft_record_alloc+0x1ab4/0x4f10
 __ntfs_create+0x680/0x2e50
 ntfs_create+0x1e6/0x3a0
 path_openat+0x2b55/0x3c10
 do_file_open+0x1f4/0x460
 do_sys_openat2+0xde/0x170
 __x64_sys_openat+0x122/0x1e0
 do_syscall_64+0x115/0x6a0
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 1294:
 kasan_save_stack+0x33/0x60
 kasan_save_track+0x14/0x30
 kasan_save_free_info+0x3b/0x60
 __kasan_slab_free+0x5f/0x80
 kfree+0x307/0x580
 ntfs_rl_realloc+0x66/0x80
 ntfs_runlists_merge+0x1212/0x3010
 ntfs_mft_data_extend_allocation_nolock+0x3e0/0x1f40
 ntfs_mft_record_alloc+0x1ab4/0x4f10
 __ntfs_create+0x680/0x2e50
 ntfs_create+0x1e6/0x3a0
 path_openat+0x2b55/0x3c10
 do_file_open+0x1f4/0x460
 do_sys_openat2+0xde/0x170
 __x64_sys_openat+0x122/0x1e0
 do_syscall_64+0x115/0x6a0
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Fixes: 115380f9a2 ("ntfs: update mft operations")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-25 23:12:02 +09:00
Linus Torvalds
dbad5db3a0 Description for this pull request:
- Harden handling of malformed on-disk metadata. It adds stricter
    validation for attributes, attribute lists, index roots and entries,
    EA entries, mapping pairs, and $LogFile restart areas. These changes
    fix several out-of-bounds access, integer overflow, and inconsistent
    metadata handling issues.
  - Preventing a writeback deadlock involving extent MFT records
  - Fixing resource leaks in fill_super() failure paths and the name cache
  - Serializing volume label access and improving its error handling
  - Fixing mapping-pairs decoding bounds and LCN overflow checks
  - Keeping resident index root metadata consistent during resize
  - Fixing the reported size of symbolic links
  - Avoiding an unnecessary allocation for resident inline data
  - Adds support for following and creating Windows native symbolic links.
    Relative links, absolute links, and junctions are handled, with new
    mount options controlling native symlink creation and absolute target
    translation. The existing WSL symlink behavior remains the default.
  - The unsupported quota code is removed, along with several smaller
    cleanups.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmozrD8WHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCAmED/9ZqtMSkXmdQUtmu003BUIcx+hV
 zcVnjl1bxsHeOKypkN83agxVub3wwgmskdBuSkwURrSaLYywXAodT7fzE8m1YxpD
 GnUTMB01Cyj3huhS7D0eic0k/ssHQgQHhJINb2oeb+rBEH7HmMyWE3eIN1jgFK7z
 2X2fBimldeVdW2rdmEGt3IOpklAzkbGiHc08DZIV+GkdIDfqHVCGewG/ja4elEfE
 MkdmY1bBCXgUYsqqMiuw9Ux31wAni9MMPGpMtU6/+fYICMDWG563YbzIUTGm1hnw
 I/ZLNFVhFTqQanTh7dorpLWSheHu8XDVC/+2+Sqi4TpVP/JbASd/+4Xj4ORXY1wz
 jkNhgt826L9D2/VWX+GzPZt7TM+WGEsZ6QFZVJqx+xQSjdiJa3QwCfmzM0lEi7dD
 sK3DsENn1gJ4GULTOunSXz3ReX1TKlGkd2N2VZcuotzJAn45em+ZS6s6aJoON2Oi
 WYhkMS1ANmdvx8haUtafED46VxMR1IDnRVdidmBP3CriWD9CdfBXDUFwIV96RTED
 Nj7nBCv3DycU4e9c2eXJqjKfWY5nmGk+ZNeCims87QGUuPxGYmnGexS2kJEhrEj3
 yAQ88/QY1lJUzCxdwh4x83VIn78OaEbd8axoDyNlx4hjs0ptr6EaE43pE7GHOnHP
 l8JfUxcMn2+Yo5vpzg==
 =8gI8
 -----END PGP SIGNATURE-----

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

Pull ntfs updates from Namjae Jeon:

 - Harden handling of malformed on-disk metadata.

   This adds stricter validation for attributes, attribute lists, index
   roots and entries, EA entries, mapping pairs, and $LogFile restart
   areas. These changes fix several out-of-bounds access, integer
   overflow, and inconsistent metadata handling issues.

 - Prevent a writeback deadlock involving extent MFT records

 - Fix resource leaks in fill_super() failure paths and the name cache

 - Serialize volume label access and improving its error handling

 - Fix mapping-pairs decoding bounds and LCN overflow checks

 - Keep resident index root metadata consistent during resize

 - Fix the reported size of symbolic links

 - Avoid an unnecessary allocation for resident inline data

 - Add support for following and creating Windows native symbolic links.

   Relative links, absolute links, and junctions are handled, with new
   mount options controlling native symlink creation and absolute target
   translation. The existing WSL symlink behavior remains the default.

 - The unsupported quota code is removed, along with several smaller
   cleanups

* tag 'ntfs-for-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs: (39 commits)
  docs/fs/ntfs: add mount options to support Windows native symbolic links
  ntfs: support creating Windows native symlinks
  ntfs: clean up target name conversion for WSL symlinks
  ntfs: add native_symlink mount option
  ntfs: support following Windows native symlink with absolute paths
  ntfs: support following Windows native symlink with relative paths
  ntfs: fix incorrect size of symbolic link
  ntfs: use direct pointer for inline data to avoid redundant allocation
  ntfs: validate resident index root values on lookup
  ntfs: update index root allocated size before shrink
  ntfs: grow index root value before reparent header update
  ntfs: reject non-resident records for resident-only attributes
  ntfs: fix u16 truncation of restart-area length check
  ntfs: bound the attribute-list entry in ntfs_read_inode_mount()
  ntfs: bound the look-ahead attribute-list entry in ntfs_external_attr_find()
  ntfs: validate resident attribute lists and harden the validator
  ntfs: validate resident volume name values on lookup
  ntfs: reinit search context before volume information lookup
  ntfs: do not replace volume name after lookup errors
  ntfs: validate attribute values on lookup
  ...
2026-06-19 13:23:59 -07:00
Hyunchul Lee
d8f1df2e13 ntfs: support creating Windows native symlinks
And introduce the symlink=<value> mount option to configure how symbolic
links are created. The option accepts "wsl" or "native", with "wsl"
being the default.

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-15 19:39:43 +09:00
Hyunchul Lee
7266767f67 ntfs: clean up target name conversion for WSL symlinks
WSL symlink target names are stored as narrow NLS/UTF-8 strings on
disk. Converting the target name to Unicode in ntfs_symlink and
converting it back to NLS in ntfs_reparse_set_wsl_symlink is
redundant.

Remove this conversion and pass the symname directly to the reparse
data setter.

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-15 19:39:41 +09:00
Hyunchul Lee
115446b06f ntfs: add native_symlink mount option
Because bind-mounted subtrees of the volume may resolve to unexpected
locations, change converting junctions and non-relative symbolic links
into paths relative to the NTFS volume to be allowed only if the
native_symlink=rel mount option is specified.

Add the native_symlink=<value> mount option to configure how absolute
symbolic links and mount points (junctions) are handled.
The option accepts "raw" or "rel", with "raw" being the default.

Under "raw", the absolute target path (ni->target) is returned as-is
without translation. Under "rel", ntfs_translate_junction() is called
to rewrite the absolute path as a relative path anchored at the volume
root.

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-15 19:39:39 +09:00
Hyunchul Lee
2b58f93a13 ntfs: support following Windows native symlink with absolute paths
Extend reparse-point handling beyond relative symlinks so NTFS can
expose the Windows absolute forms used by non-relative symbolic links
and junctions.
* Store the reparse tag and symlink flags in the inode.
* Validate junction payloads, and parse targets from substitute_name.
* Add function to rewrite supported Windows absolute path into Linux
  path relative to the mounted NTFS volume.

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-15 19:39:37 +09:00
Hyunchul Lee
517cd625ad ntfs: support following Windows native symlink with relative paths
Make ntfs_make_symlink() parse native Windows symbolic link reparse
payloads when the SYMLINK_FLAG_RELATIVE bit is set.
Implement the following changes:
 * Add a dedicated on-disk layout definition for symbolic link reparse
   data.
 * validate the UTF-16 name ranges before decoding them.
 * convert the substitute name into the mount's NLS and normalize path
   separators.

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-15 19:39:34 +09:00
Hyunchul Lee
05a5ff86a7 ntfs: fix incorrect size of symbolic link
This patch fixes the issue where a symbolic link size is displayed as 0.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-15 19:39:31 +09:00
Namjae Jeon
5aec1efb11 ntfs: use direct pointer for inline data to avoid redundant allocation
Previously, NTFS used page allocation for IOMAP_INLINE to ensure that
the inline_data pointer was page-aligned, avoiding strict boundary checks
in the iomap core. Since the previous patch has removed the over-strict
PAGE_SIZE boundary check in iomap, NTFS can now safely point
iomap::inline_data directly to the MFT record.
This change eliminates redundant memory allocations and memcpy operations
in both read and write paths. It also simplifies the iomap_ops by removing
the need for a iomap_end callback that was previously used to free
the temporary page.

Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-09 21:51:02 +09:00
DaeMyung Kang
fcf5bf0e85 ntfs: validate resident index root values on lookup
Resident $INDEX_ROOT values carry index header fields that callers
consume after lookup. Some callers already validate parts of the layout
before walking entries, but those checks are scattered and do not cover
all root header invariants, such as entries_offset alignment and lower
bound, index_length, and allocated_size consistency.

The resident root resize paths now keep these header fields consistent
while the value size changes: ntfs_ir_truncate() lowers
index.allocated_size before shrinking the resident value, and
ntfs_ir_reparent() grows the resident value before publishing a larger
root header. Lookup-time validation can therefore cover these invariants
without tripping over the driver's own resize paths.

Add $INDEX_ROOT to the minimum resident value size table and validate the
resident index header fields before returning the attribute from lookup.
Require 8-byte aligned index header fields, a sane entries_offset, an
index_length within allocated_size, allocated_size within the resident
value, and enough entry space for at least an index entry header.

The shared validator already rejects non-resident records for
resident-only attribute types, including $INDEX_ROOT.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-09 21:51:01 +09:00
DaeMyung Kang
e782ca90ce ntfs: update index root allocated size before shrink
ntfs_ir_truncate() currently shrinks the resident $INDEX_ROOT value first
and only updates index.allocated_size after re-looking up the attribute.
During that relookup, the resident value_length can already be smaller
while index.allocated_size still contains the old larger size.

That leaves a transiently inconsistent $INDEX_ROOT layout and prevents
lookup-time $INDEX_ROOT validation from being enabled: validation can
correctly reject allocated_size extending past the newly shrunk resident
value.

When shrinking, lower index.allocated_size before shrinking value_length.
If the truncate fails, restore the old allocated_size. Keep the existing
grow ordering because the old allocated_size remains within the enlarged
resident value until it is updated after the relookup. The shrink path is
safe because the new value_length still covers struct index_root, so the
index.allocated_size field remains present while it is updated first.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-09 21:50:59 +09:00
DaeMyung Kang
0bb508fb3b ntfs: grow index root value before reparent header update
ntfs_ir_reparent() moves the resident index root entries into an index
block and leaves a small root stub containing the child VCN. That root
stub can be larger than the existing resident value. For example, an
empty root with value_length 48 has an index area of 32 bytes, while the
large-index root stub needs index_length and allocated_size of 40 bytes.

The current code publishes the larger index.index_length and
index.allocated_size before resizing the resident value. If the resize
returns -ENOSPC, the recovery path can call ntfs_inode_add_attrlist(),
which looks attributes up again while the root header says
allocated_size 40 but the resident value still only provides 32 bytes of
index area. Lookup-time $INDEX_ROOT validation then correctly rejects
that transient layout as corrupt.

This reproduces as a generic/013 failure under qemu. In the failing run,
the transient root had value_len=48, index_size=32, index_length=40, and
allocated_size=40, and ntfsprogs-plus ntfsck reported "Corrupt index
root in MFT record 1177".

When the root stub grows, resize the resident value before publishing the
larger root header. If the resize fails, the old root remains valid for
recovery lookups. Keep the existing header-before-resize ordering for
shrink or same-size cases so the resident value never temporarily
exposes an allocated_size beyond its bounds.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-09 21:50:57 +09:00
DaeMyung Kang
097cdfd0a5 ntfs: reject non-resident records for resident-only attributes
The shared lookup-time attribute validator rejects non-resident
$FILE_NAME and $VOLUME_NAME records because their formats require
resident values and callers handle returned records as resident
attributes. Other resident-only attribute types still pass through the
generic non-resident mapping-pairs checks.

That leaves real resident/non-resident union confusion paths. Inode load
looks up $STANDARD_INFORMATION and then reads data.resident.value_offset
without checking a->non_resident. ntfs_inode_sync_standard_information()
does the same when updating the standard information value.
ntfs_write_volume_flags() also looks up $VOLUME_INFORMATION and reads
data.resident.value_offset directly. $INDEX_ROOT callers in dir.c and
index.c depend on the same lookup contract before consuming the resident
index root value.

Reject non-resident records for all resident-only attribute types in the
shared validator. Keep the existing $FILE_NAME and $VOLUME_NAME behavior,
but factor it through a helper and extend it to
$STANDARD_INFORMATION, $OBJECT_ID, $VOLUME_INFORMATION, $INDEX_ROOT, and
$EA_INFORMATION. For $OBJECT_ID and $EA_INFORMATION this is contract
hardening for resident-only formats; this patch only rejects the
non-resident form and does not add new resident value validation for
those types.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-09 21:50:55 +09:00
Bryam Vargas
390936fb15 ntfs: fix u16 truncation of restart-area length check
ntfs_check_restart_area() validates that the $LogFile restart area and
its trailing log client record array fit within the system page size:

        u16 ra_ofs, ra_len, ca_ofs;
        ...
        ra_len = ca_ofs + le16_to_cpu(ra->log_clients) *
                        sizeof(struct log_client_record);
        if (ra_ofs + ra_len > le32_to_cpu(rp->system_page_size) || ...)
                return false;

ra_len is u16, but the right-hand side is computed in size_t
(sizeof(struct log_client_record) == 160). Both ca_ofs and log_clients
come straight from the on-disk restart area. With an on-disk
log_clients of 410 the product 410 * 160 = 65600; adding ca_ofs and
storing into the u16 ra_len truncates modulo 65536 (e.g. ca_ofs 64
gives ra_len 128), so the "fits in the page" check passes even though
the client array described by log_clients extends far beyond the page.

ntfs_check_log_client_array() then walks the array bounded only by the
on-disk log_clients count:

        cr = ca + idx;
        if (cr->prev_client != LOGFILE_NO_CLIENT) ...

For log_clients 410 it dereferences records up to ca + 409 * 160,
~64 KiB past the kvzalloc(system_page_size) restart-page buffer -- an
out-of-bounds read of attacker-controlled extent, reachable when a
crafted NTFS image is mounted (load_and_check_logfile() at mount time).
This is the in-kernel analogue of CVE-2022-30789, fixed in the ntfs-3g
userspace driver but never in this revived classic driver.

Compute the restart-area length in a u32 so the existing bounds check
rejects an over-large client array instead of being defeated by the
truncation. Widen ra_ofs and ca_ofs to u32 as well: both are loaded
from __le16 on-disk fields and every comparison already promotes to
int/size_t, so this changes no result and keeps the declaration uniform.

Fixes: 1e9ea7e044 ("Revert "fs: Remove NTFS classic"")
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-08 23:04:17 +09:00
Bryam Vargas
98634df5b1 ntfs: bound the attribute-list entry in ntfs_read_inode_mount()
The $MFT attribute-list walk in ntfs_read_inode_mount() validates each
entry only with "(u8 *)al_entry + 6 > al_end" and
"(u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end", but then reads
al_entry->lowest_vcn (an __le64 at offset 8) and al_entry->mft_reference
(offset 16) -- fields beyond the 6 bytes proven in range. al_entry->length
is attacker-controlled and only required non-zero, so a short entry (e.g.
length 8) placed at the tail passes both checks while the lowest_vcn /
mft_reference reads fall past al_end.

al_end is ni->attr_list + attr_list_size (the on-disk size); the buffer is
kvzalloc(round_up(attr_list_size, SECTOR_SIZE)), so the sector rounding
usually absorbs the over-read -- but when attr_list_size is a multiple of
SECTOR_SIZE there is no slack and a crafted $MFT attribute list produces an
out-of-bounds read at mount time.

Validate the entry with ntfs_attr_list_entry_is_valid() (added in patch
1/3) before dereferencing it, matching the bound the other attribute-list
walks now use. The validator already requires the length to cover the fixed
header, which makes the separate "!al_entry->length" check redundant, so
drop it too.

Fixes: 1e9ea7e044 ("Revert "fs: Remove NTFS classic"")
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-08 22:58:05 +09:00
Bryam Vargas
344b18f389 ntfs: bound the look-ahead attribute-list entry in ntfs_external_attr_find()
When resolving an attribute lookup with a non-zero @lowest_vcn,
ntfs_external_attr_find() peeks at the next $ATTRIBUTE_LIST entry to
decide whether to keep searching, but bounds that not-yet-validated
entry only with "(u8 *)next_al_entry + 6 < al_end" (which proves just
bytes 0..6 are in range) and "(u8 *)next_al_entry + length <= al_end"
with an attacker-controlled, non-8-aligned length. It then reads
next_al_entry->lowest_vcn (an __le64 at offset 8) and the name at
next_al_entry->name_offset, both of which can lie past al_end -- the
exact end of the kvmalloc'd attribute-list buffer (allocated at the
on-disk attr_list_size, no rounding). A crafted on-disk $ATTRIBUTE_LIST
whose last entry sits a few bytes before al_end therefore yields a slab
out-of-bounds read when the inode is read.

Validate the look-ahead entry with ntfs_attr_list_entry_is_valid() (added
in patch 1/3) before dereferencing lowest_vcn and the name, so the same
fixed-header, length and name bounds the main attribute-list walk uses now
guard this read too.

Fixes: 1e9ea7e044 ("Revert "fs: Remove NTFS classic"")
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-08 22:58:00 +09:00
Bryam Vargas
7d19e1ffee ntfs: validate resident attribute lists and harden the validator
A base inode's $ATTRIBUTE_LIST is sanity-checked by load_attribute_list()
only on the non-resident path; ntfs_read_locked_inode() copies a *resident*
attribute list into ni->attr_list with a plain memcpy() and no validation
at all. Every subsequent walk of ni->attr_list --
ntfs_external_attr_find(), ntfs_inode_attach_all_extents() and
ntfs_attrlist_need() -- then trusts the entries are well-formed and reads
attr_list_entry fixed-header fields
(lowest_vcn at offset 8, mft_reference at offset 16, and the name) with
bounds that assume validation already happened. A crafted resident
attribute list therefore reaches those walks unvalidated and can drive
out-of-bounds reads of the attribute-list buffer.

load_attribute_list() itself reads ale->name_offset (offset 7),
ale->mft_reference (offset 16) and the name length under only an
"al < al_start + size" bound, so its own validation loop can over-read the
fixed header of a truncated trailing entry by a few bytes.

Factor the per-entry validation into ntfs_attr_list_entry_is_valid(),
which requires each entry's fixed header (offsetof(struct
attr_list_entry, name)) to be in range before any field is dereferenced,
that ale->length is a multiple of 8 covering the fixed header plus the
name, and that the entry is in use and carries a live MFT reference.
ntfs_attr_list_is_valid() walks the buffer with it and checks the entries
tile it exactly. Use the list validator in load_attribute_list()
(replacing the open-coded loop, closing its own over-read) and on the
resident path in ntfs_read_locked_inode() (which previously skipped
validation entirely); patches 2/3 reuse the per-entry helper at the other
two attribute-list walks.

Fixes: 1e9ea7e044 ("Revert "fs: Remove NTFS classic"")
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-08 22:57:55 +09:00
Al Viro
02975a536d ntfs: use d_splice_alias() for ->lookup() return value
Reviewed-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2026-06-06 17:12:21 -04:00
DaeMyung Kang
b3f6cd1d54 ntfs: validate resident volume name values on lookup
The shared lookup-time attribute validator now has a safe caller path for
$VOLUME_NAME corruption: ntfs_write_volume_label() no longer treats
lookup errors as an absent label, and the mount path reinitializes its
search context before continuing to $VOLUME_INFORMATION.

Add $VOLUME_NAME-specific resident value validation. A volume name is
stored as a UTF-16LE string, so reject odd byte lengths, and reject
values longer than the NTFS volume label limit. Empty labels remain
valid.

Also reject non-resident $VOLUME_NAME records. $VOLUME_NAME is required
to be resident, like $FILE_NAME; a crafted non-resident record would
otherwise pass lookup and ntfs_write_volume_label() would remove it as if
it were a normal resident attribute.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:38 +09:00
DaeMyung Kang
45dd046ced ntfs: reinit search context before volume information lookup
On mount the volume inode is searched for $VOLUME_NAME and then, reusing
the same search context, for $VOLUME_INFORMATION. The $VOLUME_NAME lookup
is optional and its result is otherwise ignored.

Once lookup-time validation can reject a corrupt $VOLUME_NAME with -EIO,
the search context is left in an undefined state: ntfs_attr_find()
documents that on an actual error @ctx->attr is undefined. Continuing the
$VOLUME_INFORMATION search from that context is not contractually valid.

Reinitialize the search context before the $VOLUME_INFORMATION lookup so
it always starts from a well-defined state regardless of the
$VOLUME_NAME lookup outcome.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:36 +09:00
DaeMyung Kang
40d88020d0 ntfs: do not replace volume name after lookup errors
ntfs_write_volume_label() removes an existing $VOLUME_NAME attribute and
then adds the replacement. The old code only distinguished lookup success
from all other results, so any lookup error was treated like an absent
label and the add path still ran.

That is unsafe once lookup-time validation rejects corrupt $VOLUME_NAME
records with -EIO: the corrupt record would remain in place and a second
$VOLUME_NAME record could be appended next to it.

Only add the replacement after the old label was removed successfully or
after lookup returned -ENOENT. Propagate all other lookup errors, and
also stop if removing the old attribute fails.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:33 +09:00
DaeMyung Kang
d5803e3345 ntfs: validate attribute values on lookup
ntfs_attr_find() and ntfs_external_attr_find() check that generic
resident attribute values fit in their attribute records and that
fixed-size resident values are large enough. For variable-length resident
formats, however, the fixed part is not enough: embedded length fields
can still point callers past the resident value.

A crafted image can set a small resident $FILE_NAME value_length while
leaving file_name_length large. Callers then trust file_name_length and
read past the resident value when converting or comparing the name. This
was reproduced with a crafted image under KASAN as a slab-out-of-bounds
read from the kmalloc-1k MFT record copy. The stack included
ntfs_lookup(), ntfs_iget(), ntfs_read_locked_inode(), ntfs_attr_name_get(),
ntfs_ucstonls(), and utf16s_to_utf8s().

Add a shared attribute value validator and use it before a lookup path
can return an attribute, including the AT_UNUSED enumeration case where
callers inspect returned attributes directly. The helper validates
resident value bounds, minimum resident value sizes, variable-length
$FILE_NAME fields, and non-resident mapping-pairs metadata that was
previously checked separately in both lookup paths.

This also preserves the intended resident @val matching semantics in the
external attribute lookup path. The old duplicated validation block
overwrote the actual resident value length with the type-specific minimum
length before comparing @val, so variable-length resident values could
fail to match even when the bytes were identical. Keep the comparison on
the actual value length, and make ntfs_attrlist_entry_add() compare
resident attributes with lowest_vcn zero instead of reading the
non-resident union member after a successful resident match.

Reject non-resident $FILE_NAME records too: the format requires
$FILE_NAME to be resident and callers treat returned records as resident.

Cc: stable@vger.kernel.org # v7.1
Fixes: 6ceb4cc81e ("ntfs: add bound checking to ntfs_attr_find")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:28 +09:00
Samuel Moelius
ec4f061f22 ntfs: detect mapping-pairs LCN accumulator overflow
The NTFS mapping-pairs parser accumulates relative LCN deltas in a
signed integer.  A corrupted attribute can drive that addition past
the representable range.

One corrupt runlist shape sets the accumulated LCN to S64_MAX and
then adds a delta of 1 in the next mapping-pairs entry.

Signed overflow is undefined and can turn an invalid runlist into a
different set of physical clusters.

Check the LCN addition for overflow before storing the next run.

Cc: stable@vger.kernel.org # v7.1
Assisted-by: Codex:gpt-5.5-cyber-preview
Signed-off-by: Samuel Moelius <sam.moelius@trailofbits.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:26 +09:00
Marco Crivellari
38e8db3708 ntfs: Add WQ_PERCPU to alloc_workqueue users
This continues the effort to refactor workqueue APIs, which began with
the introduction of new workqueues and a new alloc_workqueue flag in:

  commit 128ea9f6cc ("workqueue: Add system_percpu_wq and system_dfl_wq")
  commit 930c2ea566 ("workqueue: Add new WQ_PERCPU flag")

The refactoring is going to alter the default behavior of
alloc_workqueue() to be unbound by default.

With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND),
any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND
must now use WQ_PERCPU. For more details see the Link tag below.

In order to keep alloc_workqueue() behavior identical, explicitly request
WQ_PERCPU.

Cc: stable@vger.kernel.org # v7.1
Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/
Suggested-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:24 +09:00
Hyunchul Lee
e9e50ce4f1 ntfs: serialize volume label accesses
Protect vol->volume_label with a mutex and snaphost the label before
copy_to_user. This prevent a use-after-free when FS_IOC_SETFSLABEL
replaces the vol->volume_label and FS_IOC_GETTSLABEL reads it
concurrently.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:20 +09:00
Ron de Bruijn
18760a74ef ntfs: fix off-by-one in mapping pairs decoding bounds checks
In ntfs_mapping_pairs_decompress(), attr_end points one byte past the
end of the attribute record:

    attr_end = (u8 *)attr + le32_to_cpu(attr->length);

The two bounds checks validating that mapping pair data bytes fit within
the attribute use strict greater-than (>), which allows a one-byte
out-of-bounds read when the data extends exactly to attr_end:

  b = *buf & 0xf;
  if (b) {
      if (unlikely(buf + b > attr_end))   // off-by-one
          goto io_error;
      for (deltaxcn = (s8)buf[b--]; b; b--)
          deltaxcn = (deltaxcn << 8) + buf[b];
  }

When buf + b == attr_end, the check evaluates to false and buf[b] reads
one byte past the valid attribute boundary. The same pattern appears in
the LCN delta bytes check.

Fix both checks to use >= so that buf[b] at exactly attr_end is
correctly rejected as out of bounds.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: Ron de Bruijn <rmbruijn@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:17 +09:00
Hyunchul Lee
0aad215701 ntfs: not change 0-byte $DATA attribute to non-resident
When ntfs_resident_attr_resize() cannot grow a resident attribute in
place, it retries after converting other resident attributes to
non-resident to free space in the MFT recrord.

Do not select zero-length resident $DATA attributes for this conversion.
fsck treats 0-byte non-resident $DATA attribute as corruptions.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:15 +09:00
Colin Ian King
9974fe1db2 ntfs: Fix spelling mistake "etnry" -> "entry"
There is a spelling mistake in a ntfs_error message. Fix it.

Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:12 +09:00
DaeMyung Kang
8488c4d066 ntfs: free link name from ntfs_name_cache
ntfs_link() converts the new link name with ntfs_nlstoucs() using
NTFS_MAX_NAME_LEN. In this case ntfs_nlstoucs() allocates the result
from ntfs_name_cache, and its contract requires callers to release the
buffer with kmem_cache_free(ntfs_name_cache, ...).

All other ntfs_nlstoucs() callers in namei.c do that, but ntfs_link()
uses kfree(), which mismatches the allocator for successfully converted
names.

The conversion failure path reaches the common out label with uname ==
NULL. That was harmless for kfree(), but kmem_cache_free() does not
provide the same NULL contract. Return directly on conversion failure
and free successful conversions with ntfs_name_cache.

Fixes: af0db57d42 ("ntfs: update inode operations")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:10 +09:00
Namjae Jeon
3964169d68 ntfs: remove unnecessary NULL checks before kfree
NULL check before kfree() is unnecessary and triggers coccinelle warnings.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:08 +09:00
Namjae Jeon
949ba99cfe ntfs: remove unnecessary ternary boolean conversion
Coccinelle warned about unnecessary  patterns when
assigning to bool variables.

Simply assign the condition directly.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:06 +09:00
DaeMyung Kang
cbe287fd65 ntfs: remove unsupported quota handling
The ntfs driver does not implement quota accounting.  It creates
new inodes with the NTFS 1.2 $STANDARD_INFORMATION layout and does
not maintain the NTFS 3.x owner_id/quota_charged fields or the
$Quota usage records that Windows would need for meaningful quota
accounting.

The only runtime quota path left in the driver is the remount-rw
code that tries to mark $Quota/$Q out of date, plus the mount-time
code that loads $Quota and its $Q index solely to support that
marker.

Since the driver does not maintain the per-file quota metadata,
setting QUOTA_FLAG_OUT_OF_DATE does not make the quota state
meaningful, and failures in this unsupported path can unnecessarily
block remount-rw or force a mount read-only.

Remove the quota marker, the $Quota/$Q loading state, and the
unused quota volume flag.  Keep the on-disk quota layout definitions
in layout.h so the documented NTFS structures remain available.

Suggested-by: Hyunchul Lee <hyc.lee@gmail.com>
Link: https://lore.kernel.org/all/CANFS6bYTzioqZjYt=51Kb9RdR3MKXaez_fh_WCLoym093VxFmg@mail.gmail.com/
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:03 +09:00
Thorsten Blum
0283841f0c ntfs: use str_plural in ntfs_attr_make_non_resident
Replace the manual ternary "s" pluralization with str_plural() to
simplify the code.

Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:20:01 +09:00
Hyunchul Lee
937282f7d1 ntfs: add bounds check before accessing EA entries
in ntfs_ea_lookup and ntfs_listxattr, this verifies that there is enough
space in the EA entry before accessing the next_entry_offset field of
the EA entry.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:19:59 +09:00
Hyunchul Lee
2221b691d7 ntfs: validate index entries on reading
Validate index entries immediately after reading an index root or index
block from disk. This eliminates repeated checks in lookup and readdir,
and reduce the risk of missing checks in those paths.

Cc: stable@vger.kernel.org # v7.1
Tested-by: woot000 <woot000@woot000.com>
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:19:56 +09:00
Hyunchul Lee
8b97b302f5 ntfs: centalize $INDEX_ROOT header validation
Add a dedicated helper to perform stricter validation of $INDEX_ROOT and
use it for both directory inodes and named index inodes. This keeps the
root size and header geometry checks consistent across both read paths.

Cc: stable@vger.kernel.org # v7.1
Tested-by: woot000 <woot000@woot000.com>
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:19:54 +09:00
Hyunchul Lee
14bc34fe94 ntfs: validate index block header more strictly
Modify ntfs_index_block_inconsisent() to perform stricter validation of
INDEX_HEADER geometry in INDX blocks, and update
ntfs_lookup_inode_by_name() to use that function to validate INDX
blocks.

Cc: stable@vger.kernel.org # v7.1
Tested-by: woot000 <woot000@woot000.com>
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:19:52 +09:00
DaeMyung Kang
c05132077d ntfs: avoid heap allocation for free-cluster readahead state
get_nr_free_clusters() allocates a temporary file_ra_state before it
publishes the precomputed free cluster count, sets NVolFreeClusterKnown(),
and wakes vol->free_waitq. If that allocation fails, the worker returns
without setting the flag or waking waiters, so callers waiting for the free
count can block indefinitely.

The readahead state is only used synchronously while scanning the bitmap.
Keep it on the stack and pass it by address to the readahead helper. This
eliminates the early allocation failure path instead of adding a special
case that publishes a conservative count and wakes the waitqueue.
Zero-initialize the on-stack state because file_ra_state_init() only sets
ra_pages and prev_pos.

Apply the same treatment to __get_nr_free_mft_records(), which scans the
MFT bitmap with the same short-lived readahead state.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:19:49 +09:00
Hyunchul Lee
76bc14c709 ntfs: skip extent mft records in writeback to prevent deadlock
This patch fixes the ABBA deadlock between extent_lock and extent
mrec_lock triggered by xfstests generic/113, that occurs since the commit
6994acf33bae ("ntfs: use base mft_no when looking up base inode for
		extent record").

Path A (inode writeback):
  VFS writeback
    -> ntfs_write_inode()
      -> __ntfs_write_inode()
        -> mutex_lock(&ni->extent_lock)
        -> mutex_lock(&tni->mrec_lock)

Path B (MFT folio writeback):
  VFS writeback of $MFT dirty folios
    -> ntfs_mft_writepages()
      -> ntfs_write_mft_block()
        -> ntfs_may_write_mft_record()
          -> holds one extent mrec_lock from a previous iteration
          -> tries to acquire another base inode extent_lock

By removing all extent_lock and extent mrec_lock acquisition from the MFT
folio writeback path, the ABBA lock ordering is eliminated:

Path A: __ntfs_write_inode(): extent_lock -> mrec_lock
Path B (removed): ntfs_write_mft_block(): mrec_lock -> extent_lock

Path B is always redundant for extent records because:

1. mark_mft_record_dirty(ext_ni) does NOT dirty the MFT folio.
   It only sets NInoDirty(ext_ni) and marks the base VFS inode dirty
   via __mark_inode_dirty(I_DIRTY_DATASYNC), which triggers Path A.
   Therefore, normal extent modifications never create a situation where
   the MFT folio is dirty and Path B is not scheduled.

2. The MFT folio only gets dirtied via ntfs_mft_mark_dirty() inside
   ntfs_mft_record_alloc(). But all identified callers in attrib.c
   (ntfs_attr_add, ntfs_attr_record_move_away,
   ntfs_attr_make_non_resident, ntfs_attr_record_resize) follow through
   with mark_mft_record_dirty(), which triggers Path A to write the
   complete record.

3. ntfs_evict_big_inode() calls ntfs_commit_inode() before freeing extent
   inodes, ensuring all dirty extents are flushed via Path A before the
   base inode leaves the icache.

Cc: stable@vger.kernel.org # v7.1
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-06-06 00:19:47 +09:00