diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-29 13:31:48 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-29 13:31:48 -1000 |
| commit | 0ff8f40110872ecd3a9918e56ddd0ff1b46fb10e (patch) | |
| tree | fe365ddb9b3988fc583f7cb17c969e488a6fff78 | |
| parent | 5fade975d5632280855d03699edd22c0689e3a3f (diff) | |
| parent | ee1cb3443cff5a800948b662756f75b17ca63fe2 (diff) | |
| download | linux-0ff8f40110872ecd3a9918e56ddd0ff1b46fb10e.tar.gz linux-0ff8f40110872ecd3a9918e56ddd0ff1b46fb10e.zip | |
Merge tag 'ntfs3_for_7.4' of https://github.com/Paragon-Software-Group/linux-ntfs3
Pull ntfs3 updates from Konstantin Komarov:
"The bulk of this cycle is hardening against corrupted or maliciously
crafted volumes. Most of these fixes come from fuzzing and KASAN
reports. The main areas are:
- $LogFile replay, where several unchecked on-disk sizes could make
mount write past the MFT record buffer
- MFT record and index parsing
- extended attributes, where a bad $EA entry could leak up to 64K of
kernel heap to userspace through getxattr()
Reading a damaged compressed file could also return uninitialized page
cache contents to userspace; that is fixed too.
A few fixes affect healthy volumes as well: a heap overflow in
fallocate(INSERT_RANGE) on small files, a use-after-free when a
WOF-compressed file is opened for writing during a concurrent read, a
lock-order deadlock in readdir, and stale disk data showing up in
newly allocated clusters of compressed files.
On the feature side there are new tracepoints covering mount, log
replay, lookup, directory index, allocation, iomap and file I/O. There
is also a new 'ugm' mount option (on by default): with ugm=0, newly
created files don't get the WSL uid/gid/mode extended attributes,
which Windows doesn't use.
The rest is smaller correctness fixes and cleanups"
* tag 'ntfs3_for_7.4' of https://github.com/Paragon-Software-Group/linux-ntfs3: (52 commits)
ntfs3: add file I/O tracepoints
ntfs3: add iomap tracepoints
ntfs3: add allocation tracepoints
ntfs3: add directory index tracepoints
ntfs3: add create inode tracepoint
ntfs3: add namei tracepoints
ntfs3: add mount and log replay tracepoints
fs/ntfs3: validate the EA value length in ntfs_read_ea()
fs/ntfs3: handle partial allocation in attr_data_get_block_locked()
ntfs3: fix mapping pairs bounds check for alignment
fs/ntfs3: bound DeleteAttribute asize against rec->used in do_action
ntfs3: release ni_lock before dir_emit in ntfs_readdir to fix deadlocks
fs/ntfs3: bound CreateAttribute asize against record_size in do_action
fs/ntfs3: do not mark frame pages uptodate on read error
fs/ntfs3: prevent positive E_NTFS_NONRESIDENT from reaching writeback error handling
fs/ntfs3: fix ntfs_fnd leak when compare function is missing
fs/ntfs3: fix stale attr_b in attr_allocate_frame()
fs/ntfs3: reject unaligned hole punching on compressed files
fs/ntfs3: take ni_lock() once in ntfs_readdir()
fs/ntfs3: reserve a compression unit in maxbytes_sparse
...
| -rw-r--r-- | Documentation/filesystems/ntfs3.rst | 23 | ||||
| -rw-r--r-- | MAINTAINERS | 1 | ||||
| -rw-r--r-- | fs/ntfs3/attrib.c | 189 | ||||
| -rw-r--r-- | fs/ntfs3/dir.c | 62 | ||||
| -rw-r--r-- | fs/ntfs3/file.c | 8 | ||||
| -rw-r--r-- | fs/ntfs3/frecord.c | 16 | ||||
| -rw-r--r-- | fs/ntfs3/fslog.c | 35 | ||||
| -rw-r--r-- | fs/ntfs3/fsntfs.c | 3 | ||||
| -rw-r--r-- | fs/ntfs3/index.c | 40 | ||||
| -rw-r--r-- | fs/ntfs3/inode.c | 37 | ||||
| -rw-r--r-- | fs/ntfs3/namei.c | 11 | ||||
| -rw-r--r-- | fs/ntfs3/ntfs_fs.h | 5 | ||||
| -rw-r--r-- | fs/ntfs3/record.c | 16 | ||||
| -rw-r--r-- | fs/ntfs3/super.c | 44 | ||||
| -rw-r--r-- | fs/ntfs3/xattr.c | 44 | ||||
| -rw-r--r-- | include/trace/events/ntfs3.h | 389 |
16 files changed, 781 insertions, 142 deletions
diff --git a/Documentation/filesystems/ntfs3.rst b/Documentation/filesystems/ntfs3.rst index 2b86a9b3a6de..e1d86dcc3316 100644 --- a/Documentation/filesystems/ntfs3.rst +++ b/Documentation/filesystems/ntfs3.rst @@ -109,6 +109,29 @@ this table marked with no it means default is without **no**. Kernel. Not to be confused with NTFS ACLs. The option specified as acl enables support for POSIX ACLs. + * - ads + - Enable access to alternate data streams, i.e. named $DATA + attributes. A stream is addressed by appending a colon and the + stream name to the file name. Reading the pseudo-stream + ``query_streams`` returns the names of the streams attached to a + file, one per line:: + + cat file:query_streams # list stream names + cat file:ads1 # read a stream + touch file:ads2 # create a stream on an existing file + rm file:ads1 # remove a stream + + Creating a file together with a stream in a single call, and + renaming a stream, are not supported. Enabled by default. + + * - nocase + - Perform file name lookups case-insensitively. + + * - delalloc + - Delay block allocation for buffered writes until writeback, + rather than allocating as each write is issued. This lets the + driver allocate larger contiguous runs and reduces fragmentation. + Todo list ========= - Full journaling support over JBD. Currently journal replaying is supported diff --git a/MAINTAINERS b/MAINTAINERS index 09713dc4c6a7..7a162e9bcab1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19924,6 +19924,7 @@ W: http://www.paragon-software.com/ T: git https://github.com/Paragon-Software-Group/linux-ntfs3.git F: Documentation/filesystems/ntfs3.rst F: fs/ntfs3/ +F: include/trace/events/ntfs3.h NTSYNC SYNCHRONIZATION PRIMITIVE DRIVER M: Elizabeth Figura <zfigura@codeweavers.com> diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c index b1c315206ffa..805acc3ed869 100644 --- a/fs/ntfs3/attrib.c +++ b/fs/ntfs3/attrib.c @@ -13,6 +13,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include <trace/events/ntfs3.h> /* * You can set external NTFS_MIN_LOG2_OF_CLUMP/NTFS_MAX_LOG2_OF_CLUMP to manage @@ -61,11 +62,16 @@ static int attr_load_runs(struct ATTRIB *attr, struct ntfs_inode *ni, struct runs_tree *run, const CLST *vcn) { int err; - CLST svcn = le64_to_cpu(attr->nres.svcn); - CLST evcn = le64_to_cpu(attr->nres.evcn); + CLST svcn, evcn; u32 asize; u16 run_off; + if (!attr->non_res) + return -EIO; + + svcn = le64_to_cpu(attr->nres.svcn); + evcn = le64_to_cpu(attr->nres.evcn); + if (svcn >= evcn + 1 || run_is_mapped_full(run, svcn, evcn)) return 0; @@ -167,6 +173,8 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run, CLST flen, vcn0 = vcn, pre = pre_alloc ? *pre_alloc : 0; size_t cnt = run->count; + trace_ntfs3_attr_allocate_clusters(sbi->sb, vcn, lcn, len, opt); + for (;;) { err = ntfs_look_for_free_space(sbi, lcn, len + pre, &lcn, &flen, opt); @@ -253,8 +261,8 @@ out: */ int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr, struct ATTR_LIST_ENTRY *le, struct mft_inode *mi, - u64 new_size, struct runs_tree *run, - struct ATTRIB **ins_attr, struct page *page) + struct runs_tree *run, struct ATTRIB **ins_attr, + struct page *page) { struct ntfs_sb_info *sbi; struct ATTRIB *attr_s; @@ -394,13 +402,15 @@ static int attr_set_size_res(struct ntfs_inode *ni, struct ATTRIB *attr, char *next = Add2Ptr(attr, asize); s64 dsize = ALIGN(new_size, 8) - ALIGN(rsize, 8); + if (new_size > sbi->record_size || + (dsize > 0 && used + dsize > sbi->max_bytes_per_attr)) { + return attr_make_nonresident(ni, attr, le, mi, run, ins_attr, + NULL); + } + if (dsize < 0) { memmove(next + dsize, next, tail); } else if (dsize > 0) { - if (used + dsize > sbi->max_bytes_per_attr) - return attr_make_nonresident(ni, attr, le, mi, new_size, - run, ins_attr, NULL); - memmove(next + dsize, next, tail); memset(next, 0, dsize); } @@ -463,6 +473,12 @@ again: goto bad_inode; } + trace_ntfs3_attr_set_size_ex(&ni->vfs_inode, le32_to_cpu(type), + attr_b->non_res ? + le64_to_cpu(attr_b->nres.data_size) : + le32_to_cpu(attr_b->res.data_size), + new_size, keep_prealloc, no_da); + if (!attr_b->non_res) { err = attr_set_size_res(ni, attr_b, le_b, mi_b, new_size, run, &attr_b); @@ -556,6 +572,10 @@ again_1: } next_le_1: + if (!attr->non_res) { + err = -EIO; + goto out; + } svcn = le64_to_cpu(attr->nres.svcn); evcn = le64_to_cpu(attr->nres.evcn); } @@ -960,6 +980,9 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn, if (res) *res = NULL; + trace_ntfs3_attr_data_get_block(&ni->vfs_inode, vcn, clen, new, zero, + no_da); + /* Try to find in cache. */ down_read(&ni->file.run_lock); if (run_lookup_entry_da(&ni->file.run, !no_da ? &ni->file.run_da : NULL, @@ -1008,11 +1031,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen, int step; again: - if (run_lookup_entry_da(run, da ? &ni->file.run_da : NULL, vcn, lcn, - len)) { - } else { + if (!run_lookup_entry_da(run, da ? run_da : NULL, vcn, lcn, len)) *len = 0; - } if (*len) { if (*lcn != SPARSE_LCN || !new) @@ -1034,6 +1054,21 @@ again: if (!attr_b->non_res) { u32 data_size = le32_to_cpu(attr_b->res.data_size); + + /* + * A resident attribute is copied into a single page below + * (alloc_page() + memcpy()) and mapped as a one-page + * IOMAP_INLINE extent by ntfs_iomap_begin(). mi_enum_attr() + * only bounds the resident value length against the MFT record + * size, so a corrupted volume whose records are larger than a + * page can report data_size > PAGE_SIZE; copying that many bytes + * would overflow the single destination page. Reject it. + */ + if (data_size > PAGE_SIZE) { + err = -EINVAL; + goto out; + } + *lcn = RESIDENT_LCN; *len = data_size; if (res) { @@ -1138,7 +1173,7 @@ again: to_alloc = ((vcn0 + clen + clst_per_frame - 1) & cmask) - vcn; if (fr < clst_per_frame) fr = clst_per_frame; - if (vcn != vcn0) + if (vcn != vcn0 || clen < to_alloc) zero = true; /* Check if 'vcn' and 'vcn0' in different attribute segments. */ @@ -1234,6 +1269,10 @@ again: total_size = total_size0 + ((u64)*len << cluster_bits); if (vcn != vcn0) { + if (vcn0 >= end) { + err = -ENOSPC; + goto undo1; + } if (!run_lookup_entry(run, vcn0, lcn, len, NULL)) { err = -EINVAL; goto out; @@ -1456,6 +1495,9 @@ int attr_load_runs_vcn(struct ntfs_inode *ni, enum ATTR_TYPE type, return -ENOENT; } + if (!attr->non_res) + return -EIO; + svcn = le64_to_cpu(attr->nres.svcn); evcn = le64_to_cpu(attr->nres.evcn); @@ -1783,7 +1825,7 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, struct ATTR_LIST_ENTRY *le, *le_b; struct mft_inode *mi, *mi_b; CLST svcn, evcn1, next_svcn, len; - CLST vcn, end, clst_data; + CLST vcn, end, clst_data, alloc, evcn; u64 total_size, valid_size, data_size; le_b = NULL; @@ -1801,6 +1843,7 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, svcn = le64_to_cpu(attr_b->nres.svcn); evcn1 = le64_to_cpu(attr_b->nres.evcn) + 1; data_size = le64_to_cpu(attr_b->nres.data_size); + valid_size = le64_to_cpu(attr_b->nres.valid_size); if (svcn <= vcn && vcn < evcn1) { attr = attr_b; @@ -1920,63 +1963,63 @@ repack: attr = ni_find_attr(ni, attr, &le, ATTR_DATA, ni->file.ads.name, ni->file.ads.len, &svcn, &mi); - if (attr) { - CLST alloc = bytes_to_cluster( - sbi, le64_to_cpu(attr_b->nres.alloc_size)); - CLST evcn = le64_to_cpu(attr->nres.evcn); - - if (end < next_svcn) - end = next_svcn; - while (end > evcn) { - /* Remove segment [svcn : evcn). */ - mi_remove_attr(NULL, mi, attr); - - if (!al_remove_le(ni, le)) { - err = -EINVAL; - goto out; - } + if (!attr) + goto ins_ext; - if (evcn + 1 >= alloc) { - /* Last attribute segment. */ - evcn1 = evcn + 1; - goto ins_ext; - } + alloc = bytes_to_cluster(sbi, le64_to_cpu(attr_b->nres.alloc_size)); + evcn = le64_to_cpu(attr->nres.evcn); - if (ni_load_mi(ni, le, &mi)) { - attr = NULL; - goto out; - } + if (end < next_svcn) + end = next_svcn; + while (end > evcn) { + /* Remove segment [svcn : evcn). */ + mi_remove_attr(NULL, mi, attr); - attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, - ni->file.ads.name, ni->file.ads.len, - &le->id); - if (!attr) { - err = -EINVAL; - goto out; - } - svcn = le64_to_cpu(attr->nres.svcn); - evcn = le64_to_cpu(attr->nres.evcn); + if (!al_remove_le(ni, le)) { + err = -EINVAL; + goto out; } - if (end < svcn) - end = svcn; + if (evcn + 1 >= alloc) { + /* Last attribute segment. */ + evcn1 = evcn + 1; + goto ins_ext; + } - err = attr_load_runs(attr, ni, run, &end); - if (err) + if (ni_load_mi(ni, le, &mi)) { + attr = NULL; goto out; + } - evcn1 = evcn + 1; - attr->nres.svcn = cpu_to_le64(next_svcn); - err = mi_pack_runs(mi, attr, run, evcn1 - next_svcn); - if (err) + attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, &le->id); + if (!attr) { + err = -EINVAL; goto out; + } + svcn = le64_to_cpu(attr->nres.svcn); + evcn = le64_to_cpu(attr->nres.evcn); + } - le->vcn = cpu_to_le64(next_svcn); - ni->attr_list.dirty = true; - mi->dirty = true; + if (end < svcn) + end = svcn; + + err = attr_load_runs(attr, ni, run, &end); + if (err) + goto out; + + evcn1 = evcn + 1; + attr->nres.svcn = cpu_to_le64(next_svcn); + err = mi_pack_runs(mi, attr, run, evcn1 - next_svcn); + if (err) + goto out; + + le->vcn = cpu_to_le64(next_svcn); + ni->attr_list.dirty = true; + mi->dirty = true; + + next_svcn = le64_to_cpu(attr->nres.evcn) + 1; - next_svcn = le64_to_cpu(attr->nres.evcn) + 1; - } ins_ext: if (evcn1 > next_svcn) { err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name, @@ -1985,6 +2028,14 @@ ins_ext: &attr, &mi, NULL); if (err) goto out; + /* Layout of records is changed. */ + attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, NULL, + &mi_b); + if (!attr_b) { + err = -EINVAL; + goto out; + } } ok: run_truncate_around(run, vcn); @@ -1993,9 +2044,8 @@ out: if (new_valid > data_size) new_valid = data_size; - valid_size = le64_to_cpu(attr_b->nres.valid_size); if (new_valid != valid_size) { - attr_b->nres.valid_size = cpu_to_le64(valid_size); + attr_b->nres.valid_size = cpu_to_le64(new_valid); mi_b->dirty = true; } } @@ -2357,6 +2407,12 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size) if ((vbo | bytes) & mask) { /* We have to zero a range(s). */ + if (is_attr_compressed(attr_b)) { + /* Compressed file punch must be frame aligned. */ + return -EINVAL; + } + + /* Sparse files can be punched. */ if (!frame_size) { /* Caller insists range is aligned. */ return -EINVAL; @@ -2610,8 +2666,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) char *data = Add2Ptr(attr_b, le16_to_cpu(attr_b->res.data_off)); - memmove(data + bytes, data, bytes); - memset(data, 0, bytes); + memmove(data + vbo + bytes, data + vbo, data_size - vbo); + memset(data + vbo, 0, bytes); goto done; } @@ -2803,9 +2859,8 @@ int attr_force_nonresident(struct ntfs_inode *ni) } down_write(&ni->file.run_lock); - err = attr_make_nonresident(ni, attr, le, mi, - le32_to_cpu(attr->res.data_size), - &ni->file.run, &attr, NULL); + err = attr_make_nonresident(ni, attr, le, mi, &ni->file.run, &attr, + NULL); up_write(&ni->file.run_lock); return err; diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c index 14ca54e54578..7ee1a5d4189e 100644 --- a/fs/ntfs3/dir.c +++ b/fs/ntfs3/dir.c @@ -14,6 +14,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include <trace/events/ntfs3.h> /* Convert little endian UTF-16 to NLS string. */ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len, @@ -34,6 +35,9 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len, /* UTF-16 -> UTF-8 */ ret = utf16s_to_utf8s((wchar_t *)name, len, UTF16_LITTLE_ENDIAN, buf, buf_len); + if (ret >= buf_len) { + ret = buf_len-1; + } buf[ret] = '\0'; return ret; } @@ -290,6 +294,8 @@ struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni, struct inode *inode = NULL; struct ntfs_fnd *fnd_a = NULL; + trace_ntfs3_dir_search_u(dir, uni ? uni->len : 0); + if (!fnd) { fnd_a = fnd_get(); if (!fnd_a) { @@ -525,45 +531,50 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx) if (!name) return -ENOMEM; + /* + * 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); + if (!ni->mi_loaded && ni->attr_list.size) { /* * Directory inode is locked for read. * Load all subrecords to avoid 'write' access to 'ni' during * directory reading. */ - ni_lock(ni); - if (!ni->mi_loaded && ni->attr_list.size) { - err = ni_load_all_mi(ni); - if (!err) - ni->mi_loaded = true; - } - ni_unlock(ni); + err = ni_load_all_mi(ni); if (err) goto out; + ni->mi_loaded = true; } - /* - * 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_unlock; + goto out; } if (pos >= sbi->record_size) { bit = (pos - sbi->record_size) >> index_bits; } else { /* + * Release ni_lock before dir_emit (which may fault into + * user pages). root->ihdr lives in the main MFT record + * whose lifetime is bound to the inode, and i_rwsem shared + * prevents concurrent directory modifications. + */ + ni_unlock(ni); + + /* * Add each name from root in 'ctx'. */ err = ntfs_read_hdr(sbi, ni, &root->ihdr, 0, pos, name, ctx); if (err) - goto out_unlock; + goto out; + + ni_lock(ni); bit = 0; } @@ -574,7 +585,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_unlock; + goto out; if (bit == MINUS_ONE_T) { /* no more used indexes. end of dir. */ @@ -584,13 +595,19 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx) if (bit >= max_bit) { /* Corrupted directory. */ err = -EINVAL; - goto out_unlock; + goto out; } err = indx_read_ra(&ni->dir, ni, bit << ni->dir.idx2vbn_bits, &node, &file->f_ra); if (err) - goto out_unlock; + goto out; + + /* + * node->index is a kmalloc'd buffer, safe after ni_unlock. + * Release lock before dir_emit to avoid lock-order issues. + */ + ni_unlock(ni); /* * Add each name from index in 'ctx'. @@ -599,13 +616,14 @@ 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_unlock; + goto out; + + ni_lock(ni); } -out_unlock: +out: ni_unlock(ni); -out: kfree(name); put_indx_node(node); diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 52e1cd969e06..1624f9b014e0 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -20,6 +20,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include <trace/events/ntfs3.h> /* * cifx, btrfs, exfat, ext4, f2fs use this constant. @@ -183,6 +184,9 @@ static int ntfs_ioctl_set_volume_label(struct ntfs_sb_info *sbi, u8 __user *buf) if (!capable(CAP_SYS_ADMIN)) return -EPERM; + if (sb_rdonly(sbi->sb)) + return -EROFS; + if (copy_from_user(user, buf, FSLABEL_MAX)) return -EFAULT; @@ -824,6 +828,8 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) unsigned int dio_flags; ssize_t ret; + trace_ntfs3_file_read_iter(iocb, iter); + ret = check_read_restriction(inode); if (ret) return ret; @@ -1244,6 +1250,8 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) loff_t vbo, endbyte; ssize_t ret, err; + trace_ntfs3_file_write_iter(iocb, from); + if (!inode_trylock(inode)) { if (iocb->ki_flags & IOCB_NOWAIT) return -EAGAIN; diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index bead01a953f3..16ac847de48d 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -2190,10 +2190,13 @@ remove_wof: /* Clear cached flag. */ ni->ni_flags &= ~NI_FLAG_COMPRESSED_MASK; + /* offs_folio is accessed under run_lock in attr_wof_frame_info(). */ + down_write(&ni->file.run_lock); if (ni->file.offs_folio) { folio_put(ni->file.offs_folio); ni->file.offs_folio = NULL; } + up_write(&ni->file.run_lock); mapping->a_ops = &ntfs_aops; out: @@ -2287,7 +2290,6 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages, u64 vbo_disk; size_t unc_size = 0; u32 frame_size, i, ondisk_size; - struct page *pg; struct ATTRIB *attr; CLST frame, clst_data; @@ -2519,9 +2521,10 @@ out1: #endif vunmap(frame_mem); out: - for (i = 0; i < pages_per_frame; i++) { - pg = pages[i]; - SetPageUptodate(pg); + if (!err) { + for (i = 0; i < pages_per_frame; i++) { + SetPageUptodate(pages[i]); + } } return err; @@ -2569,9 +2572,8 @@ int ni_write_frame(struct ntfs_inode *ni, struct page **pages, if (!attr->non_res) { down_write(&ni->file.run_lock); - err = attr_make_nonresident(ni, attr, le, mi, - le32_to_cpu(attr->res.data_size), - &ni->file.run, &attr, pages[0]); + err = attr_make_nonresident(ni, attr, le, mi, &ni->file.run, + &attr, pages[0]); up_write(&ni->file.run_lock); if (err) goto out; diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c index ed50c1d0c23e..bb8d40bb5869 100644 --- a/fs/ntfs3/fslog.c +++ b/fs/ntfs3/fslog.c @@ -7,12 +7,14 @@ #include <linux/blkdev.h> #include <linux/fs.h> +#include <linux/overflow.h> #include <linux/random.h> #include <linux/slab.h> #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include <trace/events/ntfs3.h> /* * LOG FILE structs @@ -2457,7 +2459,8 @@ static int find_log_rec(struct ntfs_log *log, u64 lsn, struct lcb *lcb) * Check that the length field isn't greater than the total * available space the log file. */ - rec_len = len + log->record_header_len; + if (check_add_overflow(len, log->record_header_len, &rec_len)) + return -EINVAL; if (rec_len >= log->total_avail) return -EINVAL; @@ -2783,9 +2786,6 @@ static inline bool check_attr(const struct MFT_REC *rec, return false; } - if (run_off > asize) - return false; - if (run_unpack(NULL, sbi, 0, svcn, evcn, svcn, Add2Ptr(attr, run_off), asize - run_off) < 0) { return false; @@ -3318,10 +3318,15 @@ skip_load_parent: asize = le32_to_cpu(attr2->size); used = le32_to_cpu(rec->used); + /* + * attr2->size is taken from the log record and is bounded only + * by the log record length, not by the MFT record. Refuse an + * attribute that does not fit, as mi_insert_attr() does. + */ if (!check_if_attr(rec, lrh) || dlen < SIZEOF_RESIDENT || !IS_ALIGNED(asize, 8) || Add2Ptr(attr2, asize) > Add2Ptr(lrh, rec_len) || - dlen > record_size - used) { + dlen > record_size - used || asize > record_size - used) { goto dirty_vol; } @@ -3344,10 +3349,21 @@ skip_load_parent: break; case DeleteAttribute: - asize = le32_to_cpu(attr->size); used = le32_to_cpu(rec->used); - if (!check_if_attr(rec, lrh)) + /* + * check_if_attr() accepts a record_off that points at the + * ATTR_END marker, which is how CreateAttribute appends. The + * bytes there are not an attribute and check_attr() never + * validated them, so refuse an offset that cannot hold a + * resident header before reading attr->size, and then bound it + * as mi_remove_attr() does. + */ + if (!check_if_attr(rec, lrh) || roff + SIZEOF_RESIDENT > used) + goto dirty_vol; + + asize = le32_to_cpu(attr->size); + if (asize > used - roff) goto dirty_vol; rec->used = cpu_to_le32(used - asize); @@ -3421,7 +3437,8 @@ move_data: if (!check_if_attr(rec, lrh) || !attr->non_res || aoff < le16_to_cpu(attr->nres.run_off) || aoff > asize || - (nsize > asize && nsize - asize > record_size - used)) { + (nsize > asize && + ALIGN(nsize, 8) - asize > record_size - used)) { goto dirty_vol; } @@ -5402,6 +5419,8 @@ out: else if (log->set_dirty) ntfs_set_state(sbi, NTFS_DIRTY_ERROR); + trace_ntfs3_log_replay(&ni->vfs_inode, *initialized, err); + kfree(log); return err; diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c index 97c04ab2763a..b55311ccd229 100644 --- a/fs/ntfs3/fsntfs.c +++ b/fs/ntfs3/fsntfs.c @@ -2153,6 +2153,9 @@ int ntfs_insert_security(struct ntfs_sb_info *sbi, goto out; while (e) { + if (le16_to_cpu(e->de.size) < SIZEOF_SDH_DIRENTRY) + break; + if (le32_to_cpu(e->sec_hdr.size) == new_sec_size) { err = ntfs_read_run_nb(sbi, &ni->file.run, le64_to_cpu(e->sec_hdr.off), diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c index 689712d3463d..c06b36760448 100644 --- a/fs/ntfs3/index.c +++ b/fs/ntfs3/index.c @@ -13,6 +13,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include <trace/events/ntfs3.h> static const struct INDEX_NAMES { const __le16 *name; @@ -594,6 +595,8 @@ static const struct NTFS_DE *hdr_insert_head(struct INDEX_HDR *hdr, if (!e) return NULL; + if (size_add(used, ins_bytes) > le32_to_cpu(hdr->total)) + return NULL; /* Now we just make room for the inserted entries and jam it in. */ to_move = used - le32_to_cpu(hdr->de_off); @@ -1179,6 +1182,8 @@ int indx_find(struct ntfs_index *indx, struct ntfs_inode *ni, if (!root) root = indx_get_root(&ni->dir, ni, NULL, NULL); + trace_ntfs3_indx_find(&ni->vfs_inode, indx->type, key_len); + if (!root) { /* Should not happen. */ return -EINVAL; @@ -1801,7 +1806,10 @@ static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni, } /* Copy root entries into new buffer. */ - hdr_insert_head(hdr, re, to_move); + if (!hdr_insert_head(hdr, re, to_move)) { + err = -EINVAL; + goto out_put_n; + } /* Update bitmap attribute. */ indx_mark_used(indx, ni, new_vbn >> indx->idx2vbn_bits); @@ -1955,7 +1963,11 @@ static int indx_insert_into_buffer(struct ntfs_index *indx, /* Copy all the entries <= sp into the new buffer. */ de_t = hdr_first_de(hdr1); to_copy = PtrOffset(de_t, sp); - hdr_insert_head(hdr2, de_t, to_copy); + if (!hdr_insert_head(hdr2, de_t, to_copy)) { + err = -EINVAL; + put_indx_node(n2); + goto out; + } /* Remove all entries (sp including) from hdr1. */ used = used1 - to_copy - sp_size; @@ -2049,9 +2061,13 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni, cmp = get_cmp_func(root); if (unlikely(!cmp)) { WARN_ON_ONCE(1); - return -EINVAL; + err = -EINVAL; + goto out; } + trace_ntfs3_indx_insert_entry(&ni->vfs_inode, indx->type, + le16_to_cpu(new_de->key_size), undo); + if (fnd_is_empty(fnd)) { /* * Find the spot the tree where we want to @@ -2406,9 +2422,12 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni, cmp = get_cmp_func(root); if (unlikely(!cmp)) { WARN_ON_ONCE(1); - return -EINVAL; + err = -EINVAL; + goto out; } + trace_ntfs3_indx_delete_entry(&ni->vfs_inode, indx->type, key_len); + /* Locate the entry to remove. */ err = indx_find(indx, ni, root, key, key_len, ctx, &diff, &e, fnd); if (err) @@ -2702,18 +2721,28 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni, err = attr_set_size(ni, ATTR_ALLOC, in->name, in->name_len, &indx->alloc_run, 0, NULL, false); + if (err) + goto out; + if (in->name == I30_NAME) i_size_write(&ni->vfs_inode, 0); err = ni_remove_attr(ni, ATTR_ALLOC, in->name, in->name_len, false, NULL); run_close(&indx->alloc_run); + if (err) + goto out; err = attr_set_size(ni, ATTR_BITMAP, in->name, in->name_len, &indx->bitmap_run, 0, NULL, false); + if (err) + goto out; + err = ni_remove_attr(ni, ATTR_BITMAP, in->name, in->name_len, false, NULL); run_close(&indx->bitmap_run); + if (err) + goto out; root = indx_get_root(indx, ni, &attr, &mi); if (!root) { @@ -2787,7 +2816,8 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi, cmp = get_cmp_func(root); if (unlikely(!cmp)) { WARN_ON_ONCE(1); - return -EINVAL; + err = -EINVAL; + goto out; } /* Find entry in directory. */ diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 5a8d362155ac..43d5e83403aa 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -17,6 +17,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include <trace/events/ntfs3.h> struct IGET5_PARAM { const struct MFT_REF *ref; @@ -213,6 +214,11 @@ next_attr: names += 1; fname = Add2Ptr(attr, roff); + + /* Make sure the full name fits in the resident data. */ + if (rsize < fname_full_size(fname)) + goto out; + if (fname->type == FILE_NAME_DOS) goto next_attr; @@ -280,7 +286,8 @@ next_attr: break; case ATTR_ROOT: - if (attr->non_res) + if (attr->non_res || asize < sizeof(struct INDEX_ROOT) + roff || + rsize < sizeof(struct INDEX_ROOT)) goto out; root = Add2Ptr(attr, roff); @@ -947,6 +954,8 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, bool new_clst = false; bool no_da; bool zero = false; + + trace_ntfs3_iomap_begin(inode, offset, length, flags); if (unlikely(ntfs3_forced_shutdown(sbi->sb))) return -EIO; @@ -962,9 +971,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, } } - if (IOMAP_ZERO == flags && (endbyte & sbi->cluster_mask)) { - rw = true; - } else if (rw) { + if (rw) { clen_max = bytes_to_cluster(sbi, endbyte) - vcn; } @@ -972,7 +979,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, * 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. - */ + */ no_da = flags == (IOMAP_DIRECT | IOMAP_WRITE) || srcmap == WB_NO_DA; err = attr_data_get_block(ni, vcn, clen_max, &lcn, &clen, @@ -1017,7 +1024,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, } if (!clen) { - /* broken file? */ + /* Looks like file is broken.*/ return -EINVAL; } @@ -1086,6 +1093,8 @@ static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length, struct ntfs_inode *ni = ntfs_i(inode); loff_t endbyte = pos + written; + trace_ntfs3_iomap_end(inode, pos, length, written, flags); + if ((flags & IOMAP_WRITE) || (flags & IOMAP_ZERO)) { if (iomap->type == IOMAP_INLINE) { u32 data_size; @@ -1228,6 +1237,15 @@ static int ntfs_writepages(struct address_space *mapping, folio_unlock(folio); if (err2) { + /* + * attr_data_write_resident() can return the + * positive internal code E_NTFS_NONRESIDENT; + * both mapping_set_error() and writeback_iter() + * need a negative errno and warn on a positive + * value. + */ + if (err2 > 0) + err2 = -EIO; mapping_set_error(mapping, err2); if (!err) err = err2; @@ -1412,6 +1430,8 @@ int ntfs_create_inode(const struct mnt_idmap *idmap, struct inode *dir, /* New file will be resident or non resident. */ const bool new_file_resident = 1; + trace_ntfs3_create_inode(dir, dentry); + if (!fnd) ni_lock_dir(dir_ni); @@ -1831,7 +1851,7 @@ int ntfs_create_inode(const struct mnt_idmap *idmap, struct inode *dir, inode->i_flags |= S_NOSEC; } - if (!S_ISLNK(mode)) { + if (!S_ISLNK(mode) && sbi->options->ugm) { /* * ntfs_init_acl and ntfs_save_wsl_perm update extended attribute. * The packed size of extended attribute is stored in direntry too. @@ -2244,6 +2264,9 @@ static noinline int ntfs_readlink_hlp(const struct dentry *link_de, if (err < 0) goto out; + if (err >= buflen) + err = buflen - 1; + /* Translate Windows '\' into Linux '/'. */ for (i = 0; i < err; i++) { if (buffer[i] == '\\') diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index ae88cb66fb7e..f4286c642752 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -13,6 +13,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include <trace/events/ntfs3.h> /* * fill_name_de - Format NTFS_DE in @buf. @@ -56,7 +57,7 @@ int fill_name_de(struct ntfs_sb_info *sbi, void *buf, const struct qstr *name, real_size = data_size + sizeof(struct NTFS_DE); aligned_size = ALIGN(data_size, 8) + sizeof(struct NTFS_DE); if (aligned_size > real_size) - memset((char *)buf + real_size, 0, aligned_size - real_size); + memset(Add2Ptr(buf, real_size), 0, aligned_size - real_size); e->size = cpu_to_le16(aligned_size); e->key_size = cpu_to_le16(data_size); @@ -77,6 +78,8 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, struct inode *inode; int err; + trace_ntfs3_lookup(dir, dentry); + if (!uni) return ERR_PTR(-ENOMEM); @@ -231,8 +234,8 @@ static int ntfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, - mode, 0, NULL, 0, NULL)); + return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, mode, 0, + NULL, 0, NULL)); } /* @@ -299,6 +302,8 @@ static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *dir, if (unlikely(ntfs3_forced_shutdown(sb))) return -EIO; + trace_ntfs3_rename(dir, dentry, new_dir, new_dentry); + if (flags & ~RENAME_NOREPLACE) return -EINVAL; diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 689151fcf993..f400cf94d05b 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -113,6 +113,7 @@ struct ntfs_mount_options { unsigned nocase : 1; /* case insensitive. */ unsigned delalloc : 1; /* delay allocation. */ unsigned ads : 1; /* ads support. */ + unsigned ugm : 1; /* store user/group/mode in xattr. */ }; /* Special value to unpack and deallocate. */ @@ -467,8 +468,8 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run, CLST *new_len); int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr, struct ATTR_LIST_ENTRY *le, struct mft_inode *mi, - u64 new_size, struct runs_tree *run, - struct ATTRIB **ins_attr, struct page *page); + struct runs_tree *run, struct ATTRIB **ins_attr, + struct page *page); int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name, u8 name_len, struct runs_tree *run, u64 new_size, const u64 *new_valid, bool keep_prealloc, diff --git a/fs/ntfs3/record.c b/fs/ntfs3/record.c index 4f12ce15b03b..d7785f9e8e09 100644 --- a/fs/ntfs3/record.c +++ b/fs/ntfs3/record.c @@ -665,18 +665,32 @@ int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr, u32 run_size = asize - run_off; u32 tail = used - aoff - asize; u32 dsize = sbi->record_size - used; + u32 avail; /* Make a maximum gap in current record. */ memmove(next + dsize, next, tail); + /* Leave room for the 8-byte ALIGN() to avoid OOB write */ + avail = (run_size + dsize) & ~7u; + + if (!avail) { + memmove(next, next + dsize, tail); + return -ENOSPC; + } + /* Pack as much as possible. */ - err = run_pack(run, svcn, len, Add2Ptr(attr, run_off), run_size + dsize, + err = run_pack(run, svcn, len, Add2Ptr(attr, run_off), avail, &plen); if (err < 0) { memmove(next, next + dsize, tail); return err; } + if (!plen) { + memmove(next, next + dsize, tail); + return -ENOSPC; + } + new_run_size = ALIGN(err, 8); memmove(next + new_run_size - run_size, next + dsize, tail); diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index 58ef6be3286b..29aabdc8a801 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -78,6 +78,9 @@ #include "lib/lib.h" #endif +#define CREATE_TRACE_POINTS +#include <trace/events/ntfs3.h> + #ifdef CONFIG_PRINTK /* * ntfs_printk - Trace warnings/notices/errors. @@ -280,6 +283,8 @@ enum Opt { Opt_delalloc_bool, Opt_ads, Opt_ads_bool, + Opt_ugm, + Opt_ugm_bool, Opt_err, }; @@ -308,6 +313,8 @@ static const struct fs_parameter_spec ntfs_fs_parameters[] = { fsparam_bool("delalloc", Opt_delalloc_bool), fsparam_flag("ads", Opt_ads), fsparam_bool("ads", Opt_ads_bool), + fsparam_flag("ugm", Opt_ugm), + fsparam_bool("ugm", Opt_ugm_bool), {} }; // clang-format on @@ -344,6 +351,17 @@ static int ntfs_fs_parse_param(struct fs_context *fc, int opt; opt = fs_parse(fc, ntfs_fs_parameters, param, &result); + + if (opt == -ENOPARAM && strcmp(param->key, "source")) { + /* Show mini help. */ + pr_err("ntfs3: unknown option \"%s\". Use uid,gid,umask," + "dmask,fmask,sys_immutable,discard,force,sparse," + "nohidden,hide_dot_files,windows_names,showmeta,acl," + "iocharset,prealloc,nocase,delalloc,ads,ugm", + param->key); + return -ENOPARAM; + } + if (opt < 0) return opt; @@ -437,6 +455,12 @@ static int ntfs_fs_parse_param(struct fs_context *fc, case Opt_ads_bool: opts->ads = result.boolean; break; + case Opt_ugm: + opts->ugm = 1; + break; + case Opt_ugm_bool: + opts->ugm = result.boolean; + break; default: /* Should not be here unless we forget add case. */ return -EINVAL; @@ -991,7 +1015,7 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, { struct ntfs_sb_info *sbi = sb->s_fs_info; int err; - u32 mb, gb, boot_sector_size, sct_per_clst, record_size; + u32 mb, gb, boot_sector_size = 0, sct_per_clst, record_size; u64 sectors, clusters, mlcn, mlcn2, mft_pos, mft2_pos, dev_size0; struct NTFS_BOOT *boot; struct buffer_head *bh; @@ -1231,12 +1255,17 @@ read_boot: #ifdef CONFIG_NTFS3_64BIT_CLUSTER if (clusters >= (1ull << (64 - cluster_bits))) - sbi->maxbytes = -1; + sbi->maxbytes = MAX_LFS_FILESIZE; sbi->maxbytes_sparse = MAX_LFS_FILESIZE; sb->s_maxbytes = MAX_LFS_FILESIZE; #else - /* Maximum size for sparse file. */ - sbi->maxbytes_sparse = (1ull << (cluster_bits + 32)) - 1; + /* + * Maximum size for sparse file: + * 16 aligned up clusters should fit in 32 bits. + * xfstest 308 on sparse file. + */ + sbi->maxbytes_sparse = (1ull << (cluster_bits + 32)) - 1 - + (sbi->cluster_size << NTFS_LZNT_CUNIT); sb->s_maxbytes = 0xFFFFFFFFull << cluster_bits; #endif @@ -1259,6 +1288,8 @@ read_boot: } out: + trace_ntfs3_init_from_boot(sb, sector_size, boot_sector_size, + !!boot_block, err); brelse(bh); if (err == -EINVAL && !boot_block && dev_size0 > PAGE_SHIFT) { @@ -1777,12 +1808,16 @@ load_root: } ntfs_create_procdir(sb); + trace_ntfs3_fill_super(sb, ro, sbi->cluster_size, sbi->record_size, + sbi->index_size, 0); return 0; put_inode_out: iput(inode); out: + trace_ntfs3_fill_super(sb, ro, sbi->cluster_size, sbi->record_size, + sbi->index_size, err); /* sbi->options == options */ if (options) { put_mount_options(sbi->options); @@ -1915,6 +1950,7 @@ static int ntfs_init_fs_context(struct fs_context *fc) opts->fs_fmask_inv = opts->fs_dmask_inv = ~current_umask(); opts->prealloc = 1; opts->ads = 1; + opts->ugm = 1; #ifdef CONFIG_NTFS3_FS_POSIX_ACL /* Set the default value 'acl' */ diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index 941d516ab8dd..7a138e7d0be7 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -146,29 +146,32 @@ static int ntfs_read_ea(struct ntfs_inode *ni, struct EA_FULL **ea, for (off = 0; off < size; off += ea_size) { const struct EA_FULL *ef = Add2Ptr(ea_p, off); u32 bytes = size - off; - size_t need; - - /* Check if we can use field ea->size. */ - if (bytes < sizeof(ef->size)) - goto out1; + u32 packed_size; /* Check if we can use fields ef->name_len and ef->elength. */ if (bytes < offsetof(struct EA_FULL, name)) goto out1; - /* Size needed to hold this record's name and value. */ - need = struct_size(ef, name, - 1 + ef->name_len + le16_to_cpu(ef->elength)); + /* + * The name and the value have to fit in the entry whether or + * not ef->size is set, because ntfs_get_ea() copies elength + * bytes out of this buffer and checks them only against the + * caller supplied buffer size. + */ + packed_size = + ALIGN(struct_size(ef, name, + 1 + ef->name_len + + le16_to_cpu(ef->elength)), + 4); if (ef->size) { ea_size = le32_to_cpu(ef->size); - /* ef->size must fit the list and cover the record. */ - if (ea_size > bytes || ea_size < need) + if (ea_size > bytes || packed_size > ea_size) goto out1; continue; } - ea_size = ALIGN(need, 4); + ea_size = packed_size; if (ea_size > bytes) goto out1; } @@ -783,7 +786,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de, if (!buffer) { err = sizeof(u8); } else if (size < sizeof(u8)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u8); *(u8 *)buffer = le32_to_cpu(ni->std_fa); @@ -797,7 +800,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de, if (!buffer) { err = sizeof(u32); } else if (size < sizeof(u32)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u32); *(u32 *)buffer = le32_to_cpu(ni->std_fa); @@ -837,7 +840,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de, if (!buffer) { err = sd_size; } else if (size < sd_size) { - err = -ENODATA; + err = -ERANGE; } else { err = sd_size; memcpy(buffer, sd, sd_size); @@ -976,8 +979,10 @@ set_new_fa: NULL); out: - inode_set_ctime_current(inode); - mark_inode_dirty(inode); + if (!err) { + inode_set_ctime_current(inode); + mark_inode_dirty(inode); + } return err; } @@ -993,6 +998,13 @@ int ntfs_save_wsl_perm(struct inode *inode, __le32 *ea_size) __le32 value; struct ntfs_inode *ni = ntfs_i(inode); + if (!ni->mi.sbi->options->ugm) { + /* Do not save uid/gid/mode/dev in xattr. */ + if (ea_size) + *ea_size = 0; + return 0; + } + ni_lock(ni); value = cpu_to_le32(i_uid_read(inode)); err = ntfs_set_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &value, diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h new file mode 100644 index 000000000000..750300068984 --- /dev/null +++ b/include/trace/events/ntfs3.h @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ntfs3 + +#if !defined(_TRACE_NTFS3_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_NTFS3_H + +#include <linux/fs.h> +#include <linux/tracepoint.h> + +TRACE_EVENT(ntfs3_fill_super, + TP_PROTO(struct super_block *sb, bool ro, u32 cluster_size, + u32 record_size, u32 index_size, int err), + TP_ARGS(sb, ro, cluster_size, record_size, index_size, err), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u32, cluster_size) + __field(u32, record_size) + __field(u32, index_size) + __field(int, err) + __field(bool, ro) + ), + TP_fast_assign( + __entry->dev = sb->s_bdev->bd_dev; + __entry->cluster_size = cluster_size; + __entry->record_size = record_size; + __entry->index_size = index_size; + __entry->err = err; + __entry->ro = ro; + ), + TP_printk("dev=(%d,%d) ro=%d cluster=%u record=%u index=%u err=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ro, + __entry->cluster_size, __entry->record_size, + __entry->index_size, __entry->err) +); + +TRACE_EVENT(ntfs3_init_from_boot, + TP_PROTO(struct super_block *sb, u32 media_sector_size, + u32 boot_sector_size, bool used_alt_boot, int err), + TP_ARGS(sb, media_sector_size, boot_sector_size, used_alt_boot, err), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u32, media_sector_size) + __field(u32, boot_sector_size) + __field(int, err) + __field(bool, used_alt_boot) + ), + TP_fast_assign( + __entry->dev = sb->s_bdev->bd_dev; + __entry->media_sector_size = media_sector_size; + __entry->boot_sector_size = boot_sector_size; + __entry->err = err; + __entry->used_alt_boot = used_alt_boot; + ), + TP_printk("dev=(%d,%d) media_sector=%u boot_sector=%u alt_boot=%d err=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->media_sector_size, __entry->boot_sector_size, + __entry->used_alt_boot, __entry->err) +); + +TRACE_EVENT(ntfs3_log_replay, + TP_PROTO(struct inode *inode, bool initialized, int err), + TP_ARGS(inode, initialized, err), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(loff_t, size) + __field(dev_t, dev) + __field(int, err) + __field(bool, initialized) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->size = i_size_read(inode); + __entry->dev = inode->i_sb->s_dev; + __entry->err = err; + __entry->initialized = initialized; + ), + TP_printk("dev=(%d,%d) ino=%lu size=%lld initialized=%d err=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, + __entry->size, __entry->initialized, __entry->err) +); + +TRACE_EVENT(ntfs3_lookup, + TP_PROTO(struct inode *dir, struct dentry *dentry), + TP_ARGS(dir, dentry), + TP_STRUCT__entry( + __field(unsigned long, parent_ino) + __field(dev_t, dev) + __string(name, dentry->d_name.name) + ), + TP_fast_assign( + __entry->parent_ino = dir->i_ino; + __entry->dev = dir->i_sb->s_dev; + __assign_str(name); + ), + TP_printk("dev=(%d,%d) parent=%lu name=%s", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->parent_ino, __get_str(name)) +); + +TRACE_EVENT(ntfs3_rename, + TP_PROTO(struct inode *dir, struct dentry *dentry, + struct inode *new_dir, struct dentry *new_dentry), + TP_ARGS(dir, dentry, new_dir, new_dentry), + TP_STRUCT__entry( + __field(unsigned long, dir_ino) + __field(unsigned long, new_dir_ino) + __field(unsigned long, ino) + __field(dev_t, dev) + __string(old_name, dentry->d_name.name) + __string(new_name, new_dentry->d_name.name) + ), + TP_fast_assign( + __entry->dir_ino = dir->i_ino; + __entry->new_dir_ino = new_dir->i_ino; + __entry->ino = d_inode(dentry)->i_ino; + __entry->dev = dir->i_sb->s_dev; + __assign_str(old_name); + __assign_str(new_name); + ), + TP_printk("dev=(%d,%d) dir=%lu new_dir=%lu ino=%lu old=%s new=%s", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->dir_ino, __entry->new_dir_ino, __entry->ino, + __get_str(old_name), __get_str(new_name)) +); + +TRACE_EVENT(ntfs3_create_inode, + TP_PROTO(struct inode *dir, struct dentry *dentry), + TP_ARGS(dir, dentry), + TP_STRUCT__entry( + __field(unsigned long, parent_ino) + __field(dev_t, dev) + __string(name, dentry->d_name.name) + ), + TP_fast_assign( + __entry->parent_ino = dir->i_ino; + __entry->dev = dir->i_sb->s_dev; + __assign_str(name); + ), + TP_printk("dev=(%d,%d) parent=%lu name=%s", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->parent_ino, __get_str(name)) +); + +TRACE_EVENT(ntfs3_dir_search_u, + TP_PROTO(struct inode *dir, unsigned int name_len), + TP_ARGS(dir, name_len), + TP_STRUCT__entry( + __field(unsigned long, dir_ino) + __field(dev_t, dev) + __field(unsigned int, name_len) + ), + TP_fast_assign( + __entry->dir_ino = dir->i_ino; + __entry->dev = dir->i_sb->s_dev; + __entry->name_len = name_len; + ), + TP_printk("dev=(%d,%d) dir=%lu name_len=%u", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->dir_ino, __entry->name_len) +); + +TRACE_EVENT(ntfs3_indx_find, + TP_PROTO(struct inode *inode, u8 type, size_t key_len), + TP_ARGS(inode, type, key_len), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(size_t, key_len) + __field(dev_t, dev) + __field(u8, type) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->key_len = key_len; + __entry->dev = inode->i_sb->s_dev; + __entry->type = type; + ), + TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%zu", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->type, __entry->key_len) +); + +TRACE_EVENT(ntfs3_indx_insert_entry, + TP_PROTO(struct inode *inode, u8 type, u16 key_len, bool undo), + TP_ARGS(inode, type, key_len, undo), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(dev_t, dev) + __field(u16, key_len) + __field(u8, type) + __field(bool, undo) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->dev = inode->i_sb->s_dev; + __entry->key_len = key_len; + __entry->type = type; + __entry->undo = undo; + ), + TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%u undo=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->type, __entry->key_len, + __entry->undo) +); + +TRACE_EVENT(ntfs3_indx_delete_entry, + TP_PROTO(struct inode *inode, u8 type, u32 key_len), + TP_ARGS(inode, type, key_len), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(u32, key_len) + __field(dev_t, dev) + __field(u8, type) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->key_len = key_len; + __entry->dev = inode->i_sb->s_dev; + __entry->type = type; + ), + TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%u", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->type, __entry->key_len) +); + +TRACE_EVENT(ntfs3_attr_allocate_clusters, + TP_PROTO(struct super_block *sb, u64 vcn, u64 lcn, u64 len, u32 opt), + TP_ARGS(sb, vcn, lcn, len, opt), + TP_STRUCT__entry( + __field(u64, vcn) + __field(u64, lcn) + __field(u64, len) + __field(dev_t, dev) + __field(u32, opt) + ), + TP_fast_assign( + __entry->vcn = vcn; + __entry->lcn = lcn; + __entry->len = len; + __entry->dev = sb->s_dev; + __entry->opt = opt; + ), + TP_printk("dev=(%d,%d) vcn=%llu lcn=%llu len=%llu opt=0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->vcn, __entry->lcn, __entry->len, __entry->opt) +); + +TRACE_EVENT(ntfs3_attr_set_size_ex, + TP_PROTO(struct inode *inode, u32 type, u64 old_size, u64 new_size, + bool keep_prealloc, bool no_da), + TP_ARGS(inode, type, old_size, new_size, keep_prealloc, no_da), + TP_STRUCT__entry( + __field(u64, old_size) + __field(u64, new_size) + __field(unsigned long, ino) + __field(dev_t, dev) + __field(u32, type) + __field(bool, keep_prealloc) + __field(bool, no_da) + ), + TP_fast_assign( + __entry->old_size = old_size; + __entry->new_size = new_size; + __entry->ino = inode->i_ino; + __entry->dev = inode->i_sb->s_dev; + __entry->type = type; + __entry->keep_prealloc = keep_prealloc; + __entry->no_da = no_da; + ), + TP_printk("dev=(%d,%d) ino=%lu type=0x%x old_size=%llu new_size=%llu keep_prealloc=%d no_da=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, + __entry->type, __entry->old_size, __entry->new_size, + __entry->keep_prealloc, __entry->no_da) +); + +TRACE_EVENT(ntfs3_attr_data_get_block, + TP_PROTO(struct inode *inode, u64 vcn, u64 clen, bool *new, + bool zero, bool no_da), + TP_ARGS(inode, vcn, clen, new, zero, no_da), + TP_STRUCT__entry( + __field(u64, vcn) + __field(u64, clen) + __field(unsigned long, ino) + __field(dev_t, dev) + __field(bool, create) + __field(bool, zero) + __field(bool, no_da) + ), + TP_fast_assign( + __entry->vcn = vcn; + __entry->clen = clen; + __entry->ino = inode->i_ino; + __entry->dev = inode->i_sb->s_dev; + __entry->create = !!new; + __entry->zero = zero; + __entry->no_da = no_da; + ), + TP_printk("dev=(%d,%d) ino=%lu vcn=%llu clen=%llu create=%d zero=%d no_da=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, + __entry->vcn, __entry->clen, __entry->create, + __entry->zero, __entry->no_da) +); + +TRACE_EVENT(ntfs3_iomap_begin, + TP_PROTO(struct inode *inode, loff_t offset, loff_t length, + unsigned int flags), + TP_ARGS(inode, offset, length, flags), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(loff_t, offset) + __field(loff_t, length) + __field(dev_t, dev) + __field(unsigned int, flags) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->offset = offset; + __entry->length = length; + __entry->dev = inode->i_sb->s_dev; + __entry->flags = flags; + ), + TP_printk("dev=(%d,%d) ino=%lu offset=%lld length=%lld flags=0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->offset, __entry->length, + __entry->flags) +); + +TRACE_EVENT(ntfs3_iomap_end, + TP_PROTO(struct inode *inode, loff_t pos, loff_t length, + ssize_t written, unsigned int flags), + TP_ARGS(inode, pos, length, written, flags), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(loff_t, pos) + __field(loff_t, length) + __field(ssize_t, written) + __field(dev_t, dev) + __field(unsigned int, flags) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->pos = pos; + __entry->length = length; + __entry->written = written; + __entry->dev = inode->i_sb->s_dev; + __entry->flags = flags; + ), + TP_printk("dev=(%d,%d) ino=%lu pos=%lld length=%lld written=%zd flags=0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->pos, __entry->length, + __entry->written, __entry->flags) +); + +DECLARE_EVENT_CLASS(ntfs3_file_class, + TP_PROTO(struct kiocb *iocb, struct iov_iter *iter), + TP_ARGS(iocb, iter), + TP_STRUCT__entry( + __field(loff_t, size) + __field(loff_t, offset) + __field(size_t, count) + __field(unsigned long, ino) + __field(dev_t, dev) + ), + TP_fast_assign( + __entry->size = i_size_read(file_inode(iocb->ki_filp)); + __entry->offset = iocb->ki_pos; + __entry->count = iov_iter_count(iter); + __entry->ino = file_inode(iocb->ki_filp)->i_ino; + __entry->dev = file_inode(iocb->ki_filp)->i_sb->s_dev; + ), + TP_printk("dev=(%d,%d) ino=%lu size=%lld pos=%lld bytecount=%zu", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->size, __entry->offset, + __entry->count) +); + +DEFINE_EVENT(ntfs3_file_class, ntfs3_file_read_iter, + TP_PROTO(struct kiocb *iocb, struct iov_iter *iter), + TP_ARGS(iocb, iter) +); + +DEFINE_EVENT(ntfs3_file_class, ntfs3_file_write_iter, + TP_PROTO(struct kiocb *iocb, struct iov_iter *iter), + TP_ARGS(iocb, iter) +); + +#endif /* _TRACE_NTFS3_H */ + +#include <trace/define_trace.h> |
