Changes for 6.15-rc1

Fixed:
   integer overflows on 32-bit systems;
   integer overflow in hdr_first_de();
   'proc_info_root' leak when NTFS initialization failed.
 
 Removed:
   unused functions ni_load_attr, ntfs_sb_read, ntfs_flush_inodes.
 
 Changed:
   updated inode->i_mapping->a_ops on compression state;
   ensured atomicity of write operations;
   refactored ntfs_{create/remove}_procdir();
   refactored ntfs_{create/remove}_proc_root().
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEh0DEKNP0I9IjwfWEqbAzH4MkB7YFAmfruBwACgkQqbAzH4Mk
 B7bEVhAApRbsnqA1R6xPc7nroKagZBVhBKANOND8Duxvdt7fkWVWRYlSzvngpEMc
 ikLhqQVx+6MEuQXuw3B8r1/HnCjfWxyiC4eIwTC8qdt3rG8LWdAn3qOdMSKGcw5t
 fHvV9o2kwM8JwI6GQ5KSBloirbPcDLsuuriT77aw8jRGQICcpDW84YhVQ4kT78si
 DEuIXxBuYzpeNxtvn3YVU4w3IIjPlM4pfPpMD+NdpGuwJJytNqugc1iU+JUR9Rbe
 AGNPuMdWLMhX8oB0JLn5WUyVtdLWbrCOWq460YLCkqvv6ceD2vQdYo0Eq5Jq6z1D
 PpT1hnGAO6NeBaq8Fy0t1eFMAqIjxVK0zes8HKvmC6NiOZY7qrp1h/SFkzI0Nr0c
 B5pYyng4DCNu+fVb9z72tI3GyBlu/puaSvZARXXwzJAleb1ZnXu6LWz3kf4ZqnuC
 BvSyCLvQGYeKN9Ml3ImIwh1o/MrHlk0KFKr5nvNeMmSWZRX9rHAfTD8dMtjlRqae
 flhCLqudAeSvcki/qQ3qC59xiYDJj+1ZyJcqQARZzBJd3HfJ2aQ+AQM5xHOshbHH
 WZWn6QaV0rLMue9KI4lro22er4kPO/rfCWi63xnHAqeiGVEza97Q1nlrlOpi1Fjh
 96+RVyUNxYW/aAp9tMLUI2Cha1sMNrFnm6rXLr6IDsSOdtRLxIk=
 =N3l4
 -----END PGP SIGNATURE-----

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

Pull ntfs3 updates from Konstantin Komarov:

 - Fix integer overflows on 32-bit systems and in hdr_first_de()

 - Fix 'proc_info_root' leak on NTFS initialization failure

 - Remove unused functions ni_load_attr, ntfs_sb_read, ntfs_flush_inodes

 - update inode->i_mapping->a_ops on compression state

 - ensure atomicity of write operations

 - refactor ntfs_{create/remove}_{procdir,proc_root}()

* tag 'ntfs3_for_6.15' of https://github.com/Paragon-Software-Group/linux-ntfs3:
  fs/ntfs3: Remove unused ntfs_flush_inodes
  fs/ntfs3: Remove unused ntfs_sb_read
  fs/ntfs3: Remove unused ni_load_attr
  fs/ntfs3: Prevent integer overflow in hdr_first_de()
  fs/ntfs3: Fix a couple integer overflows on 32bit systems
  fs/ntfs3: Update inode->i_mapping->a_ops on compression state
  fs/ntfs3: Fix WARNING in ntfs_extend_initialized_size
  fs/ntfs3: Fix 'proc_info_root' leak when init ntfs failed
  fs/ntfs3: Factor out ntfs_{create/remove}_proc_root()
  fs/ntfs3: Factor out ntfs_{create/remove}_procdir()
  fs/ntfs3: Keep write operations atomic
This commit is contained in:
Linus Torvalds 2025-04-02 16:30:02 -07:00
commit 0cc5543fad
9 changed files with 96 additions and 181 deletions

View File

