| Age | Commit message (Collapse) | Author |
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|