Skip to content

Commit

Permalink
Merge tag 'ntfs3_for_6.6' of https://github.com/Paragon-Software-Grou…
Browse files Browse the repository at this point in the history
…p/linux-ntfs3

Pull ntfs3 fixes from Konstantin Komarov:

 - memory leak

 - some logic errors, NULL dereferences

 - some code was refactored

 - more sanity checks

* tag 'ntfs3_for_6.6' of https://github.com/Paragon-Software-Group/linux-ntfs3:
  fs/ntfs3: Avoid possible memory leak
  fs/ntfs3: Fix directory element type detection
  fs/ntfs3: Fix possible null-pointer dereference in hdr_find_e()
  fs/ntfs3: Fix OOB read in ntfs_init_from_boot
  fs/ntfs3: fix panic about slab-out-of-bounds caused by ntfs_list_ea()
  fs/ntfs3: Fix NULL pointer dereference on error in attr_allocate_frame()
  fs/ntfs3: Fix possible NULL-ptr-deref in ni_readpage_cmpr()
  fs/ntfs3: Do not allow to change label if volume is read-only
  fs/ntfs3: Add more info into /proc/fs/ntfs3/<dev>/volinfo
  fs/ntfs3: Refactoring and comments
  fs/ntfs3: Fix alternative boot searching
  fs/ntfs3: Allow repeated call to ntfs3_put_sbi
  fs/ntfs3: Use inode_set_ctime_to_ts instead of inode_set_ctime
  fs/ntfs3: Fix shift-out-of-bounds in ntfs_fill_super
  fs/ntfs3: fix deadlock in mark_as_free_ex
  fs/ntfs3: Add more attributes checks in mi_enum_attr()
  fs/ntfs3: Use kvmalloc instead of kmalloc(... __GFP_NOWARN)
  fs/ntfs3: Write immediately updated ntfs state
  fs/ntfs3: Add ckeck in ni_update_parent()
  • Loading branch information
Linus Torvalds committed Oct 19, 2023
2 parents 7cf4bea + e449477 commit f69d00d
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 82 deletions.
12 changes: 5 additions & 7 deletions fs/ntfs3/attrib.c
Original file line number Diff line number Diff line change
Expand Up @@ -1106,10 +1106,10 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
}
}

/*
/*
* The code below may require additional cluster (to extend attribute list)
* and / or one MFT record
* It is too complex to undo operations if -ENOSPC occurs deep inside
* and / or one MFT record
* It is too complex to undo operations if -ENOSPC occurs deep inside
* in 'ni_insert_nonresident'.
* Return in advance -ENOSPC here if there are no free cluster and no free MFT.
*/
Expand Down Expand Up @@ -1736,10 +1736,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
le_b = NULL;
attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
0, NULL, &mi_b);
if (!attr_b) {
err = -ENOENT;
goto out;
}
if (!attr_b)
return -ENOENT;

attr = attr_b;
le = le_b;
Expand Down
15 changes: 13 additions & 2 deletions fs/ntfs3/attrlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)

