Changes for 7.2-rc1

Added:
     depth limit to indx_find_buffer() to prevent stack overflow
     validate split-point offset in indx_insert_into_buffer()
     bounds check to run_get_highest_vcn()
     fileattr_get() and fileattr_set() support
     zero stale pagecache beyond valid data length
     handle delayed allocation overlap in run lookup
     validate lcns_follow in log_replay() conversion
     cap RESTART_TABLE free-chain walker at rt->used
     resize log->one_page_buf when adopting on-disk page size
     reject direct userspace writes to reserved $LX* xattrs
 
 Fixed:
     out-of-bounds read in decompress_lznt()
     avoid -Wmaybe-uninitialized warnings
     hold ni_lock across readdir metadata walk
     preserve non-DOS attribute bits in system.dos_attrib
     validate index entry key bounds
     syncing wrong inode on DIRSYNC cross-directory rename
     validate Dirty Page Table capacity in log_replay() copy_lcns
     wrong LCN in run_remove_range() when splitting a run
     allocate iomap inline_data using alloc_page
     mount failure on 64K page-size kernels
     out-of-bounds read in ntfs_dir_emit() and hdr_find_e()
     bound attr_off in UpdateResidentValue against data_off
     bound DeleteIndexEntryAllocation memmove length
     bound copy_lcns dp->page_lcns[] index in analysis pass
     bound NTFS_DE view.data_off in UpdateRecordData{Root,Allocation}
     prevent potential lcn remains uninitialized
 
 Changed:
     bound to_move in indx_insert_into_root() before hdr_insert_head()
     call _ntfs_bad_inode() when failing to rename
     fold resident writeback into writepages loop
     force waiting for direct I/O completion
     fold file size handling into ntfs_set_size()
     reject SEEK_DATA and SEEK_HOLE past EOF early
     format code, add descriptive comments and remove non-useful
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEh0DEKNP0I9IjwfWEqbAzH4MkB7YFAmoqcPYACgkQqbAzH4Mk
 B7ZQ3A//ZYsz0s0qIZ0ErRuxQqmliZc1hzVGbFdKi046AKeRfhN1nV/1MP75+F4V
 eD3sJ4kiROT4oc1x//uJdCoMrrH7qZs2Rcrzv3azC4F9BEFxcxLtJkyZ5NVU4eCj
 vfKaRWZ8ewKeMm37Laz8DOpsz8193KzAVYK/Fm1KYoKMR0Jt+/sdOkIO/NVczZEk
 gY4EAqKUTORfN0a/iELaA+NIrViTk2Wjzzu74YNl/1RDii1LGFaTfa3cmB6jylTY
 AVjPX/lMMtdhy5k9Thcp4lG4uK6x6fSPYHEqvB8+Q3/JGbJfS92Oz3FR9zNKlKz4
 y8depBmT85aZJ2psKetFJCXcVj3EIC1aVY/1CCgJCnLymANUuHlwFDv+3QY4D2bR
 Me//cob7zLFNuul22Uveb00+34H+Tqf1QQNFNUtam6aeXC3bK9PEMbYIQ2+rU6jr
 8M9MTDqfdc6WjyjXwVTNXqki5ZEZmEbSk0FXJM9JsKAyLeo2aentrQtCj5CuTBaH
 sfIbJjT6g/UCfQudNxUDPtlKbhCqYA8SU23iOUlVe7TQDNKnHaQ+NEJ0prqAt303
 2uVupQSJJTu+qv4s2s7ZdaA8z44WrrZfFFQm0okUIA/4NkrAKMYLTQsmki9xpW0h
 KADepNmqtQ4tva9OjTkdOYiy8FUCGTKdZTJOgihjqGO+Z881xOY=
 =OVdT
 -----END PGP SIGNATURE-----

Merge tag 'ntfs3_for_7.2' of https://github.com/Paragon-Software-Group/linux-ntfs3

Pull ntfs3 updates from Konstantin Komarov:
 "Added:
   - depth limit to indx_find_buffer() to prevent stack overflow
   - validate split-point offset in indx_insert_into_buffer()
   - bounds check to run_get_highest_vcn()
   - fileattr_get() and fileattr_set() support
   - zero stale pagecache beyond valid data length
   - handle delayed allocation overlap in run lookup
   - validate lcns_follow in log_replay() conversion
   - cap RESTART_TABLE free-chain walker at rt->used
   - resize log->one_page_buf when adopting on-disk page size
   - reject direct userspace writes to reserved $LX* xattrs

  Fixed:
   - out-of-bounds read in decompress_lznt()
   - avoid -Wmaybe-uninitialized warnings
   - hold ni_lock across readdir metadata walk
   - preserve non-DOS attribute bits in system.dos_attrib
   - validate index entry key bounds
   - syncing wrong inode on DIRSYNC cross-directory rename
   - validate Dirty Page Table capacity in log_replay() copy_lcns
   - wrong LCN in run_remove_range() when splitting a run
   - allocate iomap inline_data using alloc_page
   - mount failure on 64K page-size kernels
   - out-of-bounds read in ntfs_dir_emit() and hdr_find_e()
   - bound attr_off in UpdateResidentValue against data_off
   - bound DeleteIndexEntryAllocation memmove length
   - bound copy_lcns dp->page_lcns[] index in analysis pass
   - bound NTFS_DE view.data_off in UpdateRecordData{Root,Allocation}
   - prevent potential lcn remains uninitialized

  Changed:
   - bound to_move in indx_insert_into_root() before hdr_insert_head()
   - call _ntfs_bad_inode() when failing to rename
   - fold resident writeback into writepages loop
   - force waiting for direct I/O completion
   - fold file size handling into ntfs_set_size()
   - reject SEEK_DATA and SEEK_HOLE past EOF early
   - format code, add descriptive comments and remove non-useful"

* tag 'ntfs3_for_7.2' of https://github.com/Paragon-Software-Group/linux-ntfs3: (34 commits)
  ntfs3: reject direct userspace writes to reserved $LX* xattrs
  fs/ntfs3: resize log->one_page_buf when adopting on-disk page size
  fs/ntfs3: prevent potential lcn remains uninitialized
  ntfs3: cap RESTART_TABLE free-chain walker at rt->used
  fs/ntfs3: bound NTFS_DE view.data_off in UpdateRecordData{Root,Allocation}
  fs/ntfs3: validate lcns_follow in log_replay conversion
  fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass
  fs/ntfs3: bound DeleteIndexEntryAllocation memmove length
  fs/ntfs3: bound attr_off in UpdateResidentValue against data_off
  ntfs3: fix out-of-bounds read in ntfs_dir_emit() and hdr_find_e()
  fs/ntfs3: fix mount failure on 64K page-size kernels
  ntfs3: avoid another -Wmaybe-uninitialized warning
  ntfs3: Allocate iomap inline_data using alloc_page
  fs/ntfs3: format code, deal with comments
  fs/ntfs3: reject SEEK_DATA and SEEK_HOLE past EOF early
  fs/ntfs3: fold file size handling into ntfs_set_size()
  fs/ntfs3: force waiting for direct I/O completion
  fs/ntfs3: fold resident writeback into writepages loop
  fs/ntfs3: handle delayed allocation overlap in run lookup
  fs/ntfs3: zero stale pagecache beyond valid data length
  ...
This commit is contained in:
Linus Torvalds 2026-06-24 10:05:53 -07:00
commit f0e6f20cb5
13 changed files with 591 additions and 303 deletions

View File

