mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
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:
commit
f0e6f20cb5
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
335
fs/ntfs3/file.c
335
fs/ntfs3/file.c
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
159
fs/ntfs3/fslog.c
159
fs/ntfs3/fslog.c
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
119
fs/ntfs3/index.c
119
fs/ntfs3/index.c
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 .*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user