@ -2664,8 +2664,9 @@ int attr_set_compress(struct ntfs_inode *ni, bool compr)
attr->nres.run_off = cpu_to_le16(run_off);
}
/* Update data attribute flags. */
/* Update attribute flags. */
if (compr) {
attr->flags &= ~ATTR_FLAG_SPARSED;
attr->flags |= ATTR_FLAG_COMPRESSED;
attr->nres.c_unit = NTFS_LZNT_CUNIT;
} else {

View File

@ -101,8 +101,26 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
/* Allowed to change compression for empty files and for directories only. */
if (!is_dedup(ni) && !is_encrypted(ni) &&
(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) {
/* Change compress state. */
int err = ni_set_compress(inode, flags & FS_COMPR_FL);
int err = 0;
struct address_space *mapping = inode->i_mapping;
/* write out all data and wait. */
filemap_invalidate_lock(mapping);
err = filemap_write_and_wait(mapping);
if (err >= 0) {
/* Change compress state. */
bool compr = flags & FS_COMPR_FL;
err = ni_set_compress(inode, compr);
/* For files change a_ops too. */
if (!err)
mapping->a_ops = compr ? &ntfs_aops_cmpr :
&ntfs_aops;
}
filemap_invalidate_unlock(mapping);
if (err)
return err;
}
@ -412,6 +430,7 @@ static int ntfs_extend(struct inode *inode, loff_t pos, size_t count,
}
if (extend_init && !is_compressed(ni)) {
WARN_ON(ni->i_valid >= pos);
err = ntfs_extend_initialized_size(file, ni, ni->i_valid, pos);
if (err)
goto out;
@ -1228,21 +1247,22 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
ssize_t ret;
int err;
err = check_write_restriction(inode);
if (err)
return err;
if (is_compressed(ni) && (iocb->ki_flags & IOCB_DIRECT)) {
ntfs_inode_warn(inode, "direct i/o + compressed not supported");
return -EOPNOTSUPP;
}
if (!inode_trylock(inode)) {
if (iocb->ki_flags & IOCB_NOWAIT)
return -EAGAIN;
inode_lock(inode);
}
ret = check_write_restriction(inode);
if (ret)
goto out;
if (is_compressed(ni) && (iocb->ki_flags & IOCB_DIRECT)) {
ntfs_inode_warn(inode, "direct i/o + compressed not supported");
ret = -EOPNOTSUPP;
goto out;
}
ret = generic_write_checks(iocb, from);
if (ret <= 0)
goto out;

View File

@ -280,63 +280,6 @@ struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr,
return rec_find_attr_le(ni, mi2, le2);
}
/*
* ni_load_attr - Load attribute that contains given VCN.
*/
struct ATTRIB *ni_load_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
const __le16 *name, u8 name_len, CLST vcn,
struct mft_inode **pmi)
{
struct ATTR_LIST_ENTRY *le;
struct ATTRIB *attr;
struct mft_inode *mi;
struct ATTR_LIST_ENTRY *next;
if (!ni->attr_list.size) {
if (pmi)
*pmi = &ni->mi;
return mi_find_attr(ni, &ni->mi, NULL, type, name, name_len,
NULL);
}
le = al_find_ex(ni, NULL, type, name, name_len, NULL);
if (!le)
return NULL;
/*
* Unfortunately ATTR_LIST_ENTRY contains only start VCN.
* So to find the ATTRIB segment that contains 'vcn' we should
* enumerate some entries.
*/
if (vcn) {
for (;; le = next) {
next = al_find_ex(ni, le, type, name, name_len, NULL);
if (!next || le64_to_cpu(next->vcn) > vcn)
break;
}
}
if (ni_load_mi(ni, le, &mi))
return NULL;
if (pmi)
*pmi = mi;
attr = mi_find_attr(ni, mi, NULL, type, name, name_len, &le->id);
if (!attr)
return NULL;
if (!attr->non_res)
return attr;
if (le64_to_cpu(attr->nres.svcn) <= vcn &&
vcn <= le64_to_cpu(attr->nres.evcn))
return attr;
_ntfs_bad_inode(&ni->vfs_inode);
return NULL;
}
/*
* ni_load_all_mi - Load all subrecords.
*/
@ -3434,10 +3377,12 @@ int ni_set_compress(struct inode *inode, bool compr)
}
ni->std_fa = std->fa;
if (compr)
if (compr) {
std->fa &= ~FILE_ATTRIBUTE_SPARSE_FILE;
std->fa |= FILE_ATTRIBUTE_COMPRESSED;
else
} else {
std->fa &= ~FILE_ATTRIBUTE_COMPRESSED;
}
if (ni->std_fa != std->fa) {
ni->std_fa = std->fa;

View File

@ -1035,34 +1035,6 @@ struct buffer_head *ntfs_bread(struct super_block *sb, sector_t block)
return NULL;
}
int ntfs_sb_read(struct super_block *sb, u64 lbo, size_t bytes, void *buffer)
{
struct block_device *bdev = sb->s_bdev;
u32 blocksize = sb->s_blocksize;
u64 block = lbo >> sb->s_blocksize_bits;
u32 off = lbo & (blocksize - 1);
u32 op = blocksize - off;
for (; bytes; block += 1, off = 0, op = blocksize) {
struct buffer_head *bh = __bread(bdev, block, blocksize);
if (!bh)
return -EIO;
if (op > bytes)
op = bytes;
memcpy(buffer, bh->b_data + off, op);
put_bh(bh);
bytes -= op;
buffer = Add2Ptr(buffer, op);
}
return 0;
}
int ntfs_sb_write(struct super_block *sb, u64 lbo, size_t bytes,
const void *buf, int wait)
{

View File

@ -618,7 +618,7 @@ static bool index_hdr_check(const struct INDEX_HDR *hdr, u32 bytes)
u32 off = le32_to_cpu(hdr->de_off);
if (!IS_ALIGNED(off, 8) || tot > bytes || end > tot ||
off + sizeof(struct NTFS_DE) > end) {
size_add(off, sizeof(struct NTFS_DE)) > end) {
/* incorrect index buffer. */
return false;
}
@ -736,7 +736,7 @@ fill_table:
if (end > total)
return NULL;
if (off + sizeof(struct NTFS_DE) > end)
if (size_add(off, sizeof(struct NTFS_DE)) > end)
return NULL;
e = Add2Ptr(hdr, off);

View File

@ -1024,46 +1024,6 @@ int ntfs_sync_inode(struct inode *inode)
return _ni_write_inode(inode, 1);
}
/*
* writeback_inode - Helper function for ntfs_flush_inodes().
*
* This writes both the inode and the file data blocks, waiting
* for in flight data blocks before the start of the call. It
* does not wait for any io started during the call.
*/
static int writeback_inode(struct inode *inode)
{
int ret = sync_inode_metadata(inode, 0);
if (!ret)
ret = filemap_fdatawrite(inode->i_mapping);
return ret;
}
/*
* ntfs_flush_inodes
*
* Write data and metadata corresponding to i1 and i2. The io is
* started but we do not wait for any of it to finish.
*
* filemap_flush() is used for the block device, so if there is a dirty
* page for a block already in flight, we will not wait and start the
* io over again.
*/
int ntfs_flush_inodes(struct super_block *sb, struct inode *i1,
struct inode *i2)
{
int ret = 0;
if (i1)
ret = writeback_inode(i1);
if (!ret && i2)
ret = writeback_inode(i2);
if (!ret)
ret = filemap_flush(sb->s_bdev_file->f_mapping);
return ret;
}
/*
* Helper function to read file.
*/