@ -962,11 +962,8 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
/* Try to find in cache. */
down_read(&ni->file.run_lock);
if (!no_da && run_lookup_entry(&ni->file.run_da, vcn, lcn, len, NULL)) {
/* The requested vcn is delay allocated. */
*lcn = DELALLOC_LCN;
} else if (run_lookup_entry(&ni->file.run, vcn, lcn, len, NULL)) {
/* The requested vcn is known in current run. */
if (run_lookup_entry_da(&ni->file.run, !no_da ? &ni->file.run_da : NULL,
vcn, lcn, len)) {
} else {
*len = 0;
}
@ -1004,6 +1001,7 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
struct ATTRIB *attr, *attr_b;
struct ATTR_LIST_ENTRY *le, *le_b;
struct mft_inode *mi, *mi_b;
struct page *page;
CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0;
CLST alloc, evcn;
unsigned fr;
@ -1011,11 +1009,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
int step;
again:
if (da && run_lookup_entry(run_da, vcn, lcn, len, NULL)) {
/* The requested vcn is delay allocated. */
*lcn = DELALLOC_LCN;
} else if (run_lookup_entry(run, vcn, lcn, len, NULL)) {
/* The requested vcn is known in current run. */
if (run_lookup_entry_da(run, da ? &ni->file.run_da : NULL, vcn, lcn,
len)) {
} else {
*len = 0;
}
@ -1042,10 +1037,13 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
*lcn = RESIDENT_LCN;
*len = data_size;
if (res && data_size) {
*res = kmemdup(resident_data(attr_b), data_size,
GFP_KERNEL);
if (!*res)
page = alloc_page(GFP_KERNEL);
if (!page) {
err = -ENOMEM;
} else {
*res = page_address(page);
memcpy(*res, resident_data(attr_b), data_size);
}
}
goto out;
}
@ -1100,7 +1098,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
}
if (!*len) {
if (run_lookup_entry(run, vcn, lcn, len, NULL)) {
if (run_lookup_entry_da(run, da ? run_da : NULL, vcn, lcn,
len)) {
if (*lcn != SPARSE_LCN || !new)
goto ok; /* Slow normal way without allocation. */
@ -1157,7 +1156,7 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
struct ATTRIB *attr2;
attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL,
0, &vcn0, &mi);
0, &vcn0, &mi);
if (!attr2) {
err = -EINVAL;
goto out;

View File

@ -305,7 +305,9 @@ static inline bool ntfs_dir_emit(struct ntfs_sb_info *sbi,
if (sbi->options->nohidden && (fname->dup.fa & FILE_ATTRIBUTE_HIDDEN))
return true;
if (fname->name_len + sizeof(struct NTFS_DE) > le16_to_cpu(e->size))
if (sizeof(struct NTFS_DE) +
offsetof(struct ATTR_FILE_NAME, name) +
fname->name_len * sizeof(short) > le16_to_cpu(e->size))
return true;
name_len = ntfs_utf16_to_nls(sbi, fname->name, fname->name_len, name,
@ -489,10 +491,17 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
goto out;
}
/*
* Keep directory metadata stable for the whole walk. Loading subrecords
* once is not enough if concurrent writeback can still compact ATTR_LIST
* entries and free the record that ntfs_read_hdr() is currently walking.
*/
ni_lock(ni);
root = indx_get_root(&ni->dir, ni, NULL, NULL);
if (!root) {
err = -EINVAL;
goto out;
goto out_unlock;
}
if (pos >= sbi->record_size) {
@ -503,7 +512,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
*/
err = ntfs_read_hdr(sbi, ni, &root->ihdr, 0, pos, name, ctx);
if (err)
goto out;
goto out_unlock;
bit = 0;
}
@ -514,7 +523,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
/* Get the next used index. */
err = indx_used_bit(&ni->dir, ni, &bit);
if (err)
goto out;
goto out_unlock;
if (bit == MINUS_ONE_T) {
/* no more used indexes. end of dir. */
@ -524,13 +533,13 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
if (bit >= max_bit) {
/* Corrupted directory. */
err = -EINVAL;
goto out;
goto out_unlock;
}
err = indx_read_ra(&ni->dir, ni, bit << ni->dir.idx2vbn_bits,
&node, &file->f_ra);
if (err)
goto out;
goto out_unlock;
/*
* Add each name from index in 'ctx'.
@ -539,9 +548,12 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
((u64)bit << index_bits) + sbi->record_size,
pos, name, ctx);
if (err)
goto out;
goto out_unlock;
}
out_unlock:
ni_unlock(ni);
out:
kfree(name);
put_indx_node(node);

View File

@ -89,6 +89,84 @@ static int ntfs_ioctl_fitrim(struct ntfs_sb_info *sbi, unsigned long arg)
return 0;
}
/*
* ntfs_fileattr_get - inode_operations::fileattr_get
*/
int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
{
struct inode *inode = d_inode(dentry);
struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
struct ntfs_inode *ni = ntfs_i(inode);
u32 flags = 0;
/* Avoid any operation if inode is bad. */
if (unlikely(is_bad_ni(ni)))
return -EINVAL;
if (sbi->options->nocase)
flags |= FS_CASEFOLD_FL;
if (inode->i_flags & S_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
if (inode->i_flags & S_APPEND)
flags |= FS_APPEND_FL;
if (is_compressed(ni))
flags |= FS_COMPR_FL;
if (is_encrypted(ni))
flags |= FS_ENCRYPT_FL;
if (ni->nodump)
flags |= FS_NODUMP_FL;
fileattr_fill_flags(fa, flags);
return 0;
}
/*
* ntfs_fileattr_set - inode_operations::fileattr_set
*/
int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
struct file_kattr *fa)
{
struct inode *inode = d_inode(dentry);
struct ntfs_inode *ni = ntfs_i(inode);
u32 flags = fa->flags;
unsigned int new_fl = 0;
/* Avoid any operation if inode is bad. */
if (unlikely(is_bad_ni(ni)))
return -EINVAL;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL))
return -EOPNOTSUPP;
if (flags & FS_IMMUTABLE_FL)
new_fl |= S_IMMUTABLE;
if (flags & FS_APPEND_FL)
new_fl |= S_APPEND;
inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
/* Save nodump flag to return in ntfs_getattr. */
if (flags & FS_NODUMP_FL)
ni->nodump = 1;
else
ni->nodump = 0;
inode_set_ctime_current(inode);
mark_inode_dirty(inode);
return 0;
}
static int ntfs_ioctl_get_volume_label(struct ntfs_sb_info *sbi, u8 __user *buf)
{
if (copy_to_user(buf, sbi->volume.label, FSLABEL_MAX))
@ -180,34 +258,6 @@ long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg)
}
#endif
/*
* ntfs_fileattr_get - inode_operations::fileattr_get
*/
int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
{
struct inode *inode = d_inode(dentry);
struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
/* Avoid any operation if inode is bad. */
if (unlikely(is_bad_ni(ntfs_i(inode))))
return -EINVAL;
/*
* NTFS preserves case (the default). Case sensitivity depends on
* mount options: with "nocase", NTFS is case-insensitive;
* otherwise it is case-sensitive.
*/
if (sbi->options->nocase) {
fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
fa->flags |= FS_CASEFOLD_FL;
}
if (inode->i_flags & S_IMMUTABLE) {
fa->fsx_xflags |= FS_XFLAG_IMMUTABLE;
fa->flags |= FS_IMMUTABLE_FL;
}
return 0;
}
/*
* ntfs_getattr - inode_operations::getattr
*/
@ -231,6 +281,9 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
if (inode->i_flags & S_APPEND)
stat->attributes |= STATX_ATTR_APPEND;
if (ni->nodump)
stat->attributes |= STATX_ATTR_NODUMP;
if (is_compressed(ni))
stat->attributes |= STATX_ATTR_COMPRESSED;
@ -274,18 +327,44 @@ static int ntfs_extend_initialized_size(struct file *file,
return 0;
}
/* Zero pagecache after 'from'. */
static void ntfs_zero_tail(struct address_space *mapping, loff_t from)
{
struct folio_batch fbatch;
pgoff_t index = from >> PAGE_SHIFT;
unsigned nr, i;
folio_batch_init(&fbatch);
nr = filemap_get_folios(mapping, &index, -1, &fbatch);
for (i = 0; i < nr; i++) {
struct folio *folio = fbatch.folios[i];
u32 st = folio_pos(folio) < from ?
offset_in_folio(folio, from) :
0;
folio_lock(folio);
folio_zero_segment(folio, st, folio_size(folio));
folio_unlock(folio);
}
folio_batch_release(&fbatch);
}
static void ntfs_filemap_close(struct vm_area_struct *vma)
{
struct inode *inode = file_inode(vma->vm_file);
struct ntfs_inode *ni = ntfs_i(inode);
u64 i_size = i_size_read(inode);
u64 from = (u64)vma->vm_pgoff << PAGE_SHIFT;
u64 to = min_t(u64, i_size_read(inode),
from + vma->vm_end - vma->vm_start);
u64 to = min(i_size, from + vma->vm_end - vma->vm_start);
if (ni->i_valid < to) {
ni->i_valid = to;
mark_inode_dirty(inode);
}
ntfs_zero_tail(inode->i_mapping, ni->i_valid);
}
/* Copy of generic_file_vm_ops. */
@ -375,93 +454,6 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
return err;
}
static int ntfs_extend(struct inode *inode, loff_t pos, size_t count,
struct file *file)
{
struct ntfs_inode *ni = ntfs_i(inode);
struct address_space *mapping = inode->i_mapping;
loff_t end = pos + count;
bool extend_init = file && pos > ni->i_valid;
int err;
if (end <= inode->i_size && !extend_init)
return 0;
/* Mark rw ntfs as dirty. It will be cleared at umount. */
ntfs_set_state(ni->mi.sbi, NTFS_DIRTY_DIRTY);
if (end > inode->i_size) {
/*
* Normal files: increase file size, allocate space.
* Sparse/Compressed: increase file size. No space allocated.
*/
err = ntfs_set_size(inode, end);
if (err)
goto out;
}
if (extend_init && !is_compressed(ni)) {
err = ntfs_extend_initialized_size(file, ni, pos);
if (err)
goto out;
} else {
err = 0;
}
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
mark_inode_dirty(inode);
if (IS_SYNC(inode)) {
int err2;
err = filemap_fdatawrite_range(mapping, pos, end - 1);
err2 = write_inode_now(inode, 1);
if (!err)
err = err2;
if (!err)
err = filemap_fdatawait_range(mapping, pos, end - 1);
}
out:
return err;
}
static int ntfs_truncate(struct inode *inode, loff_t new_size)
{
int err;
struct ntfs_inode *ni = ntfs_i(inode);
u64 new_valid = min_t(u64, ni->i_valid, new_size);
truncate_setsize(inode, new_size);
ni_lock(ni);
down_write(&ni->file.run_lock);
err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
&new_valid, ni->mi.sbi->options->prealloc, NULL,
false);
up_write(&ni->file.run_lock);
ni->i_valid = new_valid;
ni_unlock(ni);
if (err)
return err;
ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
if (!IS_DIRSYNC(inode)) {
mark_inode_dirty(inode);
} else {
err = ntfs_sync_inode(inode);
if (err)
return err;
}
return 0;
}
/*
* ntfs_fallocate - file_operations::ntfs_fallocate
*
@ -668,57 +660,25 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
if (is_supported_holes) {
CLST vcn = vbo >> cluster_bits;
CLST cend = bytes_to_cluster(sbi, end);
CLST cend_v = bytes_to_cluster(sbi, ni->i_valid);
CLST lcn, clen;
bool new;
if (cend_v > cend)
cend_v = cend;
/*
* Allocate and zero new clusters.
* Zeroing these clusters may be too long.
*/
for (; vcn < cend_v; vcn += clen) {
err = attr_data_get_block(ni, vcn, cend_v - vcn,
&lcn, &clen, &new,
true, NULL, false);
if (err)
goto out;
}
/*
* Moving up 'valid size'.
*/
err = ntfs_extend_initialized_size(
file, ni, (u64)cend_v << cluster_bits);
if (err)
goto out;
/*
* Allocate but not zero new clusters.
*/
for (; vcn < cend; vcn += clen) {
err = attr_data_get_block(ni, vcn, cend - vcn,
&lcn, &clen, &new,
false, NULL, false);
true, NULL, false);
if (err)
goto out;
}
}
if (mode & FALLOC_FL_KEEP_SIZE) {
ni_lock(ni);
/* True - Keep preallocated. */
err = attr_set_size(ni, ATTR_DATA, NULL, 0,
&ni->file.run, i_size, &ni->i_valid,
true);
ni_unlock(ni);
err = ntfs_set_size(inode, i_size);
if (err)
goto out;
i_size_write(inode, i_size);
} else if (new_size > i_size) {
i_size_write(inode, new_size);
}
}
@ -775,16 +735,20 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
oldsize = i_size_read(inode);
newsize = attr->ia_size;
if (newsize <= oldsize)
err = ntfs_truncate(inode, newsize);
else
err = ntfs_extend(inode, newsize, 0, NULL);
if (newsize != oldsize) {
truncate_setsize(inode, newsize);
if (err)
goto out;
err = ntfs_set_size(inode, newsize);
if (err) {
i_size_write(inode, oldsize);
goto out;
}
ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
i_size_write(inode, newsize);
ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
inode_set_mtime_to_ts(inode,
inode_set_ctime_current(inode));
}
}
setattr_copy(idmap, inode, attr);
@ -1048,7 +1012,7 @@ static ssize_t ntfs_compress_write(struct kiocb *iocb, struct iov_iter *from)
CLST lcn, clen;
frame = valid >> frame_bits;
frame_vbo = valid & ~(frame_size - 1);
frame_vbo = valid & ~(u64)(frame_size - 1);
off = valid & (frame_size - 1);
err = attr_data_get_block(ni, frame << NTFS_LZNT_CUNIT, 1, &lcn,
@ -1117,7 +1081,7 @@ static ssize_t ntfs_compress_write(struct kiocb *iocb, struct iov_iter *from)
if (bytes > count)
bytes = count;
frame_vbo = pos & ~(frame_size - 1);
frame_vbo = pos & ~(u64)(frame_size - 1);
index = frame_vbo >> PAGE_SHIFT;
if (unlikely(fault_in_iov_iter_readable(from, bytes))) {
@ -1258,6 +1222,7 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct ntfs_inode *ni = ntfs_i(inode);
loff_t vbo, endbyte;
ssize_t ret, err;
if (!inode_trylock(inode)) {
@ -1292,15 +1257,30 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
goto out;
}
ret = ntfs_extend(inode, iocb->ki_pos, ret, file);
if (ret)
goto out;
vbo = iocb->ki_pos;
endbyte = vbo + ret;
if (endbyte > inode->i_size) {
/*
* Normal files: increase file size, allocate space.
* Sparse/Compressed: increase file size. No space allocated.
*/
ret = ntfs_set_size(inode, endbyte);
if (ret)
goto out;
}
if (is_compressed(ni)) {
ret = ntfs_compress_write(iocb, from);
goto out;
}
if (vbo > ni->i_valid) {
ret = ntfs_extend_initialized_size(file, ni, vbo);
if (ret)
goto out;
}
/* Fallback to buffered I/O if the inode does not support direct I/O. */
if (!(iocb->ki_flags & IOCB_DIRECT) ||
!ntfs_should_use_dio(iocb, from)) {
@ -1323,7 +1303,8 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
goto out;
}
ret = iomap_dio_rw(iocb, from, &ntfs_iomap_ops, NULL, 0, NULL, 0);
ret = iomap_dio_rw(iocb, from, &ntfs_iomap_ops, NULL,
IOMAP_DIO_FORCE_WAIT, NULL, 0);
if (ret == -ENOTBLK) {
/* Returns -ENOTBLK in case of a page invalidation failure for writes.*/
@ -1332,7 +1313,7 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
}
if (ret >= 0 && iov_iter_count(from)) {
loff_t offset = iocb->ki_pos, endbyte;
vbo = iocb->ki_pos;
iocb->ki_flags &= ~IOCB_DIRECT;
err = iomap_file_buffered_write(iocb, from, &ntfs_iomap_ops,
@ -1350,15 +1331,15 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
* to complete off the I/O request.
*/
ret += err;
endbyte = offset + err - 1;
err = filemap_write_and_wait_range(inode->i_mapping, offset,
endbyte = vbo + err - 1;
err = filemap_write_and_wait_range(inode->i_mapping, vbo,
endbyte);
if (err) {
ret = err;
goto out;
}
invalidate_mapping_pages(inode->i_mapping, offset >> PAGE_SHIFT,
invalidate_mapping_pages(inode->i_mapping, vbo >> PAGE_SHIFT,
endbyte >> PAGE_SHIFT);
}
@ -1439,8 +1420,9 @@ static int ntfs_file_release(struct inode *inode, struct file *file)
down_write(&ni->file.run_lock);
/* Deallocate preallocated. */
err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run,
inode->i_size, &ni->i_valid, false);
err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run,
inode->i_size, &ni->i_valid, false, NULL,
true);
up_write(&ni->file.run_lock);
ni_unlock(ni);
@ -1552,7 +1534,12 @@ static loff_t ntfs_llseek(struct file *file, loff_t offset, int whence)
loff_t maxbytes = ntfs_get_maxbytes(ni);
loff_t ret;
if (whence == SEEK_DATA || whence == SEEK_HOLE) {
if (whence != SEEK_DATA && whence != SEEK_HOLE) {
ret = generic_file_llseek_size(file, offset, whence, maxbytes,
i_size_read(inode));
} else if ((unsigned long long)offset >= i_size_read(inode)) {
ret = -ENXIO;
} else {
inode_lock_shared(inode);
/* Scan file for hole or data. */
ret = ni_seek_data_or_hole(ni, offset, whence == SEEK_DATA);
@ -1560,9 +1547,6 @@ static loff_t ntfs_llseek(struct file *file, loff_t offset, int whence)
if (ret >= 0)
ret = vfs_setpos(file, ret, maxbytes);
} else {
ret = generic_file_llseek_size(file, offset, whence, maxbytes,
i_size_read(inode));
}
return ret;
}
@ -1576,6 +1560,7 @@ const struct inode_operations ntfs_file_inode_operations = {
.set_acl = ntfs_set_acl,
.fiemap = ntfs_fiemap,
.fileattr_get = ntfs_fileattr_get,
.fileattr_set = ntfs_fileattr_set,
};
const struct file_operations ntfs_file_operations = {

View File

@ -1330,7 +1330,7 @@ int ni_expand_list(struct ntfs_inode *ni)
{
int err = 0;
u32 asize, done = 0;
struct ATTRIB *attr, *ins_attr;
struct ATTRIB *attr, *ins_attr = NULL;
struct ATTR_LIST_ENTRY *le;
bool is_mft = ni->mi.rno == MFT_REC_MFT;
struct MFT_REF ref;
@ -1363,7 +1363,7 @@ int ni_expand_list(struct ntfs_inode *ni)
le16_to_cpu(attr->name_off), true,
&ins_attr, NULL, NULL);
if (err)
if (err || !ins_attr)
goto out;
memcpy(ins_attr, attr, asize);
@ -1855,8 +1855,8 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
static struct folio *ntfs_lock_new_page(struct address_space *mapping,
pgoff_t index, gfp_t gfp)
{
struct folio *folio = __filemap_get_folio(mapping, index,
FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp);
struct folio *folio = __filemap_get_folio(
mapping, index, FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp);
if (IS_ERR(folio))
return folio;
@ -2800,8 +2800,8 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
err = ni_add_name(new_dir_ni, ni, new_de);
if (!err) {
err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
WARN_ON(err &&
ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo));
if (err && ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
_ntfs_bad_inode(&ni->vfs_inode);
}
/*
@ -2859,6 +2859,11 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data)
return err;
}
if (!clen) {
/* Corrupted file. */
return -EINVAL;
}
if (lcn == RESIDENT_LCN) {
/* clen - resident size in bytes. clen == ni->vfs_inode.i_size */
if (offset >= clen) {
@ -2889,8 +2894,14 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data)
* the file offset is set to offset.
*/
if (lcn != SPARSE_LCN) {
vbo = (u64)vcn << cluster_bits;
return max(vbo, offset);
/* Normal cluster. */
break;
}
if ((ni->std_fa & FILE_ATTRIBUTE_COMPRESSED) &&
(vcn & (NTFS_LZNT_CLUSTERS - 1))) {
/* Compressed cluster in compressed frame. */
break;
}
} else {
/*
@ -2904,16 +2915,15 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data)
/* native compression hole begins at aligned vcn. */
(!(ni->std_fa & FILE_ATTRIBUTE_COMPRESSED) ||
!(vcn & (NTFS_LZNT_CLUSTERS - 1)))) {
vbo = (u64)vcn << cluster_bits;
return max(vbo, offset);
/* Hole in sparsed or compressed file frame. */
break;
}
}
if (!clen) {
/* Corrupted file. */
return -EINVAL;
}
}
vbo = (u64)vcn << cluster_bits;
return max(vbo, offset);
}
/*

View File

@ -764,8 +764,19 @@ static bool check_rstbl(const struct RESTART_TABLE *rt, size_t bytes)
/*
* Walk through the list headed by the first entry to make
* sure none of the entries are currently being used.
*
* Bound traversal by ne (rt->used) to defeat a crafted on-disk
* cycle in the free chain. Each entry in a legitimate free
* list is unique, so a chain that visits more than ne slots
* is malformed. Without this guard, an attacker-controlled
* RESTART_TABLE with a self-loop or A->B->A cycle whose
* offsets satisfy the existing alignment + in-bounds guards
* spins forever at mount time.
*/
for (off = ff; off;) {
for (off = ff, i = 0; off; i++) {
if (i > ne)
return false;
if (off == RESTART_ENTRY_ALLOCATED)
return false;
@ -1172,7 +1183,7 @@ static int read_log_page(struct ntfs_log *log, u32 vbo,
goto out;
if (page_buf->rhdr.sign != NTFS_FFFF_SIGNATURE)
ntfs_fix_post_read(&page_buf->rhdr, PAGE_SIZE, false);
ntfs_fix_post_read(&page_buf->rhdr, log->page_size, false);
if (page_buf != *buffer)
memcpy(*buffer, Add2Ptr(page_buf, page_off), bytes);
@ -2599,11 +2610,12 @@ static int read_next_log_rec(struct ntfs_log *log, struct lcb *lcb, u64 *lsn)
bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
{
const bool has_subnode = hdr_has_subnode(hdr);
__le16 mask;
u32 min_de, de_off, used, total;
const struct NTFS_DE *e;
if (hdr_has_subnode(hdr)) {
if (has_subnode) {
min_de = sizeof(struct NTFS_DE) + sizeof(u64);
mask = NTFS_IE_HAS_SUBNODES;
} else {
@ -2620,20 +2632,33 @@ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
return false;
}
e = Add2Ptr(hdr, de_off);
e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
for (;;) {
u16 esize = le16_to_cpu(e->size);
struct NTFS_DE *next = Add2Ptr(e, esize);
u16 key_size = le16_to_cpu(e->key_size);
u16 data_size;
if (esize < min_de || PtrOffset(hdr, next) > used ||
if (!IS_ALIGNED(esize, 8) || esize < min_de ||
(e->flags & NTFS_IE_HAS_SUBNODES) != mask) {
return false;
}
if (de_is_last(e))
break;
if (size_add(de_off, esize) > used)
return false;
e = next;
if (de_is_last(e)) {
if (key_size)
return false;
break;
}
data_size = esize - min_de;
if (key_size > data_size)
return false;
de_off += esize;
e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
}
return true;
@ -3325,6 +3350,17 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
nsize = ALIGN(nsize, 8);
data_off = le16_to_cpu(attr->res.data_off);
/*
* aoff comes from the on-disk lrh->attr_off. Forbid
* writes that begin below the resident attribute's
* data_off (which would overwrite the resident header),
* and forbid aoff + dlen < data_off, which would make
* the data_size assignment below underflow to ~4 GiB.
*/
if (aoff < data_off || aoff + dlen < data_off ||
aoff + dlen > asize)
goto dirty_vol;
if (nsize < asize) {
memmove(Add2Ptr(attr, aoff), data, dlen);
data = NULL; // To skip below memmove().
@ -3368,7 +3404,10 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
memmove(Add2Ptr(attr, aoff), data, dlen);
if (run_get_highest_vcn(le64_to_cpu(attr->nres.svcn),
attr_run(attr), &t64)) {
attr_run(attr),
le32_to_cpu(attr->size) -
le16_to_cpu(attr->nres.run_off),
&t64)) {
goto dirty_vol;
}
@ -3497,6 +3536,18 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
e = Add2Ptr(attr, le16_to_cpu(lrh->attr_off));
/*
* e->view.data_off and dlen come from the on-disk
* INDEX_ROOT entry / LRH. The neighbouring read sites
* (e.g. fs/ntfs3/index.c) check that
* view.data_off + view.data_size <= e->size; mirror that
* bound here so the memmove cannot reach past the entry.
*/
if (le16_to_cpu(e->view.data_off) > le16_to_cpu(e->size) ||
le16_to_cpu(e->view.data_off) + dlen >
le16_to_cpu(e->size))
goto dirty_vol;
memmove(Add2Ptr(e, le16_to_cpu(e->view.data_off)), data, dlen);
mi->dirty = true;
@ -3570,9 +3621,23 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
}
e1 = Add2Ptr(e, esize);
nsize = esize;
used = le32_to_cpu(hdr->used);
/*
* Reject crafted entries whose e->size makes e + esize
* point past the INDEX_HDR's used boundary. Without this,
* PtrOffset(e1, hdr + used) underflows to a quasi-infinite
* size_t when fed to the memmove() below.
*
* Also reject esize == 0: memmove(e, e, ...) is a no-op and
* leaves hdr->used unchanged, masking the crafted entry.
*/
if (!esize || Add2Ptr(e, esize) > Add2Ptr(hdr, used) ||
PtrOffset(e1, Add2Ptr(hdr, used)) < esize)
goto dirty_vol;
nsize = esize;
memmove(e, e1, PtrOffset(e1, Add2Ptr(hdr, used)));
hdr->used = cpu_to_le32(used - nsize);
@ -3689,6 +3754,12 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
goto dirty_vol;
}
/* See UpdateRecordDataRoot for the rationale. */
if (le16_to_cpu(e->view.data_off) > le16_to_cpu(e->size) ||
le16_to_cpu(e->view.data_off) + dlen >
le16_to_cpu(e->size))
goto dirty_vol;
memmove(Add2Ptr(e, le16_to_cpu(e->view.data_off)), data, dlen);
a_dirty = true;
@ -3796,11 +3867,7 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
log->l_size = log->orig_file_size = ni->vfs_inode.i_size;
/* Get the size of page. NOTE: To replay we can use default page. */
#if PAGE_SIZE >= DefaultLogPageSize && PAGE_SIZE <= DefaultLogPageSize * 2
log->page_size = norm_file_page(PAGE_SIZE, &log->l_size, true);
#else
log->page_size = norm_file_page(PAGE_SIZE, &log->l_size, false);
#endif
if (!log->page_size) {
err = -EINVAL;
goto out;
@ -3938,9 +4005,28 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
*/
t32 = le32_to_cpu(log->rst_info.r_page->sys_page_size);
if (log->page_size != t32) {
u32 old_page_size = log->page_size;
log->l_size = log->orig_file_size;
log->page_size = norm_file_page(t32, &log->l_size,
t32 == DefaultLogPageSize);
/*
* If the adopted on-disk page size is larger than the size used
* to allocate one_page_buf above, grow the scratch buffer so a
* later read_log_page() cannot overflow it.
*/
if (log->page_size > old_page_size) {
void *buf;
buf = krealloc(log->one_page_buf, log->page_size,
GFP_NOFS);
if (!buf) {
err = -ENOMEM;
goto out;
}
log->one_page_buf = buf;
}
}
if (log->page_size != t32 ||
@ -4219,13 +4305,26 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
if (rst->major_ver)
goto end_conv_1; /* reduce tab pressure. */
t16 = le16_to_cpu(dptbl->size);
if (t16 < sizeof(struct DIR_PAGE_ENTRY)) {
log->set_dirty = true;
goto out;
}
t32 = (t16 - sizeof(struct DIR_PAGE_ENTRY)) / sizeof(u64);
dp = NULL;
while ((dp = enum_rstbl(dptbl, dp))) {
struct DIR_PAGE_ENTRY_32 *dp0 = (struct DIR_PAGE_ENTRY_32 *)dp;
// NOTE: Danger. Check for of boundary.
u32 lcns = le32_to_cpu(dp->lcns_follow);
if (lcns > t32) {
log->set_dirty = true;
goto out;
}
memmove(&dp->vcn, &dp0->vcn_low,
2 * sizeof(u64) +
le32_to_cpu(dp->lcns_follow) * sizeof(u64));
2 * sizeof(u64) + lcns * sizeof(u64));
}
end_conv_1:
@ -4547,12 +4646,34 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
* whole routine a loop, case Lcns do not fit below.
*/
t16 = le16_to_cpu(lrh->lcns_follow);
t32 = le32_to_cpu(dp->lcns_follow);
if (le64_to_cpu(lrh->target_vcn) < le64_to_cpu(dp->vcn)) {
err = -EINVAL;
goto out;
}
/*
* find_dp() only validates that target_vcn is the first
* cluster covered by dp. The walk through lrh->lcns_follow
* further entries must stay within the allocated
* dp->page_lcns[] array, which is sized by dp->lcns_follow.
*/
if (le64_to_cpu(lrh->target_vcn) - le64_to_cpu(dp->vcn) + t16 >
le32_to_cpu(dp->lcns_follow)) {
err = -EINVAL;
log->set_dirty = true;
goto out;
}
for (i = 0; i < t16; i++) {
size_t j = (size_t)(le64_to_cpu(lrh->target_vcn) -
le64_to_cpu(dp->vcn));
if (j >= t32 || i >= t32 - j) {
err = -EINVAL;
goto out;
}
dp->page_lcns[j + i] = lrh->page_lcns[i];
}
goto next_log_record_analyze;
case DeleteDirtyClusters: {

View File

@ -2654,7 +2654,6 @@ int ntfs_set_label(struct ntfs_sb_info *sbi, u8 *label, int len)
struct ATTRIB *attr;
u32 uni_bytes;
struct ntfs_inode *ni = sbi->volume.ni;
/* Allocate PATH_MAX bytes. */
struct cpu_str *uni = kmalloc(PATH_MAX, GFP_KERNEL);
if (!uni)

View File

@ -611,16 +611,51 @@ static const struct NTFS_DE *hdr_insert_head(struct INDEX_HDR *hdr,
*/
static bool index_hdr_check(const struct INDEX_HDR *hdr, u32 bytes)
{
const bool has_subnode = hdr_has_subnode(hdr);
const u16 min_size = sizeof(struct NTFS_DE) +
(has_subnode ? sizeof(u64) : 0);
u32 end = le32_to_cpu(hdr->used);
u32 tot = le32_to_cpu(hdr->total);
u32 off = le32_to_cpu(hdr->de_off);
const struct NTFS_DE *e;
if (!IS_ALIGNED(off, 8) || tot > bytes || end > tot ||
size_add(off, sizeof(struct NTFS_DE)) > end) {
size_add(off, min_size) > end) {
/* incorrect index buffer. */
return false;
}
/* Ensure every key stays inside its entry before lookup walks it. */
e = (const struct NTFS_DE *)((const u8 *)hdr + off);
for (;;) {
u16 e_size = le16_to_cpu(e->size);
u16 key_size = le16_to_cpu(e->key_size);
u16 data_size;
if (!IS_ALIGNED(e_size, 8) || e_size < min_size ||
de_has_vcn(e) != has_subnode) {
/* incorrect index entry. */
return false;
}
if (size_add(off, e_size) > end)
return false;
if (de_is_last(e)) {
if (key_size)
return false;
break;
}
data_size = e_size - min_size;
if (key_size > data_size)
return false;
off += e_size;
e = (const struct NTFS_DE *)((const u8 *)hdr + off);
}
return true;
}
@ -754,6 +789,10 @@ static struct NTFS_DE *hdr_find_e(const struct ntfs_index *indx,
binary_search:
e_key_len = le16_to_cpu(e->key_size);
/* Validate key_size fits within the entry data area. */
if (e_key_len > le16_to_cpu(e->size) - sizeof(struct NTFS_DE))
return NULL;
diff2 = (*cmp)(key, key_len, e + 1, e_key_len, ctx);
if (diff2 > 0) {
if (found) {
@ -1506,6 +1545,7 @@ static int indx_add_allocate(struct ntfs_index *indx, struct ntfs_inode *ni,
if (bit != MINUS_ONE_T) {
bmp = NULL;
bmp_size = bmp_size_v = 0;
} else {
if (bmp->non_res) {
bmp_size = le64_to_cpu(bmp->nres.data_size);
@ -1587,7 +1627,8 @@ static int indx_add_allocate(struct ntfs_index *indx, struct ntfs_inode *ni,
static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni,
const struct NTFS_DE *new_de,
struct NTFS_DE *root_de, const void *ctx,
struct ntfs_fnd *fnd, bool undo, NTFS_CMP_FUNC cmp)
struct ntfs_fnd *fnd, bool undo,
NTFS_CMP_FUNC cmp)
{
int err = 0;
struct NTFS_DE *e, *e0, *re;
@ -1742,6 +1783,22 @@ static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni,
hdr_used = le32_to_cpu(hdr->used);
hdr_total = le32_to_cpu(hdr->total);
/*
* The destination INDEX_BUFFER has 'hdr_total' bytes of payload
* available after the header, of which 'hdr_used' are already
* consumed by the single terminal END entry installed by
* indx_new(). A crafted image can present a resident root whose
* non-last entries (summing to 'to_move') exceed what fits in
* this buffer; copying them unchecked would overrun the
* kmalloc(1u << indx->index_bits) allocation backing the new
* buffer. Reject the copy in that case.
*/
if (to_move > hdr_total - hdr_used) {
err = -EINVAL;
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
goto out_put_n;
}
/* Copy root entries into new buffer. */
hdr_insert_head(hdr, re, to_move);
@ -1796,13 +1853,15 @@ static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni,
* Attempt to insert an entry into an Index Allocation Buffer.
* If necessary, it will split the buffer.
*/
static int
indx_insert_into_buffer(struct ntfs_index *indx, struct ntfs_inode *ni,
struct INDEX_ROOT *root, const struct NTFS_DE *new_de,
const void *ctx, int level, struct ntfs_fnd *fnd, NTFS_CMP_FUNC cmp)
static int indx_insert_into_buffer(struct ntfs_index *indx,
struct ntfs_inode *ni,
struct INDEX_ROOT *root,
const struct NTFS_DE *new_de,
const void *ctx, int level,
struct ntfs_fnd *fnd, NTFS_CMP_FUNC cmp)
{
int err;
const struct NTFS_DE *sp;
const struct NTFS_DE *sp; /* split_point */
struct NTFS_DE *e, *de_t, *up_e;
struct indx_node *n2;
struct indx_node *n1 = fnd->nodes[level];
@ -1828,10 +1887,9 @@ indx_insert_into_buffer(struct ntfs_index *indx, struct ntfs_inode *ni,
* No space to insert into buffer. Split it.
* To split we:
* - Save split point ('cause index buffers will be changed)
* - Allocate NewBuffer and copy all entries <= sp into new buffer
* - Remove all entries (sp including) from TargetBuffer
* - Insert NewEntry into left or right buffer (depending on sp <=>
* NewEntry)
* - Allocate new buffer (up_e) and copy all entries <= sp into new buffer
* - Remove all entries (sp including) from hdr1
* - Insert new_de into left or right buffer (depending on sp <=> new_de)
* - Insert sp into parent buffer (or root)
* - Make sp a parent for new buffer
*/
@ -1845,7 +1903,22 @@ indx_insert_into_buffer(struct ntfs_index *indx, struct ntfs_inode *ni,
return -ENOMEM;
memcpy(up_e, sp, sp_size);
/* Make a copy for undo. */
used1 = le32_to_cpu(hdr1->used);
/*
* hdr_find_split does not validate per-entry sizes, so a crafted
* NTFS_DE whose le16 size field is out of range can place sp such
* that (PtrOffset(hdr1, sp) + sp_size) exceeds used1. Without this
* guard the u32 'used = used1 - to_copy - sp_size' underflows and
* the subsequent memmove count becomes a near-4-GiB value,
* triggering an out-of-bounds kernel write.
*/
if (PtrOffset(hdr1, sp) + sp_size > used1) {
err = -EINVAL;
goto out;
}
hdr1_saved = kmemdup(hdr1, used1, GFP_NOFS);
if (!hdr1_saved) {
err = -ENOMEM;
@ -1894,8 +1967,7 @@ indx_insert_into_buffer(struct ntfs_index *indx, struct ntfs_inode *ni,
*/
hdr_insert_de(indx,
(*cmp)(new_de + 1, le16_to_cpu(new_de->key_size),
up_e + 1, le16_to_cpu(up_e->key_size),
ctx) < 0 ?
up_e + 1, le16_to_cpu(up_e->key_size), ctx) < 0 ?
hdr2 :
hdr1,
new_de, NULL, ctx, cmp);
@ -1912,11 +1984,13 @@ indx_insert_into_buffer(struct ntfs_index *indx, struct ntfs_inode *ni,
* insert the promoted entry into the parent.
*/
if (!level) {
/* Insert in root. */
err = indx_insert_into_root(indx, ni, up_e, NULL, ctx, fnd, 0, cmp);
/* Insert split_point in root. */
err = indx_insert_into_root(indx, ni, up_e, NULL, ctx, fnd, 0,
cmp);
} else {
/*
* The target buffer's parent is another index buffer.
* Insert split_point in parent index ( call itself recursively )
* TODO: Remove recursion.
*/
err = indx_insert_into_buffer(indx, ni, root, up_e, ctx,
@ -2022,13 +2096,21 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni,
static struct indx_node *indx_find_buffer(struct ntfs_index *indx,
struct ntfs_inode *ni,
const struct INDEX_ROOT *root,
__le64 vbn, struct indx_node *n)
__le64 vbn, struct indx_node *n,
int depth)
{
int err;
const struct NTFS_DE *e;
struct indx_node *r;
const struct INDEX_HDR *hdr = n ? &n->index->ihdr : &root->ihdr;
/*
* Limit recursion depth to prevent stack overflow from crafted
* images. Use the same bound as the fnd->nodes array (20).
*/
if (depth > ARRAY_SIZE(((struct ntfs_fnd *)NULL)->nodes))
return ERR_PTR(-EINVAL);
/* Step 1: Scan one level. */
for (e = hdr_first_de(hdr);; e = hdr_next_de(hdr, e)) {
if (!e)
@ -2049,7 +2131,8 @@ static struct indx_node *indx_find_buffer(struct ntfs_index *indx,
if (err)
return ERR_PTR(err);
r = indx_find_buffer(indx, ni, root, vbn, n);
r = indx_find_buffer(indx, ni, root, vbn, n,
depth + 1);
if (r)
return r;
}
@ -2462,7 +2545,7 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni,
fnd_clear(fnd);
in = indx_find_buffer(indx, ni, root, sub_vbn, NULL);
in = indx_find_buffer(indx, ni, root, sub_vbn, NULL, 0);
if (IS_ERR(in)) {
err = PTR_ERR(in);
goto out;

View File

@ -592,7 +592,6 @@ static void ntfs_iomap_read_end_io(struct bio *bio)
u32 f_size = folio_size(folio);
loff_t f_pos = folio_pos(folio);
if (valid < f_pos + f_size) {
u32 z_from = valid <= f_pos ?
0 :
@ -693,17 +692,18 @@ int ntfs_set_size(struct inode *inode, u64 new_size)
return -EFBIG;
}
/* Mark rw ntfs as dirty. It will be cleared at umount. */
ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
ni_lock(ni);
down_write(&ni->file.run_lock);
if (new_size < ni->i_valid)
ni->i_valid = new_size;
/* last 'true' means keep preallocated. */
err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
&ni->i_valid, true);
if (!err) {
i_size_write(inode, new_size);
mark_inode_dirty(inode);
}
up_write(&ni->file.run_lock);
ni_unlock(ni);
@ -764,7 +764,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
clen_max = bytes_to_cluster(sbi, endbyte) - vcn;
}
/*
/*
* Force to allocate clusters if directIO(write) or writeback_range.
* NOTE: attr_data_get_block allocates clusters only for sparse file.
* Normal file allocates clusters in attr_set_size.
@ -778,11 +778,6 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
return err;
}
if (!clen) {
/* broken file? */
return -EINVAL;
}
if (lcn == EOF_LCN) {
/* request out of file. */
if (flags & IOMAP_REPORT) {
@ -801,7 +796,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
if (lcn == RESIDENT_LCN) {
if (offset >= clen) {
kfree(res);
__free_page(virt_to_page(res));
if (flags & IOMAP_REPORT) {
/* special code for report. */
return -ENOENT;
@ -816,6 +811,11 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
return 0;
}
if (!clen) {
/* broken file? */
return -EINVAL;
}
iomap->bdev = inode->i_sb->s_bdev;
iomap->offset = offset;
iomap->length = ((loff_t)clen << cluster_bits) - off;
@ -829,7 +829,6 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
iomap->type = IOMAP_DELALLOC;
iomap->addr = IOMAP_NULL_ADDR;
} else {
/* Translate clusters into bytes. */
iomap->addr = ((loff_t)lcn << cluster_bits) + off;
if (length && iomap->length > length)
@ -921,7 +920,7 @@ static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
out:
if (iomap->type == IOMAP_INLINE) {
kfree(iomap->private);
__free_page(virt_to_page(iomap->private));
iomap->private = NULL;
}
@ -986,37 +985,11 @@ static ssize_t ntfs_writeback_range(struct iomap_writepage_ctx *wpc,
return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
}
static const struct iomap_writeback_ops ntfs_writeback_ops = {
.writeback_range = ntfs_writeback_range,
.writeback_submit = iomap_ioend_writeback_submit,
};
static int ntfs_resident_writepage(struct folio *folio,
struct writeback_control *wbc)
{
struct address_space *mapping = folio->mapping;
struct inode *inode = mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
int ret;
/* Avoid any operation if inode is bad. */
if (unlikely(is_bad_ni(ni)))
return -EINVAL;
if (unlikely(ntfs3_forced_shutdown(inode->i_sb)))
return -EIO;
ni_lock(ni);
ret = attr_data_write_resident(ni, folio);
ni_unlock(ni);
if (ret != E_NTFS_NONRESIDENT)
folio_unlock(folio);
mapping_set_error(mapping, ret);
return ret;
}
static int ntfs_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
@ -1024,7 +997,7 @@ static int ntfs_writepages(struct address_space *mapping,
struct inode *inode = mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
struct iomap_writepage_ctx wpc = {
.inode = mapping->host,
.inode = inode,
.wbc = wbc,
.ops = &ntfs_writeback_ops,
};
@ -1038,9 +1011,22 @@ static int ntfs_writepages(struct address_space *mapping,
if (is_resident(ni)) {
struct folio *folio = NULL;
err = 0;
while ((folio = writeback_iter(mapping, wbc, folio, &err)))
err = ntfs_resident_writepage(folio, wbc);
while ((folio = writeback_iter(mapping, wbc, folio, &err))) {
int err2;
ni_lock(ni);
err2 = attr_data_write_resident(ni, folio);
ni_unlock(ni);
folio_unlock(folio);
if (err2) {
mapping_set_error(mapping, err2);
if (!err)
err = err2;
}
}
return err;
}
@ -1291,7 +1277,6 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
if (!(mode & 0222))
fa |= FILE_ATTRIBUTE_READONLY;
/* Allocate PATH_MAX bytes. */
new_de = kzalloc(PATH_MAX, GFP_KERNEL);
if (!new_de) {
err = -ENOMEM;
@ -1730,7 +1715,6 @@ int ntfs_link_inode(struct inode *inode, struct dentry *dentry)
struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
struct NTFS_DE *de;
/* Allocate PATH_MAX bytes. */
de = kzalloc(PATH_MAX, GFP_KERNEL);
if (!de)
return -ENOMEM;
@ -2095,6 +2079,8 @@ const struct inode_operations ntfs_link_inode_operations = {
.get_link = ntfs_get_link,
.setattr = ntfs_setattr,
.listxattr = ntfs_listxattr,
.fileattr_get = ntfs_fileattr_get,
.fileattr_set = ntfs_fileattr_set,
};
const struct address_space_operations ntfs_aops = {

View File

@ -240,7 +240,7 @@ static inline ssize_t decompress_chunk(u8 *unc, u8 *unc_end, const u8 *cmpr,
if (up - unc > LZNT_CHUNK_SIZE)
return -EINVAL;
/* Correct index */
while (unc + s_max_off[index] < up)
while (index < ARRAY_SIZE(s_max_off) - 1 && unc + s_max_off[index] < up)
index += 1;
/* Check the current flag for zero. */

View File

@ -340,7 +340,7 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir,
ntfs_sync_inode(dir);
if (IS_DIRSYNC(new_dir))
ntfs_sync_inode(inode);
ntfs_sync_inode(new_dir);
}
if (dir_ni != new_dir_ni)
@ -519,6 +519,7 @@ const struct inode_operations ntfs_dir_inode_operations = {
.listxattr = ntfs_listxattr,
.fiemap = ntfs_fiemap,
.fileattr_get = ntfs_fileattr_get,
.fileattr_set = ntfs_fileattr_set,
};
const struct inode_operations ntfs_special_inode_operations = {
@ -527,6 +528,8 @@ const struct inode_operations ntfs_special_inode_operations = {
.listxattr = ntfs_listxattr,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
.fileattr_get = ntfs_fileattr_get,
.fileattr_set = ntfs_fileattr_set,
};
const struct dentry_operations ntfs_dentry_ops = {

View File

@ -392,6 +392,9 @@ struct ntfs_inode {
*/
u8 ni_bad;
/* Keep track of FS_NODUMP_FL. */
u8 nodump;
union {
struct ntfs_index dir;
struct {
@ -530,6 +533,8 @@ extern const struct file_operations ntfs_dir_operations;
/* Globals from file.c */
int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
struct file_kattr *fa);
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask, u32 flags);
int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
@ -853,6 +858,9 @@ static inline void mi_get_ref(const struct mft_inode *mi, struct MFT_REF *ref)
/* Globals from run.c */
bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
CLST *len, size_t *index);
bool run_lookup_entry_da(const struct runs_tree *run,
const struct runs_tree *run_da, CLST vcn, CLST *lcn,
CLST *len);
void run_truncate(struct runs_tree *run, CLST vcn);
void run_truncate_head(struct runs_tree *run, CLST vcn);
void run_truncate_around(struct runs_tree *run, CLST vcn);
@ -878,7 +886,8 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
#else
#define run_unpack_ex run_unpack
#endif
int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn);
int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size,
u64 *highest_vcn);
int run_clone(const struct runs_tree *run, struct runs_tree *new_run);
bool run_remove_range(struct runs_tree *run, CLST vcn, CLST len, CLST *done);
CLST run_len(const struct runs_tree *run);

View File

@ -223,6 +223,66 @@ bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
return true;
}
/*
* run_overlaps
*
* true if run overlaps with range [svcn, svcn + len)
*/
static bool run_overlaps(const struct runs_tree *run, CLST svcn, CLST len,
CLST *vcn, CLST *clen)
{
size_t i;
const struct ntfs_run *r = run->runs;
CLST end = svcn + len;
for (i = 0; i < run->count; i++, r++) {
/* Check if [r->vcn, r->vcn+r->len) overlaps [svcn, end). */
if (r->vcn < end && svcn < r->vcn + r->len) {
if (vcn)
*vcn = r->vcn;
if (clen)
*clen = r->len;
return true;
}
}
return false;
}
/*
* run_lookup_entry_da
*
* - lookup vcn in delalloc run
* - lookup vcn in real run
* - correct result if real run overlaps with delalloc
*/
bool run_lookup_entry_da(const struct runs_tree *run,
const struct runs_tree *run_da, CLST vcn, CLST *lcn,
CLST *len)
{
CLST vcn1, len1;
if (run_da && run_lookup_entry(run_da, vcn, lcn, len, NULL)) {
*lcn = DELALLOC_LCN;
return true;
}
if (!run_lookup_entry(run, vcn, lcn, len, NULL))
return false;
if (run_da && run_overlaps(run_da, vcn, *len, &vcn1, &len1)) {
/* Correct return value. */
if (vcn1 > vcn) {
*len = vcn1 - vcn;
} else {
*lcn = DELALLOC_LCN;
*len = len1;
}
}
return true;
}
/*
* run_truncate_head - Decommit the range before vcn.
*/
@ -1205,18 +1265,23 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
* Return the highest vcn from a mapping pairs array
* it used while replaying log file.
*/
int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn)
int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size,
u64 *highest_vcn)
{
const u8 *run_last = run_buf + run_buf_size;
u64 vcn64 = vcn;
u8 size_size;
while ((size_size = *run_buf & 0xF)) {
while (run_buf < run_last && (size_size = *run_buf & 0xF)) {
u8 offset_size = *run_buf++ >> 4;
u64 len;
if (size_size > 8 || offset_size > 8)
return -EINVAL;
if (run_buf + size_size + offset_size > run_last)
return -EINVAL;
len = run_unpack_s64(run_buf, size_size, 0);
if (!len)
return -EINVAL;
@ -1281,7 +1346,6 @@ bool run_remove_range(struct runs_tree *run, CLST vcn, CLST len, CLST *done)
return true;
}
e = run->runs + run->count;
r = run->runs + index;
end = vcn + len;
@ -1292,9 +1356,12 @@ bool run_remove_range(struct runs_tree *run, CLST vcn, CLST len, CLST *done)
if (r_end > end) {
/* Remove a middle part, split. */
CLST tail_lcn = r->lcn == SPARSE_LCN ?
SPARSE_LCN : (r->lcn + (end - r->vcn));
*done += len;
r->len = d;
return run_add_entry(run, end, r->lcn, r_end - end,
return run_add_entry(run, end, tail_lcn, r_end - end,
false);
}
/* Remove tail of run .*/

View File

@ -851,6 +851,12 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de,
return err;
}
static bool ntfs_is_reserved_lxattr(const char *name)
{
return !strcmp(name, "$LXUID") || !strcmp(name, "$LXGID") ||
!strcmp(name, "$LXMOD") || !strcmp(name, "$LXDEV");
}
/*
* ntfs_setxattr - inode_operations::setxattr
*/
@ -867,7 +873,9 @@ static noinline int ntfs_setxattr(const struct xattr_handler *handler,
if (!strcmp(name, SYSTEM_DOS_ATTRIB)) {
if (sizeof(u8) != size)
goto out;
new_fa = cpu_to_le32(*(u8 *)value);
/* system.dos_attrib only covers the low DOS attribute byte. */
new_fa = (ni->std_fa & ~cpu_to_le32(0xff)) |
cpu_to_le32(*(u8 *)value);
goto set_new_fa;
}
@ -955,6 +963,12 @@ static noinline int ntfs_setxattr(const struct xattr_handler *handler,
goto out;
}
/* Do not allow non privileged users to change $LXUID/$LXGID... */
if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN)) {
err = -EPERM;
goto out;
}
/* Deal with NTFS extended attribute. */
err = ntfs_set_ea(inode, name, strlen(name), value, size, flags, 0,
NULL);