summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/ntfs3/attrib.c29
-rw-r--r--fs/ntfs3/dir.c26
-rw-r--r--fs/ntfs3/file.c335
-rw-r--r--fs/ntfs3/frecord.c38
-rw-r--r--fs/ntfs3/fslog.c157
-rw-r--r--fs/ntfs3/fsntfs.c1
-rw-r--r--fs/ntfs3/index.c119
-rw-r--r--fs/ntfs3/inode.c78
-rw-r--r--fs/ntfs3/lznt.c2
-rw-r--r--fs/ntfs3/namei.c5
-rw-r--r--fs/ntfs3/ntfs_fs.h11
-rw-r--r--fs/ntfs3/run.c75
-rw-r--r--fs/ntfs3/xattr.c16
13 files changed, 590 insertions, 302 deletions
diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index e61c5bf7e27e..c621a4c582f9 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -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 @@ again:
*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 @@ again:
}
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 @@ again:
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;
diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index d99ab086ef6f..873d52233003 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -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);
diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index ad9350d7fc3f..d601f088618c 100644
--- a/fs/ntfs3/file.c
+++ b/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))
@@ -181,34 +259,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
*/
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
@@ -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 @@ out:
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 = {
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index 7b035da63c12..2b49bc077558 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -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);
}
/*
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index acfa18b84401..f038c799e7ac 100644
--- a/fs/ntfs3/fslog.c
+++ b/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))
+ if (size_add(de_off, esize) > used)
+ return false;
+
+ if (de_is_last(e)) {
+ if (key_size)
+ return false;
+
break;
+ }
- e = next;
+ 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 @@ skip_load_parent:
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 @@ move_data:
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 @@ move_data:
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 @@ move_data:
}
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 @@ move_data:
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 @@ check_restart_area:
*/
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 @@ check_dirty_page_table:
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 @@ copy_lcns:
* 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: {
diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c
index d0434756029b..bc7469d0a34d 100644
--- a/fs/ntfs3/fsntfs.c
+++ b/fs/ntfs3/fsntfs.c
@@ -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)
diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c
index 5344b29b0577..2b439ac04356 100644
--- a/fs/ntfs3/index.c
+++ b/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 @@ fill_table:
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 @@ out1:
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 @@ out_free_root:
* 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 @@ out1:
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;
diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index 42af1abe17f8..c43101cc064d 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -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))) {
+ int err2;
+
+ ni_lock(ni);
+ err2 = attr_data_write_resident(ni, folio);
+ ni_unlock(ni);
- while ((folio = writeback_iter(mapping, wbc, folio, &err)))
- err = ntfs_resident_writepage(folio, wbc);
+ 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 = {
diff --git a/fs/ntfs3/lznt.c b/fs/ntfs3/lznt.c
index fdc9b2ebf341..f818d9785004 100644
--- a/fs/ntfs3/lznt.c
+++ b/fs/ntfs3/lznt.c
@@ -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. */
diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c
index e159ba66a34a..c59de5f2fa97 100644
--- a/fs/ntfs3/namei.c
+++ b/fs/ntfs3/namei.c
@@ -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 = {
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 41db22d652c4..d98d7e474476 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -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);
diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c
index 1ce7d92fb274..3ebf0154eda3 100644
--- a/fs/ntfs3/run.c
+++ b/fs/ntfs3/run.c
@@ -224,6 +224,66 @@ bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
}
/*
+ * 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.
*/
void run_truncate_head(struct runs_tree *run, CLST 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 .*/
diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c
index 9eeac0ab2b71..04814dd29375 100644
--- a/fs/ntfs3/xattr.c
+++ b/fs/ntfs3/xattr.c
@@ -851,6 +851,12 @@ out:
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 @@ set_new_fa:
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);