View File

@ -717,7 +717,7 @@ static inline struct NTFS_DE *hdr_first_de(const struct INDEX_HDR *hdr)
struct NTFS_DE *e;
u16 esize;
if (de_off >= used || de_off + sizeof(struct NTFS_DE) > used )
if (de_off >= used || size_add(de_off, sizeof(struct NTFS_DE)) > used)
return NULL;
e = Add2Ptr(hdr, de_off);

View File

@ -530,9 +530,6 @@ struct ATTRIB *ni_find_attr(struct ntfs_inode *ni, struct ATTRIB *attr,
struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr,
struct ATTR_LIST_ENTRY **le,
struct mft_inode **mi);
struct ATTRIB *ni_load_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
const __le16 *name, u8 name_len, CLST vcn,
struct mft_inode **pmi);
int ni_load_all_mi(struct ntfs_inode *ni);
bool ni_add_subrecord(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi);
int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
@ -619,7 +616,6 @@ enum NTFS_DIRTY_FLAGS {
NTFS_DIRTY_ERROR = 2,
};
int ntfs_set_state(struct ntfs_sb_info *sbi, enum NTFS_DIRTY_FLAGS dirty);
int ntfs_sb_read(struct super_block *sb, u64 lbo, size_t bytes, void *buffer);
int ntfs_sb_write(struct super_block *sb, u64 lbo, size_t bytes,
const void *buffer, int wait);
int ntfs_sb_write_run(struct ntfs_sb_info *sbi, const struct runs_tree *run,
@ -717,8 +713,6 @@ int ntfs_write_end(struct file *file, struct address_space *mapping, loff_t pos,
u32 len, u32 copied, struct folio *folio, void *fsdata);
int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc);
int ntfs_sync_inode(struct inode *inode);
int ntfs_flush_inodes(struct super_block *sb, struct inode *i1,
struct inode *i2);
int inode_read_data(struct inode *inode, void *data, size_t bytes);
int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const struct cpu_str *uni,

