summaryrefslogtreecommitdiff
path: root/fs/ntfs
AgeCommit message (Collapse)Author
3 daystreewide: refresh kmalloc_obj() conversionsKees Cook
This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook <kees+treewide@kernel.org>
8 daysntfs: take invalidate_lock in ntfs_filemap_page_mkwrite()Hongling Zeng
ntfs_filemap_page_mkwrite() calls iomap_page_mkwrite() without holding mapping->invalidate_lock, so a concurrent truncate or fallocate can be in the middle of invalidating pagecache and rewriting the runlist while the write fault maps blocks and dirties the folio. This races with ntfs_attr_fallocate(), which merges clusters into the in-memory runlist, drops the runlist lock, and only afterwards zeroes the newly allocated clusters on disk; and with the punch-hole/insert/collapse paths that free clusters after truncating the cache. Per Documentation/filesystems/locking.rst, ->page_mkwrite() must ensure there are no truncate/invalidate races, "usually mapping->invalidate_lock is suitable for proper serialization". xfs takes its mmaplock (= the invalidate_lock rwsem) shared in exactly this path. Take invalidate_lock shared around iomap_page_mkwrite(). The read-only fault path is already covered because filemap_fault() itself grabs invalidate_lock shared on instantiation/read paths; only page_mkwrite was bypassing it in this driver. Fixes: 9c87959601e8 ("ntfs: update file operations") Cc: stable@vger.kernel.org Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Reviewed-by: Baolin Liu <liubaolin@kylinos.cn> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Co-developed-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
8 daysntfs: take invalidate_lock in ntfs_setattr_size()Hongling Zeng
ntfs_setattr_size() updates i_size and resizes the on-disk attribute without holding mapping->invalidate_lock. Page faults take the lock shared, so a fault racing the resize can resolve a VCN against the transient runlist state of ntfs_non_resident_attr_expand() and fail with a spurious SIGBUS, and can interleave with the size-change epilogue (truncate_pagecache(), i_size_write(), pagecache_isize_extended()). Take invalidate_lock exclusively around the whole resize after inode_dio_wait(), matching the fallocate path and other filesystems such as xfs, which wraps truncate in its mmaplock (= invalidate_lock). Fixes: 9c87959601e8 ("ntfs: update file operations") Cc: stable@vger.kernel.org Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Reviewed-by: Baolin Liu <liubaolin@kylinos.cn> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
8 daysntfs: handle signal interruption in fallocateHongling Zeng
The ntfs_attr_fallocate() function checks for pending signals during allocation loops and exits early via 'out' label. However, when a signal interrupts the operation with err == 0, the function returns 0 (success) instead of -EINTR. The signal_pending() checks at the allocation loops jump to 'out' without setting err = -EINTR, so the function returns success even when interrupted by a signal. Set err = -EINTR when jumping to the signal exit path, and only override when no other error is pending. This ensures: - Allocation interrupted by signal returns -EINTR - Allocation that completed successfully before signal arrived returns 0 - Other errors are preserved and not overwritten by -EINTR Fixes: 495e90fa3348 ("ntfs: update attrib operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Reviewed-by: Baolin Liu <liubaolin@kylinos.cn> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
9 daysntfs: fix FITRIM range alignmentJacopo Labardi
ntfs_trim_fs() aligns the start of a free extent up to the device discard granularity, but derives the discard length by aligning the original extent length down. When the free extent start is not discard-aligned, adding that length to the aligned start can extend the discard past the free extent and into allocated clusters. For example, with 4 KiB clusters and 32 KiB discard granularity, the free extent [4 KiB, 36 KiB) becomes the discard range [32 KiB, 64 KiB), so 28 KiB beyond the free extent may be discarded. Align the absolute end of the free extent down and derive the length from the two aligned endpoints. Skip extents that contain no full discard unit. Reproduced with a 4 KiB-cluster NTFS filesystem on scsi_debug configured for 32 KiB discard granularity and read-zero-after-trim. Before this change, FITRIM zeroed seven allocated 4 KiB clusters following an unaligned 32 KiB hole. With this change, the same data remains intact across FITRIM and remount. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Cc: stable@vger.kernel.org Assisted-by: OpenAI Codex:GPT-5.6 Sol Max Signed-off-by: Jacopo Labardi <jacopolabardi@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
9 daysntfs: read WOF chunks outside the decompression lockZhan Xusheng
WOF decompression uses four module-global workspaces, one per compression format, each with a static mutex. ntfs_read_wof_compressed_block() takes that mutex once and holds it across the whole chunk loop, so both block reads run inside it: mutex_lock(ws->lock); for each chunk { parse_wof_chunk_table(..., ws->input, ...); /* reads disk */ ntfs_read_wof_chunk(..., ws->input, ...); /* reads disk */ decompress into ws->output; } mutex_unlock(ws->lock); Readers of system-compressed files then serialise system-wide on the disk waits, not just on the decompressor scratch the lock exists for. One reader sleeping in submit_bio_wait() blocks all the rest. The waits dominate. Reading an 8 MiB xpress4k file (2048 chunks at a 48% compressed ratio, so 2048 acquisitions and 4096 block reads) and timing ws->lock against the part of it spent in ntfs_bdev_read(): backing store held of that in I/O held after virtio, host page cache 348 ms 321 ms (92%) 24.6 ms virtio, throttled 100 MB/s 978 ms 948 ms (96%) 36.6 ms The page-cache row is a lower bound, having no seek cost at all, and the share still grows with slower storage because only the wait scales while decompression stays near 26 ms. The reads are inside the lock only because they land in ws->input, a buffer shared through the workspace. Nothing else requires it: parse_wof_chunk_table() and ntfs_read_wof_chunk() already take the buffer as a parameter and both set *chunk_mem to a pointer inside it, so a caller-owned buffer works unchanged. Allocate that buffer per call, do both reads without the lock, and take the lock only around decompression, which is the step needing ws->output and ws->scratch. squashfs is arranged this way already: its squashfs_decompress() is handed a bio that has been read, and locks only for the CPU work. Block reads are unchanged in number, they just no longer run under the lock, and hold time stops tracking device speed. This also unnests two per-inode locks from the global one, runlist->lock taken by both reads and base_ni->mrec_lock taken for a resident stream. A resident chunk needs no I/O at all, yet used to queue behind a reader blocked in submit_bio_wait() and then take mrec_lock inside the global mutex. The buffer is 4608 bytes for xpress4k and at most 33280 for lzx32k. This path already does GFP_NOFS allocations per call in ntfs_attr_iget(), and in ntfs_attr_get_search_ctx() for a resident stream, so one more does not change how it behaves under memory pressure. The workspace keeps output and scratch, 4 KiB to 32 KiB and 6224 bytes (xpress) or 10240 (lzx), and its "already allocated" test moves from ws->input to ws->output. The lock is now taken per chunk rather than per call, which differs only for a folio spanning several chunks: a few more uncontended mutex operations in exchange for not holding it across the reads between them. Verified under QEMU against an uncompressed copy of the same data, on an 8 MiB file and a 100000 byte one, the latter covering the tail chunk that is not a full comp_unit. Signed-off-by: Zhan Xusheng <zhanxusheng@xiaomi.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
12 daysntfs: leave HasEA flag untouched on setxattr failureBaolin Liu
In ntfs_set_ea(), the exit path unconditionally updates the HasEA flag based on ea_info_qsize. When an error occurs before ea_info_qsize is updated, NInoClearHasEA() hides existing on-disk EAs until the inode is evicted. Only update the flag on success. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
12 daysntfs: fix race between fallocate and mmap readsHongling Zeng
The fallocate implementation only takes invalidate_lock for punch hole, collapse range, and insert range operations. For standard allocation modes (mode == 0, FALLOC_FL_KEEP_SIZE), the lock is not held. During ntfs_attr_fallocate(), new clusters are mapped to the runlist via ntfs_attr_map_cluster() before being zeroed by ntfs_dio_zero_range(). This creates a window where concurrent mmap page faults can read uninitialized disk data. Since mmap uses filemap_fault() which takes invalidate_lock in shared mode, it can fault in pages during this window and expose old disk contents to userspace. This is an information leak and data integrity issue. Fix by taking invalidate_lock for all fallocate operations, not just for punch/collapse/insert modes. This prevents concurrent page faults from accessing unzeroed clusters during the allocation window. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Cc: stable@vger.kernel.org Reviewed-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
12 daysntfs: fix memmove overlap in ntfs_new_attr_flagsHongling Zeng
When the record shrinks while the payload offsets increase (e.g., enabling compression reduces padding, making arec_size < old_arec_size, but the header grows by 8 bytes), moving the name first can overwrite the old mapping_pairs before they are copied. Move mapping_pairs first in this case. Since mp_ofs is derived from name_ofs, they always change in the same direction. Checking name_ofs alone is sufficient. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
12 daysntfs: compute bi_sector in 512-byte unitsDennis Tighe
bi_sector counts in 512 byte sectors and not in multiples of the volume's sector size. Under "normal" circumstances (with 512 byte sectors in NTFS) the current code works as is; however, when we have a 4k sector size on the volume the current usage of NTFS_B_TO_SECTOR() and ntfs_bytes_to_sector() end up converting to the number of 4k sectors after mount. Reads work today on 4k volumes as bdev-io.c as performing the shift correctly inline. With writes, we end up with significant silent disk corruption on these volumes. This fixes changes to use the new ntfs_bytes_to_bio_sector() function everywhere we're performing this calculation (including the existing read path). For the change in inode.c it removes a dead code block rather than updating. Fixes: 40796051991d ("ntfs: update in-memory, on-disk structures and headers") Assisted-by: Claude:claude-opus-5 Signed-off-by: Dennis Tighe <dennis.tighe@gmail.com> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: reject invalid sectors_per_cluster in the boot sectorDennis Tighe
is_boot_sector_ntfs() checks the boot sector's sectors_per_cluster field with a range test that rejects 0x81..0xf3 but accepts 0 and other non-power-of-two counts. A zero value reaches parse_ntfs_boot_sector(): sectors_per_cluster_bits = ffs(sectors_per_cluster) - 1; ... vol->cluster_size = vol->sector_size << sectors_per_cluster_bits; ffs(0) is 0, so sectors_per_cluster_bits becomes (unsigned)-1 and the shift is undefined: UBSAN: shift-out-of-bounds in fs/ntfs/super.c:673:39 shift exponent 4294967295 is too large for 32-bit type 'int' This change rejects any non-power-of-two value, since it feeds the aforementioned shift via ffs() - 1, which only yields the correct shift for a power of two. Fixes: 6251f0b0de7d ("ntfs: update super block operations") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe <dennis.tighe@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: bound $AttrDef table walk to the loaded table sizeDennis Tighe
ntfs_attr_find_in_attrdef() walks the in-memory $AttrDef table, but the loop condition bounds only the start of each entry, not the whole entry: for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef < vol->attrdef_size && ad->type; ++ad) struct attr_def is 160 bytes; the guard reads ad->type at offset 128 and the loop body reads further fields. vol->attrdef is kvzalloc(i_size), where i_size is the on-disk $AttrDef data size, checked in load_and_init_attrdef() only as 0 < i_size <= 0x7fffffff. A volume whose $AttrDef data size is smaller than one entry (e.g. 120 bytes) makes the read of ad->type run past the allocation. Creating a file reaches this through ntfs_attr_size_bounds_check() and reads out of bounds: BUG: KASAN: slab-out-of-bounds in ntfs_attr_find_in_attrdef+0x66/0xa0 Read of size 4 at addr ffff888005833280 by task init/1 ntfs_attr_find_in_attrdef ntfs_attr_size_bounds_check ntfs_attr_can_be_non_resident ntfs_attr_add Require the whole entry to lie within attrdef_size in the loop guard, and reject at mount a $AttrDef too small to hold one attr_def entry. Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe <dennis.tighe@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: fix undefined behavior in mft/index record size calculationHongling Zeng
The boot sector validation allows clusters_per_mft_record and clusters_per_index_record to range from 0xE1 (-31) to 0xF7 (-9) when interpreted as signed values. When these are used as negative shift counts in expressions like `1 << -clusters_per_mft_record`, values like 0xE1 cause `1 << 31`, which shifts into the sign bit of a 32-bit signed integer, resulting in undefined behavior. Fix by using unsigned shift (1U << ...) instead of signed shift. This prevents undefined behavior while preserving the full valid range of negative values (-31 to -9) that may appear in NTFS boot sectors. The encoding scheme uses negative values to represent record sizes smaller than cluster_size: -log2(record_size). Common values include -10 (1024 bytes) for mft_record_size and -12 (4096 bytes) for index_record_size. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Reviewed-by: Baolin Liu <liubaolin@kylinos.cn> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: treat any nonzero dio zero-range return as an errorWentao Guan
ntfs_dio_zero_range() returns either 0 or a negative errno from blkdev_issue_zeroout(); it never returns a positive value. The zeroing failure check in ntfs_attr_fallocate() therefore never fired, so a failed zeroing operation was silently ignored: the loop kept going, the newly allocated clusters were folded into initialized_size and the write could succeed leaving stale on-disk data. Treat any nonzero return as an error and abort the allocation. Fixes: 495e90fa33482 ("ntfs: update attrib operations") Assisted-by: atomcode:deepseek-v4-flash Signed-off-by: Wentao Guan <guanwentao@uniontech.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: fix incorrect MFT record pointer passed to ntfs_attr_record_resizeHongling Zeng
ntfs_new_attr_flags() passes the wrong MFT record to ntfs_attr_record_resize(). When the attribute is in an extent record, ctx->mrec points to the extent but the function receives the base record pointer m, causing incorrect size calculations in memmove. Fix by passing ctx->mrec (the actual MFT record containing the attribute) instead of m (the base MFT record) to ntfs_attr_record_resize(). Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: do not mark the volume clean in sync_fs when errors were recordedDennis Tighe
ntfs_put_super() and the remount-read-only path both clear the dirty bit only when NVolErrors(vol) is false. ntfs_sync_fs() clears it unconditionally, so any sync() on a volume that recorded an error marks that volume clean. A volume without this set is then seen as not needing recovery and it does not run one, so whatever went wrong is never repaired. This change skips resetting the dirty bit when there are volume errors. Reproduced on a volume whose $MFTMirr does not match $MFT, which sets the error flag while leaving the mount read-write: after a write and a sync, the on-disk volume flags read 0x0000 with this driver and 0x0001 with the guard in place. Fixes: 6251f0b0de7d ("ntfs: update super block operations") Assisted-by: claude:claude-opus-5 Signed-off-by: Dennis Tighe <dennis.tighe@gmail.com> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: skip free cluster decrement when rollback failsBaolin Liu
When the rollback in __ntfs_cluster_free() fails, the recursive call returns a negative errno and the subsequent ntfs_dec_free_clusters(vol, delta) subtracts that negative value, adding bogus clusters to the counter on an already-failing volume. Skip the decrement when the rollback failed. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: only count successfully cleared runs when freeing clustersBaolin Liu
ntfs_cluster_free_from_rl_nolock() adds a run's length to nr_freed whenever the error bookkeeping condition is false, which includes cases where ntfs_bitmap_clear_run() actually failed - e.g. a second run failing with the same errno as an earlier one, or any failure after a non-ENOMEM error was already recorded. Since a failed ntfs_bitmap_clear_run() rolls back its partial modifications, no bits were cleared for that run, yet its length still inflates vol->free_clusters, corrupting statfs output and the allocator's free space gate. Only count runs whose bitmap clear succeeded. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: fix kmap_local leak in write_mft_record_nolock() error pathsBaolin Liu
write_mft_record_nolock() maps the MFT record folio with kmap_local_folio(), but the pre_write_mst_fixup() and bio_add_folio() failure paths jump to the error label without unmapping it. kmap_local mappings are stack-ordered per task, so leaking one corrupts the nesting for any outer mapping. Unmap the folio on those error paths too. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: return real error from ntfs_non_resident_attr_record_add()Baolin Liu
ntfs_non_resident_attr_record_add() returns -1 at its put_err_out label, which callers propagate as -EPERM to userspace. Return the actual error code. Every path reaching the label has err set to a negative errno. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: preserve error code in ntfs_resident_attr_record_add()Baolin Liu
ntfs_resident_attr_record_add() collapses every failure to -EIO at its put_err_out label. This defeats the resident-to-non-resident fallback in ntfs_attr_add(), which relies on seeing -ENOSPC to convert the attribute when the MFT record has no room, and also hides -EEXIST and -ENOMEM from callers. Return the actual error code. Every path reaching the label has err set to a negative errno. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: return -ERANGE for undersized xattr bufferBaolin Liu
When the value buffer passed to getxattr(2) for system.dos_attrib, system.ntfs_attrib or system.ntfs_attrib_be is smaller than the attribute value, ntfs_getxattr() returns -ENODATA, which tells userspace the attribute does not exist. The xattr API expects -ERANGE in this case, and ntfs_get_ea() in the same file already returns -ERANGE for regular EAs. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: propagate reparse index insertion failureBaolin Liu
update_reparse_data() ignores the return value of set_reparse_index(). When index insertion fails, the code removes the just-written reparse data as cleanup but still returns 0, so symlink(2) (and WSL special file creation) reports success while no reparse data exists on disk. When there was no previous reparse data (oldsize == 0), the failure was likewise silently ignored. Propagate the error to the caller. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
13 daysntfs: return DT_UNKNOWN on inode lookup failure in readdirBaolin Liu
ntfs_reparse_tag_dt_types() returns PTR_ERR(vi) when ntfs_iget() fails, but its return type is unsigned int and the caller passes the value straight to dir_emit() as d_type. A stale or corrupt MFT reference in a directory index thus makes readdir report a garbage d_type value to userspace. Return DT_UNKNOWN on lookup failure instead. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: support resident WOF decompressionHyunchul Lee
Extend WOF decompression to support files where the reparse named data attribute or the compressed chunks themselves are resident. Retrieve resident metadata using ntfs_attr_lookup() and copy compressed chunks directly from the resident attribute payload. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: add non-resident WOF decompressionHyunchul Lee
Introduce non-resident Windows System Compression (WOF) decompression support. Add wof.c containing parse_wof_chunk_table() and ntfs_read_wof_compressed_block(), and routing them via transparent codec ops table with dynamic scratch memory allocation. Hook up ntfs_readpage/read_folio paths in aops.c to delegate to the WOF block reader when NInoWofCompressed is set. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: implement codec ops for LZX and XPRESSHyunchul Lee
Implement the transparent compression codec ops for XPRESS (4K, 8K, 16K) and LZX (32K) algorithms. The xpress_scratch_size, lzx_scratch_size, xpress_decompress_chunk, and lzx_decompress_chunk wrappers provide unified interfaces and use per-call dynamic scratch state allocation (avoiding global mutexed singletons). Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: port lzx/xpress decompressors from ntfs-3g-system-compressionHyunchul Lee
Port the LZX and XPRESS decompressors from the userspace ntfs-3g-system-compression plugin (Eric Biggers, https://github.com/ebiggers/ntfs-3g-system-compression) into the in-tree NTFS driver under lib/, and adapt them to the kernel environment. The upstream plugin implements WOF ("Windows Overlay Filesystem", a.k.a. system compression / "Compact OS") decompression for the NTFS-3G FUSE driver, and itself borrows the LZX/XPRESS decompressors that the same author wrote for wimlib (https://wimlib.net/). The XPRESS and LZX formats used here are identical to those used in WIM archives. This commit is the kernel-side port that lets fs/ntfs/wof.c read system-compressed files. The library keeps the upstream subtable-based Huffman decoder (root table + contiguous subtables decoded with MAKE_DECODE_TABLE_ENTRY()), so long codewords only need one extra lookup instead of bit-by-bit tree traversal. The ntfs_codec_ops interface exported to fs/ntfs/wof.c (ntfs_lzx32k_codec_ops and ntfs_xpress{4k,8k,16k}_codec_ops) matches what the WOF layer expects. Modifications made while porting from the upstream plugin: - Replace the variable LZX window order (2^15..2^21) with a fixed 32768-byte window, which is the only size WOF uses - Simplify the bitstream helper: - bitstream_ensure_bits() now guarantees 16 valid bits instead of the carried-over 17-bit refill path from wimlib. Neither LZX (max codeword length 16) nor XPRESS (max 15) needs more than 16 bits. - Refactor codes to satisfy checkpatch. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: return errors from inode initializationHyunchul Lee
ntfs_iget() previously converted only -ENOMEM from ntfs_read_locked_inode() into an ERR_PTR(). Other initialization errors left the inode on the normal return path after it had been unlocked. Return every non-zero initialization error after releasing the inode reference. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: parse REPARSE_TAG_WOFHyunchul Lee
Introduce parsing support for REPARSE_TAG_WOF reparse points. Rename ntfs_make_symlink() to ntfs_parse_reparse() since it now handles both symlinks and WOF reparse tags. Introduce NI_WofCompressed flag to indicate files compressed via Windows System Compression (WOF), and configure compressed block size accordingly (12 to 15 bits based on the format). Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: return errors from ntfs_attr_readallHyunchul Lee
ntfs_attr_readall() currently loses the failure reason for attribute lookup, allocation, and read failures by returning NULL. Return ERR_PTR() with the original error instead. The reparse parser can then propagate allocation and I/O errors without treating them as filesystem corruption. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: add WOF compression config optionHyunchul Lee
Add CONFIG_NTFS_FS_WOF_COMPRESSION for Windows system compression. Build XPRESS and LZX decoding code only when requested. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: define LZNT1 codec ops under transparent codec interfaceHyunchul Lee
Define the ntfs_lznt1_codec_ops structure containing decompress_pages and compress_subblock callbacks in compress.c, and export it in ntfs_codec.h. This structure binds existing LZNT1 decompress and compress helper functions under the unified transparent compression interface. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-21ntfs: introduce transparent compression codec interfaceHyunchul Lee
Introduce struct ntfs_codec_ops and enum ntfs_codec_id to provide a unified interface for compression and decompression algorithms. This interface supports WOF and LZNT1 decompression. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-20ntfs: reject invalid empty mapping pairsHyunchul Lee
Reject an attribute with empty mapping pairs if it has inconsistent highest VCN and size. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Reported-by: Robert Morris <rtm@csail.mit.edu> Closes: https://lore.kernel.org/all/9519.1786907182@localhost/ Cc: stable@vger.kernel.org Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-20ntfs: fix resource leak in ntfs_new_attr_flagsHongling Zeng
When handling resident attributes that don't need sparse/compressed changes, ntfs_new_attr_flags() returns 0 directly at line 678 without calling unmap_mft_record() or ntfs_attr_put_search_ctx(). This leaks the MFT record mapping and attribute search context. An unprivileged user can cause a denial of service by repeatedly calling setxattr(2) with system.ntfs_attrib on files with resident attributes, eventually exhausting kernel memory. Fix by replacing the direct return with goto err_out to ensure proper cleanup of resources via the existing cleanup code. Fixes: e791930240a5 ("ntfs: fix resident conversion in ntfs_new_attr_flags") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-20ntfs: validate usa_ofs before preserving the update sequence numberDennis Tighe
When ntfs_mft_record_alloc() reuses a free mft record it reads the old update sequence number straight from the on-disk record: usn = *(__le16 *)((u8 *)m + le16_to_cpu(m->usa_ofs)); Here m points into the raw $MFT page-cache folio, which still holds unvalidated, MST-protected bytes: the folio is read by a plain iomap_read_folio() and neither post_read_mst_fixup() nor ntfs_mft_record_check() has run on it (both work on private copies). m->usa_ofs is therefore an untrusted u16, and a corrupted record can put it past the end of the record so the two-byte read lands outside the folio. Reading such a record while creating a file gives, under KASAN: BUG: KASAN: use-after-free in ntfs_mft_record_alloc+... Read of size 2 at addr ... ntfs_mft_record_alloc -> __ntfs_create -> ntfs_create -> path_openat Only preserve the old update sequence number when usa_ofs is even and in range, mirroring the check ntfs_mft_record_check() already applies; otherwise leave usn zero, which the existing restore below skips. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe <dennis.tighe@gmail.com> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-20ntfs: fix off-by-one page overflow in ntfs_decompress()Dennis Tighe
The per-token range check in ntfs_decompress() uses if (cb >= cb_sb_end || dp_addr > dp_sb_end) break; so dp_addr == dp_sb_end falls through to the symbol copy `*dp_addr++ = *cb++`, writing one byte past the destination page. Since NTFS_SB_SIZE == PAGE_SIZE the destination is a single page, so the byte lands in the adjacent page, and *dest_ofs is left one past the sub-block end (the later `*dest_ofs &= ~PAGE_MASK` then yields 1, not 0, so the page is never finalized and later sub-blocks keep writing further past it). A corrupted compressed $DATA attribute thus produces a bounded run of out-of-bounds writes when the file is read. Break as soon as dp_addr reaches dp_sb_end; a full sub-block still completes, as its final copy advances dp_addr to exactly dp_sb_end. Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe <dennis.tighe@gmail.com> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: do not update ctime when setxattr failsBaolin Liu
ntfs_setxattr() updates ctime and marks the inode dirty even when the operation fails. A failed setxattr(2) must not change file metadata. Update ctime only on success. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu <liubaolin@kylinos.cn> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: reject invalid MFT LCNs from boot sectorHyunchul Lee
The NTFS boot sector stores the MFT and MFTMirr locations as unsigned 64-bit LCNs, but parse_ntfs_boot_sector() decoded them into an s64. A crafted high-bit value could therefore become negative and pass the existing upper-bound check. The invalid value then propagated into the MFT zone allocator and could result in an out-of-bounds access to lcn_empty_bits_per_page. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Reported-by: Robert Morris <rtm@csail.mit.edu> Closes: https://lore.kernel.org/all/57514.1787000602@localhost Cc: stable@vger.kernel.org Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: serialize resident iomap reads with mrec_lockHyeontae Lee
ntfs_read_iomap_begin_resident() walks the MFT record through ntfs_attr_lookup() -> ntfs_attr_find() without taking ni->mrec_lock, while ntfs_attr_record_resize(), ntfs_make_room_for_attr() and ntfs_resident_attr_record_add() memmove() the same base_ni->mrec buffer under that lock. map_mft_record() only takes a reference and does not serialize, so the reader can observe torn attribute length and offset fields while a writer is relocating the records. KCSAN reports the race between the mmap read fault path and both link() and unlink(): BUG: KCSAN: data-race in ntfs_attr_find / ntfs_attr_record_resize write to 0xffff888100af1018 of 4 bytes by task 96 on cpu 1: ntfs_attr_record_resize+0xd2/0x130 ntfs_attr_record_rm+0xad/0x530 ntfs_delete+0x224/0x640 ntfs_unlink+0x14d/0x280 vfs_unlink+0x157/0x520 read to 0xffff888100af1018 of 4 bytes by task 95 on cpu 0: ntfs_attr_find+0x104/0x5b0 ntfs_attr_lookup+0x39c/0x10c0 ntfs_read_iomap_begin_resident+0xc6/0x230 ntfs_read_iomap_begin+0x5d/0xa0 iomap_iter+0x2e2/0x6e0 iomap_read_folio+0x147/0x2a0 ntfs_read_folio+0x108/0x170 filemap_read_folio+0x35/0x100 filemap_fault+0x993/0x1000 value changed: 0x00000250 -> 0x000001f0 The address is mrec + 0x18, i.e. mft_record.bytes_in_use, and the change is the 96 bytes of one $FILE_NAME attribute being removed. Keep base_ni->mrec_lock from the resident read iomap lookup through iomap_end(). This protects both the attribute walk and the subsequent copy from iomap->inline_data, which points into the MFT record. The non-resident path is left alone: ntfs_lookup() already holds the directory inode's mrec_lock when it reads an index folio through read_mapping_folio(), and taking the lock in the shared wrapper deadlocks there with recursive locking on mrec_lock. The comment above the read_mapping_folio() call in fs/ntfs/dir.c notes the same hazard. The seek path uses the same lookup helper but does not dereference iomap->inline_data. Release the lock before returning from that path, whereas the regular read path records base_ni in iomap->private and releases the lock from its iomap_end() callback. Tested with a reproducer that faults in a 16-byte resident file while another thread runs link()/unlink() on it. Before: 40 KCSAN reports in about one second. After: no reports in 180 seconds over 206,090 read iterations and 423,540 link/unlink cycles. A PROVE_LOCKING build shows no lockdep splat with the same reproducer running for 60 seconds. Fixes: b041ca562526 ("ntfs: update iomap and address space operations") Link: https://lore.kernel.org/all/20260725042421.109599-1-wonju345@naver.com/ Signed-off-by: Hyeontae Lee <wonju345@naver.com> Co-developed-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: verify run length exceeding volume boundaryHongling Zeng
The mapping pairs decoder validates that the starting LCN is within the volume but does not check if the run extends beyond the volume boundary. A malformed NTFS image with a crafted mapping pairs array could cause the kernel to access memory beyond the volume boundary, potentially leading to memory corruption and privilege escalation. Add validation to ensure lcn + length stays within nr_clusters. Cc: stable@vger.kernel.org Fixes: b4be3a47f8ba4 ("ntfs: bound the free-cluster bitmap scan to the volume") Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: allow index root relocationNamjae Jeon
Allow a resident attribute record to move to an extent MFT record when the base record needs room for an attribute list. Retry the root conversion after creating the list, but do not relocate a root that is already external. Roll the root back to the base record if persisting the attribute list fails, and free extent MFT records left empty by relocation or rollback. Also preserve bitmap allocation errors in index operations. Fixes: af0db57d4293 ("ntfs: update inode operations") Reported-by: yi <691464208@qq.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: validate non-resident attribute offsetsHongling Zeng
ntfs_attr_update_meta() shifts the attribute name when converting between non-sparse and sparse attributes. Converting to sparse also adds the compressed_size field before the name and mapping pairs, requiring eight additional bytes in the attribute record. However, the validator does not check that name_offset is within safe boundaries for these operations or that the additional space is available. A malicious MFT record could set name_offset such that: 1. The name is positioned at the very end of a non-sparse attribute. Converting to sparse would shift the name forward by 8 bytes, writing beyond the attribute boundary. 2. The name overlaps with the mapping pairs, causing corruption during conversion. Add validation to ensure: - For named attributes, name_offset is within valid bounds - Name does not extend beyond the attribute or overlap with mapping pairs - For non-sparse, non-compressed attributes, eight bytes are available after mapping_pairs_offset for the compressed_size field The space check also covers unnamed attributes, for which name_offset = 0 is valid and no name range needs to be checked. Fixes: 7e2a1c554bc4 ("ntfs: Fix min_len for compressed/sparse attributes in ntfs_non_resident_attr_value_is_valid()") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn> Co-developed-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: simplify ntfs_reparse_set_native_symlink()Dmitry Antipov
Avoid redundant 'strlen()' and use the convenient 'strreplace()' to simplify 'ntfs_reparse_set_native_symlink()'. Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: reject unprivileged writes to reserved $LX* xattrsPisit Preechapramoth
Reject setxattr of the reserved $LXUID, $LXGID, $LXMOD and $LXDEV names from userspace unless the caller has CAP_SYS_ADMIN. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Pisit Preechapramoth <kml.delusion501@slmail.me> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: bound the free-cluster bitmap scan to the volumeBryam Vargas
vol->lcn_empty_bits_per_page is sized from vol->nr_clusters at mount, but ntfs_cluster_alloc() bounds its scan of that array by the size of $Bitmap. Those are independent on-disk quantities and the mount-time check only rejects a $Bitmap that is too small, so an image whose $Bitmap covers more clusters than the volume has lets the scan index past the array. A run whose LCN lies in that gap takes the allocator straight there, since the caller passes the file's own last LCN as its locality hint. KASAN reports a slab out-of-bounds read when a file on such a volume is extended. Clamp the scan to what that array covers, mirroring the max_index calculation the mount-time scan already uses, and reject a decoded LCN at or beyond nr_clusters in the mapping pairs decoder. Conforming volumes are unaffected. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Cc: stable@vger.kernel.org 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-08-19ntfs: respect per-file chmod mode over mount masksNamjae Jeon
fmask and dmask provide the default permissions for files without WSL metadata. Once chmod stores a mode in $LXMOD, however, that per-file mode must take precedence so selected files can retain permissions such as execute across remounts. Record whether $LXMOD was found while loading an inode and apply the mount masks only when it is absent. Do not remask the in-memory mode after setattr persists it. Continue loading $LXMOD even when optional $LXUID or $LXGID metadata is missing, since chmod may create only $LXMOD. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: apply Windows name checks only with windows_namesNamjae Jeon
The windows_names mount option is documented to reject names containing characters forbidden by Windows. However, ntfs_check_bad_windows_name() unconditionally rejects those characters before checking the mount option. Move the character validation after the option check so a default NTFS mount accepts POSIX names such as names containing ':'. Mounts using windows_names retain the existing Windows-compatible validation, including reserved device names and trailing spaces or dots. Fixes: af0db57d4293 ("ntfs: update inode operations") Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-08-19ntfs: Fix index_root heap OOB write in ntfs_ir_to_ib()Alexandro Calo
ntfs_ir_to_ib copies all entries from index_root into a freshly allocated index_block_size-byte buffer without verifying that the entries fit in the available space. The entries in index_root may be larger than the usable entry space in the index block. This can cause OOB writes past the end of the allocation. The validator ntfs_index_root_inconsistent() checks that entries are self-consistent within the IR value, but never cross-checks them against index_block_size. There is no bounds check in ntfs_ir_to_ib() before the memcpy. Fixing this at the sink in ntfs_ir_to_ib() since ntfs_index_root_inconsistent() validates the logical consistency of index_root as a structure and a root with large entries is a structurally valid root. The bug is a size conflict of ntfs_ir_to_ib(). Also, the validator is called once per inode load in ntfs_read_locked_inode() while ntfs_ir_to_ib() is only called during a reparent, a check there adds no overhead to the common path. Moreover, even a future call path that bypasses the validator would still be protected. With NULL as first parameter of ntfs_error(), the volume error flag is never set by this call, so the device name will be absent from the error message. In any case, that the caller, ntfs_ir_reparent(), prints an error message that includes the device name on NULL returns. I think this is the best solution available without adding 'struct super_block *sb' as a parameter to ntfs_ir_to_ib(). This heap out-of-bounds write is triggered by a crafted filesystem image, which is not in the kernel threat model, anyway, fixing memory errors would be nice to keep things secure. Fixes: 0a8ac0c1fa0b ("ntfs: update directory operations") Signed-off-by: Alexandro Calo <alexandro.calo@nozominetworks.com> Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>