if (!attr->non_res) {
lsize = le32_to_cpu(attr->res.data_size);
le = kmalloc(al_aligned(lsize), GFP_NOFS | __GFP_NOWARN);
/* attr is resident: lsize < record_size (1K or 4K) */
le = kvmalloc(al_aligned(lsize), GFP_KERNEL);
if (!le) {
err = -ENOMEM;
goto out;
Expand Down Expand Up @@ -80,7 +81,17 @@ int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
if (err < 0)
goto out;

le = kmalloc(al_aligned(lsize), GFP_NOFS | __GFP_NOWARN);
/* attr is nonresident.
* The worst case:
* 1T (2^40) extremely fragmented file.
* cluster = 4K (2^12) => 2^28 fragments
* 2^9 fragments per one record => 2^19 records
* 2^5 bytes of ATTR_LIST_ENTRY per one record => 2^24 bytes.
*
* the result is 16M bytes per attribute list.
* Use kvmalloc to allocate in range [several Kbytes - dozen Mbytes]
*/
le = kvmalloc(al_aligned(lsize), GFP_KERNEL);
if (!le) {
err = -ENOMEM;
goto out;
Expand Down
4 changes: 3 additions & 1 deletion fs/ntfs3/bitmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ void wnd_close(struct wnd_bitmap *wnd)
struct rb_node *node, *next;

kfree(wnd->free_bits);
wnd->free_bits = NULL;
run_close(&wnd->run);

node = rb_first(&wnd->start_tree);
Expand Down Expand Up @@ -659,7 +660,8 @@ int wnd_init(struct wnd_bitmap *wnd, struct super_block *sb, size_t nbits)
wnd->bits_last = wbits;

wnd->free_bits =
kcalloc(wnd->nwnd, sizeof(u16), GFP_NOFS | __GFP_NOWARN);
kvmalloc_array(wnd->nwnd, sizeof(u16), GFP_KERNEL | __GFP_ZERO);

if (!wnd->free_bits)
return -ENOMEM;

Expand Down
6 changes: 5 additions & 1 deletion fs/ntfs3/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,11 @@ static inline int ntfs_filldir(struct ntfs_sb_info *sbi, struct ntfs_inode *ni,
return 0;
}

dt_type = (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY) ? DT_DIR : DT_REG;
/* NTFS: symlinks are "dir + reparse" or "file + reparse" */
if (fname->dup.fa & FILE_ATTRIBUTE_REPARSE_POINT)
dt_type = DT_LNK;
else
dt_type = (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY) ? DT_DIR : DT_REG;

return !dir_emit(ctx, (s8 *)name, name_len, ino, dt_type);
}
Expand Down
4 changes: 2 additions & 2 deletions fs/ntfs3/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -745,8 +745,8 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
}

static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe,
size_t len, unsigned int flags)
struct pipe_inode_info *pipe, size_t len,
unsigned int flags)
{
struct inode *inode = in->f_mapping->host;
struct ntfs_inode *ni = ntfs_i(inode);
Expand Down
8 changes: 7 additions & 1 deletion fs/ntfs3/frecord.c
Original file line number Diff line number Diff line change
Expand Up @@ -2148,7 +2148,7 @@ int ni_readpage_cmpr(struct ntfs_inode *ni, struct page *page)

for (i = 0; i < pages_per_frame; i++) {
pg = pages[i];
if (i == idx)
if (i == idx || !pg)
continue;
unlock_page(pg);
put_page(pg);
Expand Down Expand Up @@ -3208,6 +3208,12 @@ static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup,
if (!fname || !memcmp(&fname->dup, dup, sizeof(fname->dup)))
continue;

/* Check simple case when parent inode equals current inode. */
if (ino_get(&fname->home) == ni->vfs_inode.i_ino) {
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
continue;
}

/* ntfs_iget5 may sleep. */
dir = ntfs_iget5(sb, &fname->home, NULL);
if (IS_ERR(dir)) {
Expand Down
6 changes: 4 additions & 2 deletions fs/ntfs3/fslog.c
Original file line number Diff line number Diff line change
Expand Up @@ -2168,8 +2168,10 @@ static int last_log_lsn(struct ntfs_log *log)

if (!page) {
page = kmalloc(log->page_size, GFP_NOFS);
if (!page)
return -ENOMEM;
if (!page) {
err = -ENOMEM;
goto out;
}
}

/*
Expand Down
19 changes: 8 additions & 11 deletions fs/ntfs3/fsntfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -983,18 +983,11 @@ int ntfs_set_state(struct ntfs_sb_info *sbi, enum NTFS_DIRTY_FLAGS dirty)
if (err)
return err;

mark_inode_dirty(&ni->vfs_inode);
mark_inode_dirty_sync(&ni->vfs_inode);
/* verify(!ntfs_update_mftmirr()); */

/*
* If we used wait=1, sync_inode_metadata waits for the io for the
* inode to finish. It hangs when media is removed.
* So wait=0 is sent down to sync_inode_metadata
* and filemap_fdatawrite is used for the data blocks.
*/
err = sync_inode_metadata(&ni->vfs_inode, 0);
if (!err)
err = filemap_fdatawrite(ni->vfs_inode.i_mapping);
/* write mft record on disk. */
err = _ni_write_inode(&ni->vfs_inode, 1);

return err;
}
Expand Down Expand Up @@ -2461,10 +2454,12 @@ void mark_as_free_ex(struct ntfs_sb_info *sbi, CLST lcn, CLST len, bool trim)
{
CLST end, i, zone_len, zlen;
struct wnd_bitmap *wnd = &sbi->used.bitmap;
bool dirty = false;

down_write_nested(&wnd->rw_lock, BITMAP_MUTEX_CLUSTERS);
if (!wnd_is_used(wnd, lcn, len)) {
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
/* mark volume as dirty out of wnd->rw_lock */
dirty = true;

end = lcn + len;
len = 0;
Expand Down Expand Up @@ -2518,6 +2513,8 @@ void mark_as_free_ex(struct ntfs_sb_info *sbi, CLST lcn, CLST len, bool trim)

out:
up_write(&wnd->rw_lock);
if (dirty)
ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
}

/*
Expand Down
3 changes: 3 additions & 0 deletions fs/ntfs3/index.c
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,9 @@ static struct NTFS_DE *hdr_find_e(const struct ntfs_index *indx,
u32 total = le32_to_cpu(hdr->total);
u16 offs[128];

if (unlikely(!cmp))
return NULL;

fill_table:
if (end > total)
return NULL;
Expand Down
5 changes: 3 additions & 2 deletions fs/ntfs3/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ static struct inode *ntfs_read_mft(struct inode *inode,
nt2kernel(std5->cr_time, &ni->i_crtime);
#endif
nt2kernel(std5->a_time, &inode->i_atime);
ctime = inode_get_ctime(inode);
nt2kernel(std5->c_time, &ctime);
inode_set_ctime_to_ts(inode, ctime);
nt2kernel(std5->m_time, &inode->i_mtime);

ni->std_fa = std5->fa;
Expand Down Expand Up @@ -1660,7 +1660,8 @@ struct inode *ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
d_instantiate(dentry, inode);

/* Set original time. inode times (i_ctime) may be changed in ntfs_init_acl. */
inode->i_atime = inode->i_mtime = inode_set_ctime_to_ts(inode, ni->i_crtime);
inode->i_atime = inode->i_mtime =
inode_set_ctime_to_ts(inode, ni->i_crtime);
dir->i_mtime = inode_set_ctime_to_ts(dir, ni->i_crtime);

mark_inode_dirty(dir);
Expand Down
6 changes: 3 additions & 3 deletions fs/ntfs3/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ static int ntfs_link(struct dentry *ode, struct inode *dir, struct dentry *de)
err = ntfs_link_inode(inode, de);

if (!err) {
dir->i_mtime = inode_set_ctime_to_ts(inode,
inode_set_ctime_current(dir));
dir->i_mtime = inode_set_ctime_to_ts(
inode, inode_set_ctime_current(dir));
mark_inode_dirty(inode);
mark_inode_dirty(dir);
d_instantiate(de, inode);
Expand Down Expand Up @@ -373,7 +373,7 @@ static int ntfs_atomic_open(struct inode *dir, struct dentry *dentry,

#ifdef CONFIG_NTFS3_FS_POSIX_ACL
if (IS_POSIXACL(dir)) {
/*
/*
* Load in cache current acl to avoid ni_lock(dir):
* ntfs_create_inode -> ntfs_init_acl -> posix_acl_create ->
* ntfs_get_acl -> ntfs_get_acl_ex -> ni_lock
Expand Down
2 changes: 1 addition & 1 deletion fs/ntfs3/ntfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ struct OBJECT_ID {
// Birth Volume Id is the Object Id of the Volume on.
// which the Object Id was allocated. It never changes.
struct GUID BirthVolumeId; //0x10:

// Birth Object Id is the first Object Id that was
// ever assigned to this MFT Record. I.e. If the Object Id
// is changed for some reason, this field will reflect the
Expand Down
4 changes: 2 additions & 2 deletions fs/ntfs3/ntfs_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ enum utf16_endian;
#define MINUS_ONE_T ((size_t)(-1))
/* Biggest MFT / smallest cluster */
#define MAXIMUM_BYTES_PER_MFT 4096
#define MAXIMUM_SHIFT_BYTES_PER_MFT 12
#define NTFS_BLOCKS_PER_MFT_RECORD (MAXIMUM_BYTES_PER_MFT / 512)

#define MAXIMUM_BYTES_PER_INDEX 4096
#define MAXIMUM_SHIFT_BYTES_PER_INDEX 12
#define NTFS_BLOCKS_PER_INODE (MAXIMUM_BYTES_PER_INDEX / 512)

/* NTFS specific error code when fixup failed. */
Expand Down Expand Up @@ -495,8 +497,6 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask, u32 flags);
int ntfs3_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
void ntfs_sparse_cluster(struct inode *inode, struct page *page0, CLST vcn,
CLST len);
int ntfs_file_open(struct inode *inode, struct file *file);
int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
__u64 start, __u64 len);
Expand Down
74 changes: 58 additions & 16 deletions fs/ntfs3/record.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,19 @@ int mi_read(struct mft_inode *mi, bool is_mft)
return err;
}

/*
* mi_enum_attr - start/continue attributes enumeration in record.
*
* NOTE: mi->mrec - memory of size sbi->record_size
* here we sure that mi->mrec->total == sbi->record_size (see mi_read)
*/
struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
{
const struct MFT_REC *rec = mi->mrec;
u32 used = le32_to_cpu(rec->used);
u32 t32, off, asize;
u32 t32, off, asize, prev_type;
u16 t16;
u64 data_size, alloc_size, tot_size;

if (!attr) {
u32 total = le32_to_cpu(rec->total);
Expand All @@ -213,6 +220,7 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
if (!is_rec_inuse(rec))
return NULL;

prev_type = 0;
attr = Add2Ptr(rec, off);
} else {
/* Check if input attr inside record. */
Expand All @@ -226,11 +234,11 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
return NULL;
}

if (off + asize < off) {
/* Overflow check. */
/* Overflow check. */
if (off + asize < off)
return NULL;
}

prev_type = le32_to_cpu(attr->type);
attr = Add2Ptr(attr, asize);
off += asize;
}
Expand All @@ -250,7 +258,11 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)

/* 0x100 is last known attribute for now. */
t32 = le32_to_cpu(attr->type);
if ((t32 & 0xf) || (t32 > 0x100))
if (!t32 || (t32 & 0xf) || (t32 > 0x100))
return NULL;

/* attributes in record must be ordered by type */
if (t32 < prev_type)
return NULL;

/* Check overflow and boundary. */
Expand All @@ -259,16 +271,15 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)

/* Check size of attribute. */
if (!attr->non_res) {
/* Check resident fields. */
if (asize < SIZEOF_RESIDENT)
return NULL;

t16 = le16_to_cpu(attr->res.data_off);

if (t16 > asize)
return NULL;

t32 = le32_to_cpu(attr->res.data_size);
if (t16 + t32 > asize)
if (t16 + le32_to_cpu(attr->res.data_size) > asize)
return NULL;

t32 = sizeof(short) * attr->name_len;
Expand All @@ -278,21 +289,52 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
return attr;
}

/* Check some nonresident fields. */
if (attr->name_len &&
le16_to_cpu(attr->name_off) + sizeof(short) * attr->name_len >
le16_to_cpu(attr->nres.run_off)) {
/* Check nonresident fields. */
if (attr->non_res != 1)
return NULL;

t16 = le16_to_cpu(attr->nres.run_off);
if (t16 > asize)
return NULL;

t32 = sizeof(short) * attr->name_len;
if (t32 && le16_to_cpu(attr->name_off) + t32 > t16)
return NULL;

/* Check start/end vcn. */
if (le64_to_cpu(attr->nres.svcn) > le64_to_cpu(attr->nres.evcn) + 1)
return NULL;
}

if (attr->nres.svcn || !is_attr_ext(attr)) {
data_size = le64_to_cpu(attr->nres.data_size);
if (le64_to_cpu(attr->nres.valid_size) > data_size)
return NULL;

alloc_size = le64_to_cpu(attr->nres.alloc_size);
if (data_size > alloc_size)
return NULL;

t32 = mi->sbi->cluster_mask;
if (alloc_size & t32)
return NULL;

if (!attr->nres.svcn && is_attr_ext(attr)) {
/* First segment of sparse/compressed attribute */
if (asize + 8 < SIZEOF_NONRESIDENT_EX)
return NULL;

tot_size = le64_to_cpu(attr->nres.total_size);
if (tot_size & t32)
return NULL;

if (tot_size > alloc_size)
return NULL;
} else {
if (asize + 8 < SIZEOF_NONRESIDENT)
return NULL;

if (attr->nres.c_unit)
return NULL;
} else if (asize + 8 < SIZEOF_NONRESIDENT_EX)
return NULL;
}

return attr;
}
Expand Down
Loading

0 comments on commit f69d00d

Please sign in to comment.