View File

@ -555,6 +555,55 @@ static const struct proc_ops ntfs3_label_fops = {
.proc_write = ntfs3_label_write,
};
static void ntfs_create_procdir(struct super_block *sb)
{
struct proc_dir_entry *e;
if (!proc_info_root)
return;
e = proc_mkdir(sb->s_id, proc_info_root);
if (e) {
struct ntfs_sb_info *sbi = sb->s_fs_info;
proc_create_data("volinfo", 0444, e,
&ntfs3_volinfo_fops, sb);
proc_create_data("label", 0644, e,
&ntfs3_label_fops, sb);
sbi->procdir = e;
}
}
static void ntfs_remove_procdir(struct super_block *sb)
{
struct ntfs_sb_info *sbi = sb->s_fs_info;
if (!sbi->procdir)
return;
remove_proc_entry("label", sbi->procdir);
remove_proc_entry("volinfo", sbi->procdir);
remove_proc_entry(sb->s_id, proc_info_root);
sbi->procdir = NULL;
}
static void ntfs_create_proc_root(void)
{
proc_info_root = proc_mkdir("fs/ntfs3", NULL);
}
static void ntfs_remove_proc_root(void)
{
if (proc_info_root) {
remove_proc_entry("fs/ntfs3", NULL);
proc_info_root = NULL;
}
}
#else
static void ntfs_create_procdir(struct super_block *sb) {}
static void ntfs_remove_procdir(struct super_block *sb) {}
static void ntfs_create_proc_root(void) {}
static void ntfs_remove_proc_root(void) {}
#endif
static struct kmem_cache *ntfs_inode_cachep;
@ -644,15 +693,7 @@ static void ntfs_put_super(struct super_block *sb)
{
struct ntfs_sb_info *sbi = sb->s_fs_info;
#ifdef CONFIG_PROC_FS
// Remove /proc/fs/ntfs3/..
if (sbi->procdir) {
remove_proc_entry("label", sbi->procdir);
remove_proc_entry("volinfo", sbi->procdir);
remove_proc_entry(sb->s_id, proc_info_root);
sbi->procdir = NULL;
}
#endif
ntfs_remove_procdir(sb);
/* Mark rw ntfs as clear, if possible. */
ntfs_set_state(sbi, NTFS_DIRTY_CLEAR);
@ -1590,20 +1631,7 @@ load_root:
kfree(boot2);
}
#ifdef CONFIG_PROC_FS
/* Create /proc/fs/ntfs3/.. */
if (proc_info_root) {
struct proc_dir_entry *e = proc_mkdir(sb->s_id, proc_info_root);
static_assert((S_IRUGO | S_IWUSR) == 0644);
if (e) {
proc_create_data("volinfo", S_IRUGO, e,
&ntfs3_volinfo_fops, sb);
proc_create_data("label", S_IRUGO | S_IWUSR, e,
&ntfs3_label_fops, sb);
sbi->procdir = e;
}
}
#endif
ntfs_create_procdir(sb);
if (is_legacy_ntfs(sb))
sb->s_flags |= SB_RDONLY;
@ -1853,14 +1881,11 @@ static int __init init_ntfs_fs(void)
if (IS_ENABLED(CONFIG_NTFS3_LZX_XPRESS))
pr_info("ntfs3: Read-only LZX/Xpress compression included\n");
#ifdef CONFIG_PROC_FS
/* Create "/proc/fs/ntfs3" */
proc_info_root = proc_mkdir("fs/ntfs3", NULL);
#endif
ntfs_create_proc_root();
err = ntfs3_init_bitmap();
if (err)
return err;
goto out2;
ntfs_inode_cachep = kmem_cache_create(
"ntfs_inode_cache", sizeof(struct ntfs_inode), 0,
@ -1880,6 +1905,8 @@ out:
kmem_cache_destroy(ntfs_inode_cachep);
out1:
ntfs3_exit_bitmap();
out2:
ntfs_remove_proc_root();
return err;
}
@ -1890,11 +1917,7 @@ static void __exit exit_ntfs_fs(void)
unregister_filesystem(&ntfs_fs_type);
unregister_as_ntfs_legacy();
ntfs3_exit_bitmap();
#ifdef CONFIG_PROC_FS
if (proc_info_root)
remove_proc_entry("fs/ntfs3", NULL);
#endif
ntfs_remove_proc_root();
}
MODULE_LICENSE("GPL");