diff options
Diffstat (limited to 'fs/ntfs3')
| -rw-r--r-- | fs/ntfs3/Kconfig | 10 | ||||
| -rw-r--r-- | fs/ntfs3/Makefile | 20 | ||||
| -rw-r--r-- | fs/ntfs3/attrib.c | 197 | ||||
| -rw-r--r-- | fs/ntfs3/attrlist.c | 15 | ||||
| -rw-r--r-- | fs/ntfs3/dir.c | 162 | ||||
| -rw-r--r-- | fs/ntfs3/file.c | 388 | ||||
| -rw-r--r-- | fs/ntfs3/frecord.c | 274 | ||||
| -rw-r--r-- | fs/ntfs3/fslog.c | 217 | ||||
| -rw-r--r-- | fs/ntfs3/fsntfs.c | 39 | ||||
| -rw-r--r-- | fs/ntfs3/index.c | 190 | ||||
| -rw-r--r-- | fs/ntfs3/inode.c | 455 | ||||
| -rw-r--r-- | fs/ntfs3/lznt.c | 4 | ||||
| -rw-r--r-- | fs/ntfs3/namei.c | 74 | ||||
| -rw-r--r-- | fs/ntfs3/ntfs.h | 3 | ||||
| -rw-r--r-- | fs/ntfs3/ntfs_fs.h | 73 | ||||
| -rw-r--r-- | fs/ntfs3/record.c | 34 | ||||
| -rw-r--r-- | fs/ntfs3/run.c | 94 | ||||
| -rw-r--r-- | fs/ntfs3/super.c | 153 | ||||
| -rw-r--r-- | fs/ntfs3/xattr.c | 40 |
19 files changed, 1688 insertions, 754 deletions
diff --git a/fs/ntfs3/Kconfig b/fs/ntfs3/Kconfig index 7bc31d69f680..876dbc613ae6 100644 --- a/fs/ntfs3/Kconfig +++ b/fs/ntfs3/Kconfig @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config NTFS3_FS tristate "NTFS Read-Write file system support" + depends on !NTFS_FS || m select BUFFER_HEAD select NLS select LEGACY_DIRECT_IO @@ -46,12 +47,3 @@ config NTFS3_FS_POSIX_ACL NOTE: this is linux only feature. Windows will ignore these ACLs. If you don't know what Access Control Lists are, say N. - -config NTFS_FS - tristate "NTFS file system support" - select NTFS3_FS - select BUFFER_HEAD - select NLS - help - This config option is here only for backward compatibility. NTFS - filesystem is now handled by the NTFS3 driver. diff --git a/fs/ntfs3/Makefile b/fs/ntfs3/Makefile index 279701b62bbe..53bf2c17ac28 100644 --- a/fs/ntfs3/Makefile +++ b/fs/ntfs3/Makefile @@ -3,6 +3,26 @@ # Makefile for the ntfs3 filesystem support. # +# Subset of W=1 warnings +subdir-ccflags-y += -Wextra -Wunused -Wno-unused-parameter +subdir-ccflags-y += -Wmissing-declarations +subdir-ccflags-y += -Wmissing-format-attribute +subdir-ccflags-y += -Wmissing-prototypes +subdir-ccflags-y += -Wold-style-definition +subdir-ccflags-y += -Wmissing-include-dirs +condflags := \ + $(call cc-option, -Wunused-but-set-variable) \ + $(call cc-option, -Wunused-const-variable) \ + $(call cc-option, -Wpacked-not-aligned) \ + $(call cc-option, -Wstringop-truncation) \ + $(call cc-option, -Wmaybe-uninitialized) +subdir-ccflags-y += $(condflags) +# The following turn off the warnings enabled by -Wextra +subdir-ccflags-y += -Wno-missing-field-initializers +subdir-ccflags-y += -Wno-sign-compare +subdir-ccflags-y += -Wno-type-limits +subdir-ccflags-y += -Wno-shift-negative-value + # to check robot warnings ccflags-y += -Wint-to-pointer-cast \ $(call cc-option,-Wunused-but-set-variable,-Wunused-const-variable) \ diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c index 6cb9bc5d605c..b1c315206ffa 100644 --- a/fs/ntfs3/attrib.c +++ b/fs/ntfs3/attrib.c @@ -173,7 +173,7 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run, if (err == -ENOSPC && pre) { pre = 0; - if (*pre_alloc) + if (pre_alloc) *pre_alloc = 0; continue; } @@ -278,7 +278,7 @@ int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr, next = Add2Ptr(attr, asize); aoff = PtrOffset(rec, attr); rsize = le32_to_cpu(attr->res.data_size); - is_data = attr->type == ATTR_DATA && !attr->name_len; + is_data = attr->type == ATTR_DATA; /* len - how many clusters required to store 'rsize' bytes */ if (is_attr_compressed(attr)) { @@ -433,6 +433,7 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type, struct ATTRIB **ret, bool no_da) { int err = 0; + struct ntfs_inode *nb = ni->base; struct ntfs_sb_info *sbi = ni->mi.sbi; u8 cluster_bits = sbi->cluster_bits; bool is_mft = ni->mi.rno == MFT_REC_MFT && type == ATTR_DATA && @@ -703,8 +704,8 @@ pack_runs: goto again; } - if (!ni->attr_list.size) { - err = ni_create_attr_list(ni); + if (!nb->attr_list.size) { + err = ni_create_attr_list(nb); /* In case of error layout of records is not changed. */ if (err) goto undo_2; @@ -877,8 +878,7 @@ ok1: if (ret) *ret = attr_b; - if (((type == ATTR_DATA && !name_len) || - (type == ATTR_ALLOC && name == I30_NAME))) { + if ((type == ATTR_DATA || (type == ATTR_ALLOC && name == I30_NAME))) { /* Update inode_set_bytes. */ if (attr_b->non_res && inode_get_bytes(&ni->vfs_inode) != new_alloc) { @@ -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; } @@ -1011,11 +1008,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; } @@ -1031,7 +1025,8 @@ again: step = 0; le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b); + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, NULL, &mi_b); if (!attr_b) { err = -ENOENT; goto out; @@ -1041,11 +1036,18 @@ again: u32 data_size = le32_to_cpu(attr_b->res.data_size); *lcn = RESIDENT_LCN; *len = data_size; - if (res && data_size) { - *res = kmemdup(resident_data(attr_b), data_size, - GFP_KERNEL); - if (!*res) - err = -ENOMEM; + if (res) { + *res = NULL; + if (data_size) { + struct page *page = alloc_page(GFP_KERNEL); + if (!page) { + err = -ENOMEM; + goto out; + } + + *res = page_address(page); + memcpy(*res, resident_data(attr_b), data_size); + } } goto out; } @@ -1069,7 +1071,8 @@ again: mi = mi_b; if (le_b && (vcn < svcn || evcn1 <= vcn)) { - attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, + attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, &vcn, &mi); if (!attr) { err = -EINVAL; @@ -1100,7 +1103,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. */ @@ -1141,8 +1145,9 @@ again: if (vcn < svcn || evcn1 <= vcn) { struct ATTRIB *attr2; /* Load runs for truncated vcn. */ - attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL, - 0, &vcn, &mi); + attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, + ni->file.ads.name, + ni->file.ads.len, &vcn, &mi); if (!attr2) { err = -EINVAL; goto out; @@ -1152,6 +1157,22 @@ again: if (err) goto out; } + + if (vcn0 < svcn || evcn1 <= vcn0) { + struct ATTRIB *attr2; + + attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, + ni->file.ads.name, + ni->file.ads.len, &vcn0, &mi); + if (!attr2) { + err = -EINVAL; + goto out; + } + err = attr_load_runs(attr2, ni, run, NULL); + if (err) + goto out; + } + da = false; /* no delalloc for compressed file. */ } @@ -1255,8 +1276,9 @@ repack: goto undo1; /* Layout of records is changed. */ le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, - 0, NULL, &mi_b); + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, + ni->file.ads.name, + ni->file.ads.len, NULL, &mi_b); if (!attr_b) { err = -ENOENT; goto out; @@ -1286,7 +1308,8 @@ repack: svcn = evcn1; /* Estimate next attribute. */ - attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi); + attr = ni_find_attr(ni, attr, &le, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, &svcn, &mi); if (!attr) { /* Insert new attribute segment. */ @@ -1319,7 +1342,8 @@ repack: goto out; } - attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0, &le->id); + 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; @@ -1348,9 +1372,10 @@ repack: ins_ext: if (evcn1 > next_svcn) { - err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run, - next_svcn, evcn1 - next_svcn, - attr_b->flags, &attr, &mi, NULL); + err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, run, next_svcn, + evcn1 - next_svcn, attr_b->flags, + &attr, &mi, NULL); if (err) goto out; } @@ -1384,7 +1409,8 @@ int attr_data_write_resident(struct ntfs_inode *ni, struct folio *folio) struct ATTRIB *attr; u32 data_size; - attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi); + attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, NULL, &mi); if (!attr) return -EINVAL; @@ -1501,7 +1527,7 @@ int attr_wof_frame_info(struct ntfs_inode *ni, struct ATTRIB *attr, u8 bytes_per_off; char *addr; struct folio *folio; - int i, err; + int i, err = 0; __le32 *off32; __le64 *off64; @@ -1576,6 +1602,12 @@ int attr_wof_frame_info(struct ntfs_inode *ni, struct ATTRIB *attr, u64 from = vbo[i] & ~(u64)(PAGE_SIZE - 1); u64 to = min(from + PAGE_SIZE, wof_size); + if (from >= wof_size) { + _ntfs_bad_inode(&ni->vfs_inode); + err = -EINVAL; + goto out1; + } + err = attr_load_runs_range(ni, ATTR_DATA, WOF_NAME, ARRAY_SIZE(WOF_NAME), run, from, to); @@ -1755,7 +1787,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, u64 total_size, valid_size, data_size; le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b); + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, NULL, &mi_b); if (!attr_b) return -ENOENT; @@ -1778,7 +1811,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, goto out; } else { le = le_b; - attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, + attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, &vcn, &mi); if (!attr) { err = -EINVAL; @@ -1865,8 +1899,9 @@ repack: goto out; /* Layout of records is changed. */ le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, - 0, NULL, &mi_b); + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, + ni->file.ads.name, + ni->file.ads.len, NULL, &mi_b); if (!attr_b) { err = -ENOENT; goto out; @@ -1882,7 +1917,8 @@ repack: svcn = evcn1; /* Estimate next attribute. */ - attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi); + 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( @@ -1911,7 +1947,8 @@ repack: goto out; } - attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0, + attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, &le->id); if (!attr) { err = -EINVAL; @@ -1942,9 +1979,10 @@ repack: } ins_ext: if (evcn1 > next_svcn) { - err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run, - next_svcn, evcn1 - next_svcn, - attr_b->flags, &attr, &mi, NULL); + err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, run, next_svcn, + evcn1 - next_svcn, attr_b->flags, + &attr, &mi, NULL); if (err) goto out; } @@ -1987,7 +2025,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) return 0; le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b); + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, NULL, &mi_b); if (!attr_b) return -ENOENT; @@ -2017,7 +2056,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) /* Simple truncate file at 'vbo'. */ truncate_setsize(&ni->vfs_inode, vbo); - err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, vbo, + err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, &ni->file.run, vbo, &valid_size, true); if (!err && valid_size < ni->i_valid) @@ -2041,7 +2081,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) /* * The requested range is full in delayed clusters. */ - err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, run, + err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, run, i_size - bytes, NULL, false, NULL, true); goto out; @@ -2054,7 +2095,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) /* Layout of records maybe changed. */ le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, NULL, &mi_b); if (!attr_b || !attr_b->non_res) { err = -ENOENT; @@ -2085,7 +2127,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) } le = le_b; - attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, &mi); + attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, &vcn, &mi); if (!attr) { err = -EINVAL; goto out; @@ -2149,7 +2192,8 @@ check_seg: next_svcn = le64_to_cpu(attr->nres.evcn) + 1; if (next_svcn + eat + done < evcn1) { err = ni_insert_nonresident( - ni, ATTR_DATA, NULL, 0, run, next_svcn, + ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, run, next_svcn, evcn1 - eat - next_svcn, a_flags, &attr, &mi, &le); if (err) @@ -2189,7 +2233,8 @@ check_seg: /* Look for required attribute. */ attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, - NULL, 0, &le->id); + ni->file.ads.name, + ni->file.ads.len, &le->id); if (!attr) { err = -EINVAL; goto out; @@ -2212,7 +2257,8 @@ next_attr: if (!attr_b) { le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, NULL, &mi_b); if (!attr_b) { err = -ENOENT; @@ -2273,7 +2319,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size) return 0; le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b); + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, NULL, &mi_b); if (!attr_b) return -ENOENT; @@ -2344,7 +2391,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size) goto bad_inode; } else { le = le_b; - attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, + attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, &vcn, &mi); if (!attr) { err = -EINVAL; @@ -2396,10 +2444,10 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size) next_svcn = le64_to_cpu(attr->nres.evcn) + 1; if (next_svcn < evcn1) { /* Insert new attribute segment. */ - err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run, - next_svcn, - evcn1 - next_svcn, a_flags, - &attr, &mi, &le); + err = ni_insert_nonresident( + ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, run, next_svcn, + evcn1 - next_svcn, a_flags, &attr, &mi, &le); if (err) goto undo_punch; @@ -2434,7 +2482,8 @@ done: goto out; if (!attr_b) { - attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, + 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; @@ -2492,7 +2541,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) return 0; le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b); + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, NULL, &mi_b); if (!attr_b) return -ENOENT; @@ -2539,11 +2589,13 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) down_write(&ni->file.run_lock); if (!attr_b->non_res) { - err = attr_set_size(ni, ATTR_DATA, NULL, 0, run, - data_size + bytes, NULL, false); + err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, run, data_size + bytes, + NULL, false); le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, NULL, &mi_b); if (!attr_b) { err = -EINVAL; @@ -2584,7 +2636,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) goto bad_inode; } else { le = le_b; - attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, + attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, &vcn, &mi); if (!attr) { err = -EINVAL; @@ -2627,12 +2680,14 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) } if (next_svcn < evcn1 + len) { - err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run, - next_svcn, evcn1 + len - next_svcn, - a_flags, NULL, NULL, NULL); + err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, run, next_svcn, + evcn1 + len - next_svcn, a_flags, + NULL, NULL, NULL); le_b = NULL; - attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, + attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, NULL, &mi_b); if (!attr_b) { err = -EINVAL; @@ -2689,7 +2744,8 @@ undo_insert_range: goto bad_inode; } else { le = le_b; - attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, + attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, &vcn, &mi); if (!attr) { goto bad_inode; @@ -2734,7 +2790,8 @@ int attr_force_nonresident(struct ntfs_inode *ni) struct ATTR_LIST_ENTRY *le = NULL; struct mft_inode *mi; - attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, &mi); + attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, NULL, &mi); if (!attr) { _ntfs_bad_inode(&ni->vfs_inode); return -ENOENT; diff --git a/fs/ntfs3/attrlist.c b/fs/ntfs3/attrlist.c index 270a29323530..8710560cf294 100644 --- a/fs/ntfs3/attrlist.c +++ b/fs/ntfs3/attrlist.c @@ -19,6 +19,7 @@ static inline bool al_is_valid_le(const struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le) { + ni = ni->base; if (!le || !ni->attr_list.le || !ni->attr_list.size) return false; @@ -28,6 +29,7 @@ static inline bool al_is_valid_le(const struct ntfs_inode *ni, void al_destroy(struct ntfs_inode *ni) { + ni = ni->base; run_close(&ni->attr_list.run); kvfree(ni->attr_list.le); ni->attr_list.le = NULL; @@ -47,6 +49,7 @@ int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr) size_t lsize; void *le = NULL; + ni = ni->base; if (ni->attr_list.size) return 0; @@ -199,6 +202,7 @@ struct ATTR_LIST_ENTRY *al_find_ex(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *ret = NULL; u32 type_in = le32_to_cpu(type); + ni = ni->base; while ((le = al_enumerate(ni, le))) { u64 le_vcn; int diff = le32_to_cpu(le->type) - type_in; @@ -256,6 +260,7 @@ static struct ATTR_LIST_ENTRY *al_find_le_to_insert(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le = NULL, *prev; u32 type_in = le32_to_cpu(type); + ni = ni->base; /* List entries are sorted by type, name and VCN. */ while ((le = al_enumerate(ni, prev = le))) { int diff = le32_to_cpu(le->type) - type_in; @@ -305,6 +310,7 @@ int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name, u64 new_size; typeof(ni->attr_list) *al = &ni->attr_list; + ni = ni->base; /* * Compute the size of the new 'le' */ @@ -374,8 +380,10 @@ bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le) { u16 size; size_t off; - typeof(ni->attr_list) *al = &ni->attr_list; + typeof(ni->attr_list) *al; + ni = ni->base; + al = &ni->attr_list; if (!al_is_valid_le(ni, le)) return false; @@ -395,7 +403,10 @@ int al_update(struct ntfs_inode *ni, int sync) { int err; struct ATTRIB *attr; - typeof(ni->attr_list) *al = &ni->attr_list; + typeof(ni->attr_list) *al; + + ni = ni->base; + al = &ni->attr_list; if (!al->dirty || !al->size) return 0; diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c index 4652a56ad105..eb9152e9fa22 100644 --- a/fs/ntfs3/dir.c +++ b/fs/ntfs3/dir.c @@ -25,6 +25,11 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len, static_assert(sizeof(wchar_t) == sizeof(__le16)); + if (buf_len <= 0) + return -EINVAL; + + buf_len -= 1; + if (!nls) { /* UTF-16 -> UTF-8 */ ret = utf16s_to_utf8s((wchar_t *)name, len, UTF16_LITTLE_ENDIAN, @@ -179,7 +184,7 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len, struct cpu_str *uni, u32 max_ulen, enum utf16_endian endian) { - int ret, slen; + int ret, slen, i; const u8 *end; struct nls_table *nls = sbi->options->nls; u16 *uname = uni->name; @@ -189,50 +194,83 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len, if (!nls) { /* utf8 -> utf16 */ ret = _utf8s_to_utf16s(name, name_len, endian, uname, max_ulen); - uni->len = ret; - return ret; - } - - for (ret = 0, end = name + name_len; name < end; ret++, name += slen) { - if (ret >= max_ulen) - return -ENAMETOOLONG; + } else { + for (ret = 0, end = name + name_len; name < end; + ret++, name += slen) { + if (ret >= max_ulen) + return -ENAMETOOLONG; - slen = nls->char2uni(name, end - name, uname + ret); - if (!slen) - return -EINVAL; - if (slen < 0) - return slen; - } + slen = nls->char2uni(name, end - name, uname + ret); + if (!slen) + return -EINVAL; + if (slen < 0) + return slen; + } #ifdef __BIG_ENDIAN - if (endian == UTF16_LITTLE_ENDIAN) { - int i = ret; + if (endian == UTF16_LITTLE_ENDIAN) { + i = ret; - while (i--) { - __cpu_to_le16s(uname); - uname++; + while (i--) { + __cpu_to_le16s(uname); + uname++; + } } - } #else - if (endian == UTF16_BIG_ENDIAN) { - int i = ret; + if (endian == UTF16_BIG_ENDIAN) { + i = ret; - while (i--) { - __cpu_to_be16s(uname); - uname++; + while (i--) { + __cpu_to_be16s(uname); + uname++; + } } - } #endif + } uni->len = ret; + uni->ads_len = 0; + if (ret > 0 && sbi->options->ads) { + uname = uni->name; + /* Find delimiter in range [1 : ret-2). */ + for (i = 1; i + 1 < ret; i++) { + if (uname[i] == ':') { + uni->ads_len = ret - i - 1; + uni->len = i; + uname[i] = 0; + ret = i; + + uname += i + 1; + i = uni->ads_len; + /* Return ADS name as little endian. Always */ +#ifdef __BIG_ENDIAN + if (endian == UTF16_LITTLE_ENDIAN) { + while (i--) { + __cpu_to_le16s(uname); + uname++; + } + } +#else + if (endian == UTF16_BIG_ENDIAN) { + while (i--) { + __cpu_to_be16s(uname); + uname++; + } + } +#endif + break; + } + } + } + return ret; } /* * dir_search_u - Helper function. */ -struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni, - struct ntfs_fnd *fnd) +struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni, + struct ntfs_fnd *fnd, u32 flags) { int err = 0; struct super_block *sb = dir->i_sb; @@ -262,7 +300,7 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni, goto out; } - inode = ntfs_iget5(sb, &e->ref, uni); + inode = ntfs_iget5_flags(sb, &e->ref, uni, flags); if (!IS_ERR(inode) && is_bad_inode(inode)) { iput(inode); err = -EINVAL; @@ -273,6 +311,12 @@ out: return err == -ENOENT ? NULL : err ? ERR_PTR(err) : inode; } +static inline bool de_fname_fits(const struct NTFS_DE *e, u32 e_size, + const struct ATTR_FILE_NAME *fname) +{ + return sizeof(struct NTFS_DE) + fname_full_size(fname) <= e_size; +} + /* * returns false if 'ctx' if full */ @@ -281,7 +325,7 @@ static inline bool ntfs_dir_emit(struct ntfs_sb_info *sbi, u8 *name, struct dir_context *ctx) { const struct ATTR_FILE_NAME *fname; - unsigned long ino; + u64 ino; int name_len; u32 dt_type; @@ -305,13 +349,13 @@ 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 (!de_fname_fits(e, le16_to_cpu(e->size), fname)) return true; name_len = ntfs_utf16_to_nls(sbi, fname->name, fname->name_len, name, PATH_MAX); if (name_len <= 0) { - ntfs_warn(sbi->sb, "failed to convert name for inode %lx.", + ntfs_warn(sbi->sb, "failed to convert name for inode %llx.", ino); return true; } @@ -489,10 +533,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 +554,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 +565,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 +575,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 +590,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); @@ -564,6 +618,23 @@ out: return err; } +/* + * Return fname when @e passes the same checks as ntfs_dir_emit() before + * exposing an entry (valid key, non-DOS, fname fits in e->size). + */ +static inline const struct ATTR_FILE_NAME * +de_countable_fname(const struct NTFS_DE *e, u32 e_size) +{ + const struct ATTR_FILE_NAME *fname; + + fname = de_get_fname(e); + if (!fname || fname->type == FILE_NAME_DOS || + !de_fname_fits(e, e_size, fname)) + return NULL; + + return fname; +} + static int ntfs_dir_count(struct inode *dir, bool *is_empty, size_t *dirs, size_t *files) { @@ -603,13 +674,10 @@ static int ntfs_dir_count(struct inode *dir, bool *is_empty, size_t *dirs, if (de_is_last(e)) break; - fname = de_get_fname(e); + fname = de_countable_fname(e, e_size); if (!fname) continue; - if (fname->type == FILE_NAME_DOS) - continue; - if (is_empty) { *is_empty = false; if (!dirs && !files) @@ -676,14 +744,4 @@ const struct file_operations ntfs_dir_operations = { #endif .setlease = generic_setlease, }; - -#if IS_ENABLED(CONFIG_NTFS_FS) -const struct file_operations ntfs_legacy_dir_operations = { - .llseek = generic_file_llseek, - .read = generic_read_dir, - .iterate_shared = ntfs_readdir, - .open = ntfs_file_open, - .setlease = generic_setlease, -}; -#endif // clang-format on diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 7eecf1e01f74..2abf334bfa0c 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)) @@ -203,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; @@ -246,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. */ @@ -276,7 +383,7 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc) struct file *file = desc->file; struct inode *inode = file_inode(file); struct ntfs_inode *ni = ntfs_i(inode); - const bool rw = vma_desc_test_flags(desc, VMA_WRITE_BIT); + const bool rw = vma_desc_test(desc, VMA_WRITE_BIT); int err; /* Avoid any operation if inode is bad. */ @@ -347,96 +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 = sync_mapping_buffers(mapping); - if (!err) - err = err2; - 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 * @@ -643,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); } } @@ -750,21 +735,27 @@ 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); - if (mode != inode->i_mode) { + if (!is_ni_base(ni)) { + ia_valid &= ~ATTR_SIZE; + } else if (mode != inode->i_mode) { err = ntfs_acl_chmod(idmap, dentry); if (err) goto out; @@ -831,15 +822,31 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) size_t bytes = iov_iter_count(iter); loff_t valid, i_size, vbo, end; unsigned int dio_flags; - ssize_t err; + ssize_t ret; - err = check_read_restriction(inode); - if (err) - return err; + ret = check_read_restriction(inode); + if (ret) + return ret; if (!bytes) return 0; /* skip atime */ + if (ni->file.ads.len == ARRAY_SIZE(QUERY_STREAMS) && + !memcmp(ni->file.ads.name, QUERY_STREAMS, sizeof(QUERY_STREAMS))) { + /* Query ADS. */ + if (unlikely(iocb->ki_flags & IOCB_DIRECT)) { + ntfs_inode_warn( + inode, + "direct I/O for streams is not supported"); + return -EOPNOTSUPP; + } + + inode_lock_shared(inode); + ret = ni_query_ads(ni, &iocb->ki_pos, iter); + inode_unlock_shared(inode); + return ret; + } + if (is_compressed(ni)) { if (iocb->ki_flags & IOCB_DIRECT) { ntfs_inode_warn( @@ -878,17 +885,17 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) if (ni->file.run_da.count) { /* Direct I/O is not compatible with delalloc. */ - err = ni_allocate_da_blocks(ni); - if (err) + ret = ni_allocate_da_blocks(ni); + if (ret) goto out; } - err = iomap_dio_rw(iocb, iter, &ntfs_iomap_ops, NULL, dio_flags, + ret = iomap_dio_rw(iocb, iter, &ntfs_iomap_ops, NULL, dio_flags, NULL, 0); - if (err <= 0) + if (ret <= 0) goto out; - end = vbo + err; + end = vbo + ret; if (valid < end) { size_t to_zero = end - valid; /* Fix iter. */ @@ -900,35 +907,36 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) bytes = i_size - vbo; iov_iter_zero(bytes, iter); iocb->ki_pos += bytes; - err = bytes; + ret = bytes; } out: inode_unlock_shared(inode); - file_accessed(iocb->ki_filp); - return err; + file_accessed(file); + return ret; } /* * ntfs_file_splice_read - file_operations::splice_read */ -static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos, +static ssize_t ntfs_file_splice_read(struct file *file, loff_t *ppos, struct pipe_inode_info *pipe, size_t len, unsigned int flags) { - struct inode *inode = file_inode(in); - ssize_t err; + struct inode *inode = file_inode(file); + struct ntfs_inode *ni = ntfs_i(inode); + ssize_t ret; - err = check_read_restriction(inode); - if (err) - return err; + ret = check_read_restriction(inode); + if (ret) + return ret; - if (is_compressed(ntfs_i(inode))) { + if (is_compressed(ni)) { /* Turn off readahead for compressed files. */ - in->f_ra.ra_pages = 0; + file->f_ra.ra_pages = 0; } - return filemap_splice_read(in, ppos, pipe, len, flags); + return filemap_splice_read(file, ppos, pipe, len, flags); } /* @@ -1023,7 +1031,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, @@ -1092,7 +1100,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))) { @@ -1233,6 +1241,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)) { @@ -1267,15 +1276,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)) { @@ -1298,7 +1322,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.*/ @@ -1307,7 +1332,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, @@ -1325,15 +1350,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); } @@ -1414,8 +1439,10 @@ 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, ni->file.ads.name, + ni->file.ads.len, &ni->file.run, + inode->i_size, &ni->i_valid, false, NULL, + true); up_write(&ni->file.run_lock); ni_unlock(ni); @@ -1527,7 +1554,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); @@ -1535,9 +1567,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; } @@ -1550,6 +1579,8 @@ const struct inode_operations ntfs_file_inode_operations = { .get_acl = ntfs_get_acl, .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 = { @@ -1569,15 +1600,4 @@ const struct file_operations ntfs_file_operations = { .release = ntfs_file_release, .setlease = generic_setlease, }; - -#if IS_ENABLED(CONFIG_NTFS_FS) -const struct file_operations ntfs_legacy_file_operations = { - .llseek = generic_file_llseek, - .read_iter = ntfs_file_read_iter, - .splice_read = ntfs_file_splice_read, - .open = ntfs_file_open, - .release = ntfs_file_release, - .setlease = generic_setlease, -}; -#endif // clang-format on diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index 2e901d073fe9..bead01a953f3 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -132,6 +132,13 @@ void ni_clear(struct ntfs_inode *ni) ni->file.offs_folio = NULL; } #endif + kfree(ni->file.ads.name); + ni->file.ads.name = NULL; + } + + if (ni->base && ni->base != ni) { + iput(&ni->base->vfs_inode); + ni->base = NULL; } mi_clear(&ni->mi); @@ -145,6 +152,7 @@ int ni_load_mi_ex(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi) int err; struct mft_inode *r; + ni = ni->base; r = ni_find_mi(ni, rno); if (r) goto out; @@ -169,8 +177,9 @@ out: int ni_load_mi(struct ntfs_inode *ni, const struct ATTR_LIST_ENTRY *le, struct mft_inode **mi) { - CLST rno; + u64 rno; + ni = ni->base; if (!le) { *mi = &ni->mi; return 0; @@ -197,6 +206,7 @@ struct ATTRIB *ni_find_attr(struct ntfs_inode *ni, struct ATTRIB *attr, struct ATTR_LIST_ENTRY *le; struct mft_inode *m; + ni = ni->base; if (!ni->attr_list.size || (!name_len && (type == ATTR_LIST || type == ATTR_STD))) { if (le_o) @@ -257,6 +267,7 @@ struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr, struct mft_inode *mi2; struct ATTR_LIST_ENTRY *le2; + ni = ni->base; /* Do we have an attribute list? */ if (!ni->attr_list.size) { *le = NULL; @@ -290,6 +301,7 @@ int ni_load_all_mi(struct ntfs_inode *ni) int err; struct ATTR_LIST_ENTRY *le; + ni = ni->base; if (!ni->attr_list.size) return 0; @@ -316,6 +328,7 @@ bool ni_add_subrecord(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi) { struct mft_inode *m; + ni = ni->base; m = kzalloc_obj(struct mft_inode, GFP_NOFS); if (!m) return false; @@ -348,6 +361,7 @@ int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type, u32 type_in; int diff; + ni = ni->base; if (base_only || type == ATTR_LIST || !ni->attr_list.size) { attr = mi_find_attr(ni, &ni->mi, NULL, type, name, name_len, id); @@ -417,6 +431,7 @@ ni_ins_new_attr(struct ntfs_inode *ni, struct mft_inode *mi, bool le_added = false; struct MFT_REF ref; + ni = ni->base; mi_get_ref(mi, &ref); if (type != ATTR_LIST && !le && ni->attr_list.size) { @@ -618,6 +633,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni) struct MFT_REC *mrec; __le16 id; + ni = ni->base; if (!ni->attr_list.dirty) return 0; @@ -763,15 +779,29 @@ int ni_create_attr_list(struct ntfs_inode *ni) u32 free_b, nb, to_free, rs; u16 sz; + ni = ni->base; is_mft = ni->mi.rno == MFT_REC_MFT; rec = ni->mi.mrec; rs = sbi->record_size; /* - * Skip estimating exact memory requirement. - * Looks like one record_size is always enough. + * Compute the exact size of the attribute list. Each attribute in the + * record yields one ATTR_LIST_ENTRY of le_size(name_len) bytes. The + * minimum on-disk attribute is SIZEOF_RESIDENT (0x18) bytes, but an + * unnamed one expands to le_size(0) (0x20) here, so a record crafted + * with many such attributes needs more than a single record_size; the + * previous fixed kzalloc(record_size) could therefore be overflowed by + * an attacker-controlled record. */ - le = kzalloc(al_aligned(rs), GFP_NOFS); + lsize = 0; + attr = NULL; + while ((attr = mi_enum_attr(ni, &ni->mi, attr))) + lsize += le_size(attr->name_len); + + if (!lsize) + return -EINVAL; + + le = kzalloc(al_aligned(lsize), GFP_NOFS); if (!le) return -ENOMEM; @@ -781,7 +811,6 @@ int ni_create_attr_list(struct ntfs_inode *ni) attr = NULL; nb = 0; free_b = 0; - attr = NULL; for (; (attr = mi_enum_attr(ni, &ni->mi, attr)); le = Add2Ptr(le, sz)) { sz = le_size(attr->name_len); @@ -905,6 +934,7 @@ static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le, bool is_mft, is_mft_data; struct ntfs_sb_info *sbi = ni->mi.sbi; + ni = ni->base; is_mft = ni->mi.rno == MFT_REC_MFT; is_mft_data = is_mft && type == ATTR_DATA && !name_len; @@ -1057,6 +1087,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type, __le16 id; u16 t16; + ni = ni->base; is_mft = ni->mi.rno == MFT_REC_MFT; rec = ni->mi.mrec; @@ -1203,6 +1234,7 @@ static int ni_expand_mft_list(struct ntfs_inode *ni) struct mft_inode *mi, *mi_min, *mi_new; struct ntfs_sb_info *sbi = ni->mi.sbi; + ni = ni->base; /* Find the nearest MFT. */ mft_min = 0; mft_new = 0; @@ -1330,11 +1362,12 @@ 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; + ni = ni->base; mi_get_ref(&ni->mi, &ref); le = NULL; @@ -1363,7 +1396,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); @@ -1407,6 +1440,7 @@ int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type, u32 run_size, asize; struct ntfs_sb_info *sbi = ni->mi.sbi; + ni = ni->base; /* Estimate packed size (run_buf=NULL). */ err = run_pack(run, svcn, len, NULL, sbi->max_bytes_per_attr - run_off, &plen); @@ -1476,6 +1510,7 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size, u32 asize = SIZEOF_RESIDENT + name_size + ALIGN(data_size, 8); struct ATTRIB *attr; + ni = ni->base; err = ni_insert_attr(ni, type, name, name_len, asize, SIZEOF_RESIDENT, 0, &attr, mi, le); if (err) @@ -1507,6 +1542,7 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size, void ni_remove_attr_le(struct ntfs_inode *ni, struct ATTRIB *attr, struct mft_inode *mi, struct ATTR_LIST_ENTRY *le) { + ni = ni->base; mi_remove_attr(ni, mi, attr); if (le) @@ -1531,6 +1567,7 @@ int ni_delete_all(struct ntfs_inode *ni) bool nt3 = is_ntfs3(sbi); struct MFT_REF ref; + ni = ni->base; while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) { if (!nt3 || attr->name_len) { ; @@ -1608,6 +1645,7 @@ struct ATTR_FILE_NAME *ni_fname_name(struct ntfs_inode *ni, struct ATTRIB *attr = NULL; struct ATTR_FILE_NAME *fname; + ni = ni->base; if (le) *le = NULL; @@ -1649,6 +1687,7 @@ struct ATTR_FILE_NAME *ni_fname_type(struct ntfs_inode *ni, u8 name_type, struct ATTR_FILE_NAME *fname; *le = NULL; + ni = ni->base; if (name_type == FILE_NAME_POSIX) return NULL; @@ -1679,6 +1718,7 @@ int ni_new_attr_flags(struct ntfs_inode *ni, enum FILE_ATTRIBUTE new_fa) __le16 new_aflags; u32 new_asize; + ni = ni->base; attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi); if (!attr) return -EINVAL; @@ -1760,6 +1800,7 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr, u16 len; typeof(rp->CompressReparseBuffer) *cmpr; + ni = ni->base; /* Try to estimate reparse point. */ if (!attr->non_res) { rp = resident_data_ex(attr, sizeof(struct REPARSE_DATA_BUFFER)); @@ -1852,27 +1893,31 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr, return REPARSE_LINK; } -static struct page *ntfs_lock_new_page(struct address_space *mapping, - pgoff_t index, gfp_t gfp) +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 page *page; if (IS_ERR(folio)) - return ERR_CAST(folio); + return folio; - if (!folio_test_uptodate(folio)) - return folio_file_page(folio, index); + if (!folio_test_uptodate(folio)) { + struct page *page = folio_file_page(folio, index); + + if (IS_ERR(page)) + return ERR_CAST(page); + return page_folio(page); + } /* Use a temporary page to avoid data corruption */ folio_unlock(folio); folio_put(folio); - page = alloc_page(gfp); - if (!page) + folio = folio_alloc(gfp, 0); + if (!folio) return ERR_PTR(-ENOMEM); - __SetPageLocked(page); - return page; + __folio_set_locked(folio); + return folio; } /* @@ -1894,6 +1939,7 @@ int ni_read_folio_cmpr(struct ntfs_inode *ni, struct folio *folio) u32 i, idx, frame_size, pages_per_frame; gfp_t gfp_mask; struct page *pg; + struct folio *f; if (vbo >= i_size_read(&ni->vfs_inode)) { folio_zero_range(folio, 0, folio_size(folio)); @@ -1929,12 +1975,12 @@ int ni_read_folio_cmpr(struct ntfs_inode *ni, struct folio *folio) if (i == idx) continue; - pg = ntfs_lock_new_page(mapping, index, gfp_mask); - if (IS_ERR(pg)) { - err = PTR_ERR(pg); + f = ntfs_lock_new_page(mapping, index, gfp_mask); + if (IS_ERR(f)) { + err = PTR_ERR(f); goto out1; } - pages[i] = pg; + pages[i] = &f->page; } ni_lock(ni); @@ -1983,6 +2029,7 @@ int ni_decompress_file(struct ntfs_inode *ni) struct mft_inode *mi; int err; + ni = ni->base; /* Clusters for decompressed data. */ cend = bytes_to_cluster(sbi, i_size); @@ -2023,18 +2070,18 @@ int ni_decompress_file(struct ntfs_inode *ni) } for (i = 0; i < pages_per_frame; i++, index++) { - struct page *pg; + struct folio *f; - pg = ntfs_lock_new_page(mapping, index, gfp_mask); - if (IS_ERR(pg)) { + f = ntfs_lock_new_page(mapping, index, gfp_mask); + if (IS_ERR(f)) { while (i--) { unlock_page(pages[i]); put_page(pages[i]); } - err = PTR_ERR(pg); + err = PTR_ERR(f); goto out; } - pages[i] = pg; + pages[i] = &f->page; } err = ni_read_frame(ni, vbo, pages, pages_per_frame, 1); @@ -2244,6 +2291,7 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages, struct ATTRIB *attr; CLST frame, clst_data; + ni = ni->base; /* * To simplify decompress algorithm do vmap for source * and target pages. @@ -2438,6 +2486,15 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages, err = unc_size; else if (!unc_size || unc_size > frame_size) err = -EINVAL; + else if (unc_size < frame_size) { + /* + * Partial decompress: zero the [unc_size, frame_size) + * tail. decompress_lznt() leaves it untouched, so + * without this the freshly vmapped pages would expose + * uninitialized kernel memory to userspace. + */ + memset(frame_mem + unc_size, 0, frame_size - unc_size); + } } if (!err && valid_size < frame_vbo + frame_size) { size_t ok = valid_size - frame_vbo; @@ -2493,6 +2550,7 @@ int ni_write_frame(struct ntfs_inode *ni, struct page **pages, size_t compr_size, ondisk_size; struct lznt *lznt; + ni = ni->base; attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, &mi); if (!attr) { err = -ENOENT; @@ -2618,6 +2676,7 @@ int ni_remove_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni, u16 de_key_size = le16_to_cpu(de->key_size); u8 name_type; + ni = ni->base; *undo_step = 0; /* Find name in record. */ @@ -2680,6 +2739,7 @@ bool ni_remove_name_undo(struct ntfs_inode *dir_ni, struct ntfs_inode *ni, struct ATTRIB *attr; u16 de_key_size; + ni = ni->base; switch (undo_step) { case 4: de_key_size = le16_to_cpu(de2->key_size); @@ -2730,6 +2790,7 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni, struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1); u16 de_key_size = le16_to_cpu(de->key_size); + ni = ni->base; if (sbi->options->windows_names && !valid_windows_name(sbi, (struct le_str *)&de_name->name_len)) return -EINVAL; @@ -2777,6 +2838,7 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni, int err; struct NTFS_DE *de2 = NULL; int undo = 0; + const int way = 1; /* Hope compiler removes below 'else'. */ /* * There are two possible ways to rename: @@ -2788,29 +2850,30 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni, * Second way may result to bad inode if we can't add new name * and then can't restore (add) old name. */ - - /* - * Way 1 - Add new + remove old. - */ - err = ni_add_name(new_dir_ni, ni, new_de); - if (!err) { + if (way == 1) { + /* + * Way 1 - Add new + remove old. + */ + err = ni_add_name(new_dir_ni, ni, new_de); + if (!err) { + err = ni_remove_name(dir_ni, ni, de, &de2, &undo); + if (err && + ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo)) + _ntfs_bad_inode(&ni->vfs_inode); + } + } else { + /* + * Way 2 - Remove old + add new. + */ 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) { + err = ni_add_name(new_dir_ni, ni, new_de); + if (err && + !ni_remove_name_undo(dir_ni, ni, de, de2, undo)) + _ntfs_bad_inode(&ni->vfs_inode); + } } - /* - * Way 2 - Remove old + add new. - */ - /* - * err = ni_remove_name(dir_ni, ni, de, &de2, &undo); - * if (!err) { - * err = ni_add_name(new_dir_ni, ni, new_de); - * if (err && !ni_remove_name_undo(dir_ni, ni, de, de2, undo)) - * *is_bad = true; - * } - */ - return err; } @@ -2854,6 +2917,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) { @@ -2884,8 +2952,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 { /* @@ -2899,16 +2973,14 @@ 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); } /* @@ -2924,6 +2996,9 @@ int ni_write_parents(struct ntfs_inode *ni, int sync) struct ntfs_sb_info *sbi = ni->mi.sbi; struct super_block *sb = sbi->sb; + if (!is_ni_base(ni)) + return 0; + while ((attr = ni_find_attr(ni, attr, &le, ATTR_NAME, NULL, 0, NULL, NULL))) { struct inode *dir; @@ -2946,8 +3021,8 @@ int ni_write_parents(struct ntfs_inode *ni, int sync) if (IS_ERR(dir)) { ntfs_inode_warn( &ni->vfs_inode, - "failed to open parent directory r=%lx to write", - (long)ino_get(&fname->home)); + "failed to open parent directory r=%llx to write", + (u64)ino_get(&fname->home)); continue; } @@ -3066,8 +3141,8 @@ static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup, if (IS_ERR(dir)) { ntfs_inode_warn( &ni->vfs_inode, - "failed to open parent directory r=%lx to update", - (long)ino_get(&fname->home)); + "failed to open parent directory r=%llx to update", + (u64)ino_get(&fname->home)); continue; } @@ -3106,6 +3181,9 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint) if (is_bad_inode(inode) || sb_rdonly(sb)) return 0; + if (!is_ni_base(ni)) + return 0; + /* Avoid any operation if inode is bad. */ if (unlikely(is_bad_ni(ni))) return -EINVAL; @@ -3262,7 +3340,7 @@ int ni_allocate_da_blocks(struct ntfs_inode *ni) */ int ni_allocate_da_blocks_locked(struct ntfs_inode *ni) { - int err; + int err = 0; if (!ni->file.run_da.count) return 0; @@ -3290,10 +3368,84 @@ int ni_allocate_da_blocks_locked(struct ntfs_inode *ni) /* * Normal file allocates clusters in 'attr_set_size' */ - err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run, + err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, &ni->file.run, ni->vfs_inode.i_size, &ni->i_valid, false, NULL, true); } return err; } + +/* + * Helper function to read ADS. + * bytes = iov_iter_count(iter) is not 0. Checked by caller; + */ +ssize_t ni_query_ads(struct ntfs_inode *ni, loff_t *pos, struct iov_iter *iter) +{ + ssize_t ret = 0; + struct ntfs_sb_info *sbi = ni->mi.sbi; + size_t bytes = iov_iter_count(iter); + loff_t end = *pos + bytes; + char *buf = NULL; + struct ATTR_LIST_ENTRY *le = NULL; + u64 vbo = 0; + struct ATTRIB *attr; + size_t done, used; + int err; + + /* + * Enumerate ADS. + */ + ni = ni->base; + attr = NULL; + while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) { + if (attr->type != ATTR_DATA || !attr->name_len) + continue; + + if (!buf) { + buf = kmalloc(PAGE_SIZE, GFP_NOFS); + if (!buf) + return -ENOMEM; + } + + /* attr - named DATA attribute (ADS). */ + err = ntfs_utf16_to_nls(sbi, attr_name(attr), attr->name_len, + buf, PAGE_SIZE); + if (err < 0) { + ret = err; + break; + } + + /* + * err is the length of ADS name in bytes. + * Copy pseudo data interval [vbo, err + 1). + * into 'iter': [*pos, bytes) + */ + /* Add \n as streams name separator. */ + buf[err++] = '\n'; + + if (vbo >= end) { + iov_iter_zero(bytes, iter); + break; + } + + if (vbo + err > *pos) { + size_t off = *pos - vbo; + used = err - off; + done = copy_to_iter(Add2Ptr(buf, off), min(used, bytes), + iter); + } else { + done = 0; + } + + ret += done; + *pos += done; + if (done >= bytes) + break; + bytes -= done; + vbo += err; + } + kfree(buf); + return ret; +} diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c index 272e45276143..ed50c1d0c23e 100644 --- a/fs/ntfs3/fslog.c +++ b/fs/ntfs3/fslog.c @@ -45,10 +45,10 @@ struct CLIENT_REC { __le16 seq_num; // 0x14: u8 align[6]; // 0x16: __le32 name_bytes; // 0x1C: In bytes. - __le16 name[32]; // 0x20: Name of client. + __le16 name[64]; // 0x20: Name of client. }; -static_assert(sizeof(struct CLIENT_REC) == 0x60); +static_assert(sizeof(struct CLIENT_REC) == 0xa0); /* Two copies of these will exist at the beginning of the log file */ struct RESTART_AREA { @@ -648,6 +648,14 @@ static inline void *enum_rstbl(struct RESTART_TABLE *t, void *c) } /* + * dp_range_ok - true if [j, j + count) fits in a page_lcns[cap] array. + */ +static inline bool dp_range_ok(size_t j, u32 count, u32 cap) +{ + return j < cap && count <= cap - j; +} + +/* * find_dp - Search for a @vcn in Dirty Page Table. */ static inline struct DIR_PAGE_ENTRY *find_dp(struct RESTART_TABLE *dptbl, @@ -764,8 +772,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; @@ -778,6 +797,20 @@ static bool check_rstbl(const struct RESTART_TABLE *rt, size_t bytes) return true; } +static bool check_dp_table(const struct RESTART_TABLE *dptbl) +{ + u32 rsize = le16_to_cpu(dptbl->size); + struct DIR_PAGE_ENTRY *dp = NULL; + + while ((dp = enum_rstbl((struct RESTART_TABLE *)dptbl, dp))) { + if (struct_size(dp, page_lcns, le32_to_cpu(dp->lcns_follow)) > + rsize) + return false; + } + + return true; +} + /* * free_rsttbl_idx - Free a previously allocated index a Restart Table. */ @@ -842,6 +875,9 @@ static inline struct RESTART_TABLE *extend_rsttbl(struct RESTART_TABLE *tbl, u32 used = le16_to_cpu(tbl->used); struct RESTART_TABLE *rt; + if (used + add > U16_MAX) + return NULL; + rt = init_rsttbl(esize, used + add); if (!rt) return NULL; @@ -1172,7 +1208,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); @@ -2265,7 +2301,15 @@ static int read_log_rec_buf(struct ntfs_log *log, */ for (;;) { bool usa_error; - u32 tail = log->page_size - off; + u32 tail; + + /* off comes from the on-disk restart area; bound it. */ + if (off > log->page_size) { + err = -EINVAL; + goto out; + } + + tail = log->page_size - off; if (tail >= data_len) tail = data_len; @@ -2599,11 +2643,11 @@ 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 +2664,31 @@ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes) return false; } - e = Add2Ptr(hdr, de_off); for (;;) { + const struct NTFS_DE *e = Add2Ptr(hdr, de_off); 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; + } + + data_size = esize - min_de; + if (key_size > data_size) + return false; - e = next; + de_off += esize; } return true; @@ -2791,13 +2846,14 @@ static inline bool check_file_record(const struct MFT_REC *rec, u16 fn = le16_to_cpu(rec->rhdr.fix_num); u16 ao = le16_to_cpu(rec->attr_off); u32 rs = sbi->record_size; + u32 used = le32_to_cpu(rec->used); /* Check the file record header for consistency. */ if (rec->rhdr.sign != NTFS_FILE_SIGNATURE || fo > (SECTOR_SIZE - ((rs >> SECTOR_SHIFT) + 1) * sizeof(short)) || (fn - 1) * SECTOR_SIZE != rs || ao < MFTRECORD_FIXUP_OFFSET_1 || ao > sbi->record_size - SIZEOF_RESIDENT || !is_rec_inuse(rec) || - le32_to_cpu(rec->total) != rs) { + le32_to_cpu(rec->total) != rs || used > rs || used < ao) { return false; } @@ -2809,6 +2865,15 @@ static inline bool check_file_record(const struct MFT_REC *rec, return false; } + /* + * The do_action() handlers compute memmove lengths as + * "rec->used - <offset of validated attr>", which underflows when + * rec->used is smaller than the attribute walk reached. At this + * point attr is the ATTR_END marker; rec->used must cover it. + */ + if (used < PtrOffset(rec, attr) + sizeof(attr->type)) + return false; + return true; } @@ -3315,6 +3380,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(). @@ -3358,7 +3434,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; } @@ -3487,6 +3566,17 @@ 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; @@ -3560,9 +3650,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); @@ -3679,6 +3783,11 @@ 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; @@ -3786,11 +3895,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; @@ -3928,9 +4033,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 || @@ -4199,6 +4323,11 @@ check_dirty_page_table: goto out; } + if (!check_dp_table(rt)) { + err = -EINVAL; + goto out; + } + dptbl = kmemdup(rt, t32, GFP_NOFS); if (!dptbl) { err = -ENOMEM; @@ -4209,13 +4338,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: @@ -4537,12 +4679,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: { @@ -4956,6 +5120,13 @@ find_dirty_page: /* Shorten length by any Lcns which were deleted. */ saved_len = dlen; + if (!dp_range_ok(le64_to_cpu(lrh->target_vcn) - le64_to_cpu(dp->vcn), + le16_to_cpu(lrh->lcns_follow), + le32_to_cpu(dp->lcns_follow))) { + err = -EINVAL; + goto out; + } + for (i = le16_to_cpu(lrh->lcns_follow); i; i--) { size_t j; u32 alen, voff; diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c index 0df2aa81d884..97c04ab2763a 100644 --- a/fs/ntfs3/fsntfs.c +++ b/fs/ntfs3/fsntfs.c @@ -88,6 +88,13 @@ const __le16 SQ_NAME[2] = { const __le16 SR_NAME[2] = { cpu_to_le16('$'), cpu_to_le16('R'), }; +const __le16 QUERY_STREAMS[13] = { + cpu_to_le16('q'), cpu_to_le16('u'), cpu_to_le16('e'), cpu_to_le16('r'), + cpu_to_le16('y'), cpu_to_le16('_'), cpu_to_le16('s'), cpu_to_le16('t'), + cpu_to_le16('r'), cpu_to_le16('e'), cpu_to_le16('a'), cpu_to_le16('m'), + cpu_to_le16('s'), +}; + #ifdef CONFIG_NTFS3_LZX_XPRESS const __le16 WOF_NAME[17] = { @@ -122,7 +129,6 @@ static const __le16 COM_NAME[3] = { static const __le16 LPT_NAME[3] = { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), }; - // clang-format on /* @@ -236,7 +242,7 @@ int ntfs_extend_init(struct ntfs_sb_info *sbi) } /* Try to find $ObjId */ - inode2 = dir_search_u(inode, &NAME_OBJID, NULL); + inode2 = dir_search(inode, &NAME_OBJID); if (inode2 && !IS_ERR(inode2)) { if (is_bad_inode(inode2)) { iput(inode2); @@ -247,21 +253,21 @@ int ntfs_extend_init(struct ntfs_sb_info *sbi) } /* Try to find $Quota */ - inode2 = dir_search_u(inode, &NAME_QUOTA, NULL); + inode2 = dir_search(inode, &NAME_QUOTA); if (inode2 && !IS_ERR(inode2)) { sbi->quota_no = inode2->i_ino; iput(inode2); } /* Try to find $Reparse */ - inode2 = dir_search_u(inode, &NAME_REPARSE, NULL); + inode2 = dir_search(inode, &NAME_REPARSE); if (inode2 && !IS_ERR(inode2)) { sbi->reparse.ni = ntfs_i(inode2); sbi->reparse_no = inode2->i_ino; } /* Try to find $UsnJrnl */ - inode2 = dir_search_u(inode, &NAME_USNJRNL, NULL); + inode2 = dir_search(inode, &NAME_USNJRNL); if (inode2 && !IS_ERR(inode2)) { sbi->usn_jrnl_no = inode2->i_ino; iput(inode2); @@ -475,7 +481,7 @@ bool ntfs_check_free_space(struct ntfs_sb_info *sbi, CLST clen, CLST mlen, avail = free - (zlen + clen); - /* + /* * When delalloc is active then keep in mind some reserved space. * The worst case: 1 mft record per each ~500 clusters. */ @@ -1440,8 +1446,8 @@ int ntfs_write_bh(struct ntfs_sb_info *sbi, struct NTFS_RECORD_HEADER *rhdr, u16 fo = le16_to_cpu(rhdr->fix_off); u16 fn = le16_to_cpu(rhdr->fix_num); u32 idx; - __le16 *fixup; - __le16 sample; + __le16 *fixup = NULL; + __le16 sample = cpu_to_le16(-1u); if ((fo & 1) || fo + fn * sizeof(short) > SECTOR_SIZE || !fn-- || fn * SECTOR_SIZE > bytes) { @@ -1705,6 +1711,8 @@ struct ntfs_inode *ntfs_new_inode(struct ntfs_sb_info *sbi, CLST rno, goto out; } + ni->base = ni; + out: if (err) { make_bad_inode(inode); @@ -2302,8 +2310,8 @@ int ntfs_reparse_init(struct ntfs_sb_info *sbi) goto out; } - root_r = resident_data(attr); - if (root_r->type != ATTR_ZERO || + root_r = resident_data_ex(attr, sizeof(struct INDEX_ROOT)); + if (!root_r || root_r->type != ATTR_ZERO || root_r->rule != NTFS_COLLATION_TYPE_UINTS) { err = -EINVAL; goto out; @@ -2340,8 +2348,8 @@ int ntfs_objid_init(struct ntfs_sb_info *sbi) goto out; } - root = resident_data(attr); - if (root->type != ATTR_ZERO || + root = resident_data_ex(attr, sizeof(struct INDEX_ROOT)); + if (!root || root->type != ATTR_ZERO || root->rule != NTFS_COLLATION_TYPE_UINTS) { err = -EINVAL; goto out; @@ -2654,7 +2662,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) @@ -2665,6 +2672,12 @@ int ntfs_set_label(struct ntfs_sb_info *sbi, u8 *label, int len) if (err < 0) goto out; + if (uni->ads_len) { + /* Undo delimiter parse */ + uni->len += uni->ads_len + 1; + uni->ads_len = 0; + } + uni_bytes = uni->len * sizeof(u16); if (uni_bytes > NTFS_LABEL_MAX_LENGTH * sizeof(u16)) { ntfs_warn(sbi->sb, "new label is too long"); diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c index 97f06c26fe1a..689712d3463d 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; } @@ -714,10 +749,10 @@ static bool fnd_is_empty(struct ntfs_fnd *fnd) */ static struct NTFS_DE *hdr_find_e(const struct ntfs_index *indx, const struct INDEX_HDR *hdr, const void *key, - size_t key_len, const void *ctx, int *diff) + size_t key_len, const void *ctx, int *diff, + NTFS_CMP_FUNC cmp) { struct NTFS_DE *e, *found = NULL; - NTFS_CMP_FUNC cmp = indx->cmp; int min_idx = 0, mid_idx, max_idx = 0; int diff2; int table_size = 8; @@ -727,9 +762,6 @@ 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; @@ -757,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) { @@ -800,7 +836,8 @@ binary_search: static struct NTFS_DE *hdr_insert_de(const struct ntfs_index *indx, struct INDEX_HDR *hdr, const struct NTFS_DE *de, - struct NTFS_DE *before, const void *ctx) + struct NTFS_DE *before, const void *ctx, + NTFS_CMP_FUNC cmp) { int diff; size_t off = PtrOffset(hdr, before); @@ -823,7 +860,7 @@ static struct NTFS_DE *hdr_insert_de(const struct ntfs_index *indx, } /* No insert point is applied. Get it manually. */ before = hdr_find_e(indx, hdr, de + 1, le16_to_cpu(de->key_size), ctx, - &diff); + &diff, cmp); if (!before) return NULL; off = PtrOffset(hdr, before); @@ -915,10 +952,6 @@ int indx_init(struct ntfs_index *indx, struct ntfs_sb_info *sbi, init_rwsem(&indx->run_lock); - indx->cmp = get_cmp_func(root); - if (!indx->cmp) - goto out; - return 0; out: @@ -1141,6 +1174,7 @@ int indx_find(struct ntfs_index *indx, struct ntfs_inode *ni, int err; struct NTFS_DE *e; struct indx_node *node; + NTFS_CMP_FUNC cmp; if (!root) root = indx_get_root(&ni->dir, ni, NULL, NULL); @@ -1150,10 +1184,16 @@ int indx_find(struct ntfs_index *indx, struct ntfs_inode *ni, return -EINVAL; } + cmp = get_cmp_func(root); + if (unlikely(!cmp)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + /* Check cache. */ e = fnd->level ? fnd->de[fnd->level - 1] : fnd->root_de; if (e && !de_is_last(e) && - !(*indx->cmp)(key, key_len, e + 1, le16_to_cpu(e->key_size), ctx)) { + !(*cmp)(key, key_len, e + 1, le16_to_cpu(e->key_size), ctx)) { *entry = e; *diff = 0; return 0; @@ -1163,7 +1203,7 @@ int indx_find(struct ntfs_index *indx, struct ntfs_inode *ni, fnd_clear(fnd); /* Lookup entry that is <= to the search value. */ - e = hdr_find_e(indx, &root->ihdr, key, key_len, ctx, diff); + e = hdr_find_e(indx, &root->ihdr, key, key_len, ctx, diff, cmp); if (!e) return -EINVAL; @@ -1183,7 +1223,7 @@ int indx_find(struct ntfs_index *indx, struct ntfs_inode *ni, /* Lookup entry that is <= to the search value. */ e = hdr_find_e(indx, &node->index->ihdr, key, key_len, ctx, - diff); + diff, cmp); if (!e) { put_indx_node(node); return -EINVAL; @@ -1285,6 +1325,7 @@ pop_level: /* Pop one level. */ if (n) { fnd_pop(fnd); + kfree(n->index); kfree(n); } @@ -1481,6 +1522,7 @@ out1: run_deallocate(sbi, &run, false); out: + run_close(&run); return err; } @@ -1504,6 +1546,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); @@ -1585,7 +1628,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) + struct ntfs_fnd *fnd, bool undo, + NTFS_CMP_FUNC cmp) { int err = 0; struct NTFS_DE *e, *e0, *re; @@ -1626,7 +1670,7 @@ static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni, if ((undo || asize + ds_root < sbi->max_bytes_per_attr) && mi_resize_attr(mi, attr, ds_root)) { hdr->total = cpu_to_le32(hdr_total + ds_root); - e = hdr_insert_de(indx, hdr, new_de, root_de, ctx); + e = hdr_insert_de(indx, hdr, new_de, root_de, ctx, cmp); WARN_ON(!e); fnd_clear(fnd); fnd->root_de = e; @@ -1740,6 +1784,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); @@ -1767,7 +1827,7 @@ static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni, * Now root is a parent for new index buffer. * Insert NewEntry a new buffer. */ - e = hdr_insert_de(indx, hdr, new_de, NULL, ctx); + e = hdr_insert_de(indx, hdr, new_de, NULL, ctx, cmp); if (!e) { err = -EINVAL; goto out_put_n; @@ -1794,13 +1854,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) +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]; @@ -1814,7 +1876,7 @@ indx_insert_into_buffer(struct ntfs_index *indx, struct ntfs_inode *ni, /* Try the most easy case. */ e = fnd->level - 1 == level ? fnd->de[level] : NULL; - e = hdr_insert_de(indx, hdr1, new_de, e, ctx); + e = hdr_insert_de(indx, hdr1, new_de, e, ctx, cmp); fnd->de[level] = e; if (e) { /* Just write updated index into disk. */ @@ -1826,10 +1888,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 */ @@ -1843,7 +1904,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; @@ -1891,12 +1967,11 @@ indx_insert_into_buffer(struct ntfs_index *indx, struct ntfs_inode *ni, * (depending on sp <=> new_de). */ hdr_insert_de(indx, - (*indx->cmp)(new_de + 1, le16_to_cpu(new_de->key_size), - up_e + 1, le16_to_cpu(up_e->key_size), - ctx) < 0 ? + (*cmp)(new_de + 1, le16_to_cpu(new_de->key_size), + up_e + 1, le16_to_cpu(up_e->key_size), ctx) < 0 ? hdr2 : hdr1, - new_de, NULL, ctx); + new_de, NULL, ctx, cmp); indx_mark_used(indx, ni, new_vbn >> indx->idx2vbn_bits); @@ -1910,15 +1985,17 @@ 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); + /* 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, - level - 1, fnd); + level - 1, fnd, cmp); } if (err) { @@ -1952,6 +2029,7 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni, struct NTFS_DE *e; struct ntfs_fnd *fnd_a = NULL; struct INDEX_ROOT *root; + NTFS_CMP_FUNC cmp; if (!fnd) { fnd_a = fnd_get(); @@ -1968,6 +2046,12 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni, goto out; } + cmp = get_cmp_func(root); + if (unlikely(!cmp)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + if (fnd_is_empty(fnd)) { /* * Find the spot the tree where we want to @@ -1991,13 +2075,13 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni, * new entry into it. */ err = indx_insert_into_root(indx, ni, new_de, fnd->root_de, ctx, - fnd, undo); + fnd, undo, cmp); } else { /* * Found a leaf buffer, so we'll insert the new entry into it. */ err = indx_insert_into_buffer(indx, ni, root, new_de, ctx, - fnd->level - 1, fnd); + fnd->level - 1, fnd, cmp); } indx->version += 1; @@ -2013,13 +2097,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) @@ -2040,7 +2132,7 @@ 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; } @@ -2291,6 +2383,7 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni, u32 e_size, root_size, new_root_size; size_t trim_bit; const struct INDEX_NAMES *in; + NTFS_CMP_FUNC cmp; fnd = fnd_get(); if (!fnd) { @@ -2310,6 +2403,12 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni, goto out; } + cmp = get_cmp_func(root); + if (unlikely(!cmp)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + /* Locate the entry to remove. */ err = indx_find(indx, ni, root, key, key_len, ctx, &diff, &e, fnd); if (err) @@ -2376,9 +2475,9 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni, err = level ? indx_insert_into_buffer(indx, ni, root, re, ctx, fnd->level - 1, - fnd) : + fnd, cmp) : indx_insert_into_root(indx, ni, re, e, - ctx, fnd, 0); + ctx, fnd, 0, cmp); kfree(re); if (err) @@ -2446,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; @@ -2673,6 +2772,7 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi, struct INDEX_ROOT *root; struct mft_inode *mi; struct ntfs_index *indx = &ni->dir; + NTFS_CMP_FUNC cmp; fnd = fnd_get(); if (!fnd) @@ -2684,6 +2784,12 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi, goto out; } + cmp = get_cmp_func(root); + if (unlikely(!cmp)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + /* Find entry in directory. */ err = indx_find(indx, ni, root, fname, fname_full_size(fname), sbi, &diff, &e, fnd); diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 6e65066ebcc1..4ac26c80bd34 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -18,12 +18,16 @@ #include "ntfs.h" #include "ntfs_fs.h" +struct IGET5_PARAM { + const struct MFT_REF *ref; + const struct cpu_str *name; +}; + /* * ntfs_read_mft - Read record and parse MFT. */ -static struct inode *ntfs_read_mft(struct inode *inode, - const struct cpu_str *name, - const struct MFT_REF *ref) +static int ntfs_read_mft(struct inode *inode, const struct cpu_str *name, + const struct MFT_REF *ref) { int err = 0; struct ntfs_inode *ni = ntfs_i(inode); @@ -36,7 +40,7 @@ static struct inode *ntfs_read_mft(struct inode *inode, bool is_match = false; bool is_root = false; bool is_dir; - unsigned long ino = inode->i_ino; + u64 ino = inode->i_ino; u32 rp_fa = 0, asize, t32; u16 roff, rsize, names = 0, links = 0; const struct ATTR_FILE_NAME *fname = NULL; @@ -46,6 +50,7 @@ static struct inode *ntfs_read_mft(struct inode *inode, struct MFT_REC *rec; struct runs_tree *run; struct timespec64 ts; + const __le16 *aname; inode->i_op = NULL; /* Setup 'uid' and 'gid' */ @@ -79,7 +84,7 @@ static struct inode *ntfs_read_mft(struct inode *inode, ; } else if (ref->seq != rec->seq) { err = -EINVAL; - ntfs_err(sb, "MFT: r=%lx, expect seq=%x instead of %x!", ino, + ntfs_err(sb, "MFT: r=%llx, expect seq=%x instead of %x!", ino, le16_to_cpu(ref->seq), le16_to_cpu(rec->seq)); goto out; } else if (!is_rec_inuse(rec)) { @@ -127,10 +132,16 @@ next_attr: if (le && le->vcn) { /* This is non primary attribute segment. Ignore if not MFT. */ - if (ino != MFT_REC_MFT || attr->type != ATTR_DATA) + if (ino != MFT_REC_MFT) + goto next_attr; + + if (attr->type == ATTR_DATA) + run = &ni->file.run; + else if (attr->type == ATTR_BITMAP) + run = &sbi->mft.bitmap.run; + else goto next_attr; - run = &ni->file.run; asize = le32_to_cpu(attr->size); goto attr_unpack_run; } @@ -138,6 +149,7 @@ next_attr: roff = attr->non_res ? 0 : le16_to_cpu(attr->res.data_off); rsize = attr->non_res ? 0 : le32_to_cpu(attr->res.data_size); asize = le32_to_cpu(attr->size); + aname = attr_name(attr); /* * Really this check was done in 'ni_enum_attr_ex' -> ... 'mi_enum_attr'. @@ -224,10 +236,10 @@ next_attr: if (attr->name_len && ((ino != MFT_REC_BADCLUST || !attr->non_res || attr->name_len != ARRAY_SIZE(BAD_NAME) || - memcmp(attr_name(attr), BAD_NAME, sizeof(BAD_NAME))) && + memcmp(aname, BAD_NAME, sizeof(BAD_NAME))) && (ino != MFT_REC_SECURE || !attr->non_res || attr->name_len != ARRAY_SIZE(SDS_NAME) || - memcmp(attr_name(attr), SDS_NAME, sizeof(SDS_NAME))))) { + memcmp(aname, SDS_NAME, sizeof(SDS_NAME))))) { /* File contains stream attribute. Ignore it. */ goto next_attr; } @@ -247,14 +259,11 @@ next_attr: else ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED; - if (!attr->non_res) { - ni->i_valid = inode->i_size = rsize; - inode_set_bytes(inode, rsize); - } - mode = S_IFREG | (0777 & sbi->options->fs_fmask_inv); if (!attr->non_res) { + ni->i_valid = inode->i_size = rsize; + inode_set_bytes(inode, rsize); ni->ni_flags |= NI_FLAG_RESIDENT; goto next_attr; } @@ -432,6 +441,11 @@ end_enum: ni->mi.dirty = true; } + if (!links) { + err = -EINVAL; + goto out; + } + set_nlink(inode, links); if (S_ISDIR(mode)) { @@ -443,9 +457,7 @@ end_enum: * Usually a hard links to directories are disabled. */ inode->i_op = &ntfs_dir_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_dir_operations : - &ntfs_dir_operations; + inode->i_fop = &ntfs_dir_operations; ni->i_valid = 0; } else if (S_ISLNK(mode)) { ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY; @@ -455,9 +467,7 @@ end_enum: } else if (S_ISREG(mode)) { ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY; inode->i_op = &ntfs_file_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_file_operations : - &ntfs_file_operations; + inode->i_fop = &ntfs_file_operations; inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr : &ntfs_aops; if (ino != MFT_REC_MFT) @@ -494,16 +504,136 @@ end_enum: if (ino == MFT_REC_MFT && !sb->s_root) sbi->mft.ni = NULL; - unlock_new_inode(inode); - - return inode; + return 0; out: if (ino == MFT_REC_MFT && !sb->s_root) sbi->mft.ni = NULL; - iget_failed(inode); - return ERR_PTR(err); + return err; +} + +/* + * ntfs_init_ads_node + * + * This function scans base inode for given ADS. + * And init inode associated with this ADS + */ +static int ntfs_init_ads_node(struct inode *inode, const __le16 *ads_name, + u8 ads_len, u32 flags) +{ + int err = -EINVAL; + struct ntfs_inode *ni = ntfs_i(inode); + struct ntfs_inode *nb = ni->base; + struct ntfs_sb_info *sbi = nb->mi.sbi; + struct ATTR_LIST_ENTRY *le = NULL; + struct ATTRIB *attr = NULL; + u16 roff, asize; + u64 svcn; + + if (nb->ni_flags & NI_FLAG_DIR) + return -EINVAL; /* no ADS for directories. */ + + ni->mi.sbi = sbi; + ni->mi.rno = inode->i_ino; + + if (ads_len == ARRAY_SIZE(QUERY_STREAMS) && + !memcmp(ads_name, QUERY_STREAMS, sizeof(QUERY_STREAMS))) { + goto ok; /* use goto to reduce tab pressure. */ + } + + /* Enumerate all attributes in record. */ + while ((attr = ni_enum_attr_ex(nb, attr, &le, NULL))) { + if (attr->type == ATTR_DATA && attr->name_len && + ads_len == attr->name_len && + !memcmp(ads_name, attr_name(attr), ads_len * sizeof(u16))) { + /* We have found the ADS to open. */ + break; + } + } + + if (!attr) { + if (!(flags & LOOKUP_CREATE)) { + /* Do not create ADS. */ + return -ENOENT; + } + + /* Create new ADS. */ + err = ni_insert_resident(nb, 0, ATTR_DATA, ads_name, ads_len, + &attr, NULL, NULL); + if (err) { + /* Looks like the only reasons: ENOSPC/ENOMEM .*/ + return err; + } + } + + if (is_attr_sparsed(attr)) + ni->std_fa |= FILE_ATTRIBUTE_SPARSE_FILE; + else + ni->std_fa &= ~FILE_ATTRIBUTE_SPARSE_FILE; + + if (is_attr_compressed(attr)) + ni->std_fa |= FILE_ATTRIBUTE_COMPRESSED; + else + ni->std_fa &= ~FILE_ATTRIBUTE_COMPRESSED; + + if (is_attr_encrypted(attr)) + ni->std_fa |= FILE_ATTRIBUTE_ENCRYPTED; + else + ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED; + + if (!attr->non_res) { + ni->ni_flags |= NI_FLAG_RESIDENT; + ni->i_valid = inode->i_size = le32_to_cpu(attr->res.data_size); + inode_set_bytes(inode, inode->i_size); + goto ok; + } + + inode_set_bytes(inode, attr_ondisk_size(attr)); + ni->i_valid = le64_to_cpu(attr->nres.valid_size); + inode->i_size = le64_to_cpu(attr->nres.data_size); + + if (!attr->nres.alloc_size) + goto ok; + + roff = le16_to_cpu(attr->nres.run_off); + asize = le32_to_cpu(attr->size); + + if (roff > asize) { + /* This case should be checked in mi_enum_attr */ + return -EINVAL; + } + + svcn = le64_to_cpu(attr->nres.svcn); + err = run_unpack_ex(&ni->file.run, sbi, ni->mi.rno, svcn, + le64_to_cpu(attr->nres.evcn), svcn, + Add2Ptr(attr, roff), asize - roff); + if (err < 0) { + /* run_unpack_ex marks volume dirty, if logical error. */ + return err; + } + +ok: + /* Keep ADS name (little endian). */ + ni->file.ads.name = kmemdup(ads_name, ads_len * sizeof(u16), GFP_NOFS); + if (!ni->file.ads.name) + return -ENOMEM; + ni->file.ads.len = ads_len; + + set_nlink(inode, 1); + + init_rwsem(&ni->file.run_lock); + /* Most fields are the same as the base's? */ + inode->i_op = nb->vfs_inode.i_op; + inode->i_fop = nb->vfs_inode.i_fop; + inode->i_mapping->a_ops = nb->vfs_inode.i_mapping->a_ops; + inode->i_flags = nb->vfs_inode.i_flags; + inode->i_mode = nb->vfs_inode.i_mode; + inode->i_uid = nb->vfs_inode.i_uid; + inode->i_gid = nb->vfs_inode.i_gid; + inode->i_generation = nb->vfs_inode.i_generation; + + return 0; } /* @@ -513,44 +643,120 @@ out: */ static int ntfs_test_inode(struct inode *inode, void *data) { - struct MFT_REF *ref = data; + const struct IGET5_PARAM *ig5 = data; + struct ntfs_inode *ni; + const struct cpu_str *name; + + if (ino_get(ig5->ref) != inode->i_ino) + return 0; + + ni = ntfs_i(inode); + + if (ni->ni_flags & NI_FLAG_DIR) { + /* No ads for directories. */ + return 1; + } + + name = ig5->name; + if (!name || !name->ads_len) { + if (!ni->file.ads.len) { + /* default file (not ads) match. */ + return 1; + } + } else if (ni->file.ads.len == name->ads_len && + !memcmp(ni->file.ads.name, &name->name[name->len + 1], + name->ads_len * sizeof(u16))) { + /* ads name match. */ + return 1; + } - return ino_get(ref) == inode->i_ino; + return 0; } static int ntfs_set_inode(struct inode *inode, void *data) { - const struct MFT_REF *ref = data; + const struct IGET5_PARAM *ig5 = data; - inode->i_ino = ino_get(ref); + inode->i_ino = ino_get(ig5->ref); return 0; } -struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref, - const struct cpu_str *name) +struct inode *ntfs_iget5_flags(struct super_block *sb, + const struct MFT_REF *ref, + const struct cpu_str *name, u32 flags) { - struct inode *inode; + int err; + /* Pack params to pass in iget5_locked. */ + struct IGET5_PARAM ig5 = { ref, name }; + u64 ino = ino_get(ref); + struct inode *inode, *base = NULL; + bool ads = name && name->ads_len; + struct ntfs_inode *ni; + + if (ads) { + /* First get base inode */ + base = ntfs_iget5_flags(sb, ref, NULL, 0); + if (IS_ERR(base)) + return base; + } + + inode = iget5_locked(sb, ino, ntfs_test_inode, ntfs_set_inode, &ig5); + if (unlikely(!inode)) { + err = -ENOMEM; + goto out; + } - inode = iget5_locked(sb, ino_get(ref), ntfs_test_inode, ntfs_set_inode, - (void *)ref); - if (unlikely(!inode)) - return ERR_PTR(-ENOMEM); + ni = ntfs_i(inode); /* If this is a freshly allocated inode, need to read it now. */ - if (inode_state_read_once(inode) & I_NEW) - inode = ntfs_read_mft(inode, name, ref); - else if (ref->seq != ntfs_i(inode)->mi.mrec->seq) { + if (inode_state_read_once(inode) & I_NEW) { + if (!base) { + /* default inode. generic file/dir. */ + ni->base = ni; + } else { + /* inode + ads */ + ni->base = ntfs_i(base); + base = NULL; /* keep reference incremented (instead of ihold). */ + } + + if (ads) { + /* base record is loaded. Init ads node. */ + err = ntfs_init_ads_node( + inode, (__le16 *)&name->name[name->len + 1], + name->ads_len, flags); + } else { + err = ntfs_read_mft(inode, name, ref); + } + + if (!err) { + unlock_new_inode(inode); + } else { + iget_failed(inode); + /* Do not mark volume dirty if ADS not found. */ + if (ads) + goto out; + } + } else if (!ads && ref->seq != ni->mi.mrec->seq) { /* * Sequence number is not expected. * Looks like inode was reused but caller uses the old reference */ iput(inode); - inode = ERR_PTR(-ESTALE); + err = -ESTALE; + } else { + err = 0; } - if (IS_ERR(inode)) + if (err) ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR); +out: + if (base) + iput(base); + + if (err) + return ERR_PTR(err); + return inode; } @@ -591,7 +797,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 : @@ -605,64 +810,18 @@ static void ntfs_iomap_read_end_io(struct bio *bio) bio_put(bio); } -/* - * Copied from iomap/bio.c. - */ -static int ntfs_iomap_bio_read_folio_range(const struct iomap_iter *iter, - struct iomap_read_folio_ctx *ctx, - size_t plen) -{ - struct folio *folio = ctx->cur_folio; - const struct iomap *iomap = &iter->iomap; - loff_t pos = iter->pos; - size_t poff = offset_in_folio(folio, pos); - loff_t length = iomap_length(iter); - sector_t sector; - struct bio *bio = ctx->read_ctx; - - sector = iomap_sector(iomap, pos); - if (!bio || bio_end_sector(bio) != sector || - !bio_add_folio(bio, folio, plen, poff)) { - gfp_t gfp = mapping_gfp_constraint(folio->mapping, GFP_KERNEL); - gfp_t orig_gfp = gfp; - unsigned int nr_vecs = DIV_ROUND_UP(length, PAGE_SIZE); - - if (bio) - submit_bio(bio); - - if (ctx->rac) /* same as readahead_gfp_mask */ - gfp |= __GFP_NORETRY | __GFP_NOWARN; - bio = bio_alloc(iomap->bdev, bio_max_segs(nr_vecs), REQ_OP_READ, - gfp); - /* - * If the bio_alloc fails, try it again for a single page to - * avoid having to deal with partial page reads. This emulates - * what do_mpage_read_folio does. - */ - if (!bio) - bio = bio_alloc(iomap->bdev, 1, REQ_OP_READ, orig_gfp); - if (ctx->rac) - bio->bi_opf |= REQ_RAHEAD; - bio->bi_iter.bi_sector = sector; - bio->bi_end_io = ntfs_iomap_read_end_io; - bio_add_folio_nofail(bio, folio, plen, poff); - ctx->read_ctx = bio; - } - return 0; -} - -static void ntfs_iomap_bio_submit_read(struct iomap_read_folio_ctx *ctx) +static void ntfs_iomap_bio_submit_read(const struct iomap_iter *iter, + struct iomap_read_folio_ctx *ctx) { - struct bio *bio = ctx->read_ctx; - - if (bio) - submit_bio(bio); + iomap_bio_submit_read_endio(iter, ctx, ntfs_iomap_read_end_io); } +// clang-format off static const struct iomap_read_ops ntfs_iomap_bio_read_ops = { - .read_folio_range = ntfs_iomap_bio_read_folio_range, - .submit_read = ntfs_iomap_bio_submit_read, + .read_folio_range = iomap_bio_read_folio_range, + .submit_read = ntfs_iomap_bio_submit_read, }; +// clang-format on static int ntfs_read_folio(struct file *file, struct folio *folio) { @@ -737,16 +896,17 @@ 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; - 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); - } + /* last 'true' means keep preallocated. */ + err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name, ni->file.ads.len, + &ni->file.run, new_size, &ni->i_valid, true); up_write(&ni->file.run_lock); ni_unlock(ni); @@ -808,7 +968,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. @@ -840,7 +1000,8 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, if (lcn == RESIDENT_LCN) { if (offset >= clen) { - kfree(res); + if (res) + __free_page(virt_to_page(res)); if (flags & IOMAP_REPORT) { /* special code for report. */ return -ENOENT; @@ -873,7 +1034,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) @@ -932,7 +1092,8 @@ static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length, struct ATTRIB *attr; struct mft_inode *mi; - attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, + attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, NULL, &mi); if (!attr || attr->non_res) { err = -EINVAL; @@ -965,7 +1126,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; } @@ -1030,37 +1191,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) { @@ -1068,7 +1203,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, }; @@ -1082,9 +1217,22 @@ static int ntfs_writepages(struct address_space *mapping, if (is_resident(ni)) { struct folio *folio = NULL; + err = 0; - while ((folio = writeback_iter(mapping, wbc, folio, &err))) - err = ntfs_resident_writepage(folio, wbc); + while ((folio = writeback_iter(mapping, wbc, folio, &err))) { + int err2; + + ni_lock(ni); + err2 = attr_data_write_resident(ni, folio); + ni_unlock(ni); + + folio_unlock(folio); + if (err2) { + mapping_set_error(mapping, err2); + if (!err) + err = err2; + } + } return err; } @@ -1267,6 +1415,15 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, if (!fnd) ni_lock_dir(dir_ni); + if (sbi->options->ads) { + const char *ads = strchr(name->name + 1, ':'); + if (ads && ads[1]) { + ntfs_warn(sb, "failed to create ads"); + err = -EINVAL; + goto out1; + } + } + dir_root = indx_get_root(&dir_ni->dir, dir_ni, NULL, NULL); if (!dir_root) { err = -EINVAL; @@ -1335,7 +1492,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; @@ -1646,9 +1802,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, if (S_ISDIR(mode)) { inode->i_op = &ntfs_dir_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_dir_operations : - &ntfs_dir_operations; + inode->i_fop = &ntfs_dir_operations; } else if (S_ISLNK(mode)) { inode->i_op = &ntfs_link_inode_operations; inode->i_fop = NULL; @@ -1657,9 +1811,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, inode_nohighmem(inode); } else if (S_ISREG(mode)) { inode->i_op = &ntfs_file_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_file_operations : - &ntfs_file_operations; + inode->i_fop = &ntfs_file_operations; inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr : &ntfs_aops; init_rwsem(&ni->file.run_lock); @@ -1714,10 +1866,10 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, goto out6; /* - * Call 'd_instantiate' after inode->i_op is set + * Call 'd_instantiate_new' after inode->i_op is set * but before finish_open. */ - d_instantiate(dentry, inode); + d_instantiate_new(dentry, inode); /* Set original time. inode times (i_ctime) may be changed in ntfs_init_acl. */ inode_set_atime_to_ts(inode, ni->i_crtime); @@ -1765,9 +1917,6 @@ out1: if (!fnd) ni_unlock(dir_ni); - if (!err) - unlock_new_inode(inode); - return err; } @@ -1778,7 +1927,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; @@ -1860,7 +2008,6 @@ void ntfs_evict_inode(struct inode *inode) { truncate_inode_pages_final(&inode->i_data); - invalidate_inode_buffers(inode); clear_inode(inode); ni_clear(ntfs_i(inode)); @@ -2144,6 +2291,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 = { @@ -2164,9 +2313,11 @@ const struct address_space_operations ntfs_aops_cmpr = { .invalidate_folio = iomap_invalidate_folio, }; +static DEFINE_IOMAP_ITER_NEXT_END(ntfs_iomap_next, ntfs_iomap_begin, + ntfs_iomap_end); + const struct iomap_ops ntfs_iomap_ops = { - .iomap_begin = ntfs_iomap_begin, - .iomap_end = ntfs_iomap_end, + .iomap_next = ntfs_iomap_next, }; const struct iomap_write_ops ntfs_iomap_folio_ops = { diff --git a/fs/ntfs3/lznt.c b/fs/ntfs3/lznt.c index fdc9b2ebf341..5dcb7674790c 100644 --- a/fs/ntfs3/lznt.c +++ b/fs/ntfs3/lznt.c @@ -240,8 +240,10 @@ 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. */ if (!(ch & (1 << bit))) { diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index b2af8f695e60..ec59bbabd3c5 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -22,7 +22,7 @@ int fill_name_de(struct ntfs_sb_info *sbi, void *buf, const struct qstr *name, { int err; struct NTFS_DE *e = buf; - u16 data_size; + u16 data_size, real_size, aligned_size; struct ATTR_FILE_NAME *fname = (struct ATTR_FILE_NAME *)(e + 1); #ifndef CONFIG_NTFS3_64BIT_CLUSTER @@ -53,7 +53,12 @@ int fill_name_de(struct ntfs_sb_info *sbi, void *buf, const struct qstr *name, fname->type = FILE_NAME_POSIX; data_size = fname_full_size(fname); - e->size = cpu_to_le16(ALIGN(data_size, 8) + sizeof(struct NTFS_DE)); + 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); + + e->size = cpu_to_le16(aligned_size); e->key_size = cpu_to_le16(data_size); e->flags = 0; e->res = 0; @@ -73,21 +78,23 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, int err; if (!uni) - inode = ERR_PTR(-ENOMEM); - else { - err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name, - dentry->d_name.len, uni, NTFS_NAME_LEN, - UTF16_HOST_ENDIAN); - if (err < 0) - inode = ERR_PTR(err); - else { - ni_lock_dir(ni); - inode = dir_search_u(dir, uni, NULL); - ni_unlock(ni); - } + return ERR_PTR(-ENOMEM); + + err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name, + dentry->d_name.len, uni, NTFS_NAME_LEN, + UTF16_HOST_ENDIAN); + + if (err < 0) { kfree(uni); + return ERR_PTR(err); } + ni_lock_dir(ni); + inode = dir_search_flags(dir, uni, NULL, flags); + ni_unlock(ni); + + kfree(uni); + /* * Check for a null pointer * If the MFT record of ntfs inode is not a base record, inode->i_op can be NULL. @@ -95,7 +102,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, */ if (!IS_ERR_OR_NULL(inode) && !inode->i_op) { iput(inode); - inode = ERR_PTR(-EINVAL); + return ERR_PTR(-EINVAL); } return d_splice_alias(inode, dentry); @@ -105,7 +112,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, * ntfs_create - inode_operations::create */ static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ntfs_create_inode(idmap, dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, NULL); @@ -168,7 +175,9 @@ static int ntfs_link(struct dentry *ode, struct inode *dir, struct dentry *de) */ static int ntfs_unlink(struct inode *dir, struct dentry *dentry) { - struct ntfs_inode *ni = ntfs_i(dir); + struct ntfs_inode *dir_ni = ntfs_i(dir); + struct inode *inode = d_inode(dentry); + struct ntfs_inode *ni = ntfs_i(inode); int err; /* Avoid any operation if inode is bad. */ @@ -178,11 +187,21 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry) if (unlikely(ntfs3_forced_shutdown(dir->i_sb))) return -EIO; - ni_lock_dir(ni); - - err = ntfs_unlink_inode(dir, dentry); + if (likely(is_ni_base(ni))) { + ni_lock_dir(dir_ni); + /* Remove general file/dir. */ + err = ntfs_unlink_inode(dir, dentry); + ni_unlock(dir_ni); + } else { + ni_lock(ni); + /* Remove ADS. */ + err = ni_remove_attr(ni, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, false, NULL); + ni_unlock(ni); - ni_unlock(ni); + if (!err) + drop_nlink(inode); + } return err; } @@ -213,7 +232,7 @@ static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, - S_IFDIR | mode, 0, NULL, 0, NULL)); + mode, 0, NULL, 0, NULL)); } /* @@ -268,6 +287,11 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir, 1024); static_assert(PATH_MAX >= 4 * 1024); + if (!is_ni_base(ni)) { + /* No rename for ADS. */ + return -EOPNOTSUPP; + } + /* Avoid any operation if inode is bad. */ if (unlikely(is_bad_ni(ni))) return -EINVAL; @@ -340,7 +364,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) @@ -518,6 +542,8 @@ const struct inode_operations ntfs_dir_inode_operations = { .getattr = ntfs_getattr, .listxattr = ntfs_listxattr, .fiemap = ntfs_fiemap, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, }; const struct inode_operations ntfs_special_inode_operations = { @@ -526,6 +552,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.h b/fs/ntfs3/ntfs.h index 892f13e65d42..4589b16329c9 100644 --- a/fs/ntfs3/ntfs.h +++ b/fs/ntfs3/ntfs.h @@ -58,7 +58,7 @@ struct GUID { */ struct cpu_str { u8 len; - u8 unused; + u8 ads_len; u16 name[]; }; @@ -170,6 +170,7 @@ extern const __le16 SDH_NAME[4]; extern const __le16 SO_NAME[2]; extern const __le16 SQ_NAME[2]; extern const __le16 SR_NAME[2]; +extern const __le16 QUERY_STREAMS[13]; extern const __le16 BAD_NAME[4]; extern const __le16 SDS_NAME[4]; diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index daf5a1f47275..5811d89d67b3 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -110,6 +110,7 @@ struct ntfs_mount_options { unsigned prealloc : 1; /* Preallocate space when file is growing. */ unsigned nocase : 1; /* case insensitive. */ unsigned delalloc : 1; /* delay allocation. */ + unsigned ads : 1; /* ads support. */ }; /* Special value to unpack and deallocate. */ @@ -196,9 +197,6 @@ struct ntfs_index { struct rw_semaphore run_lock; size_t version; /* increment each change */ - /*TODO: Remove 'cmp'. */ - NTFS_CMP_FUNC cmp; - u8 index_bits; // log2(root->index_block_size) u8 idx2vbn_bits; // log2(root->index_block_clst) u8 vbn2vbo_bits; // index_block_size < cluster? 9 : cluster_bits @@ -395,13 +393,16 @@ struct ntfs_inode { */ u8 ni_bad; + /* Keep track of FS_NODUMP_FL. */ + u8 nodump; + union { struct ntfs_index dir; struct { struct rw_semaphore run_lock; /* Unpacked runs from just one record. */ struct runs_tree run; - /* + /* * Pairs [vcn, len] for all delay allocated clusters. * Normal file always contains delayed clusters in one fragment. * TODO: use 2 CLST per pair instead of 3. @@ -410,6 +411,11 @@ struct ntfs_inode { #ifdef CONFIG_NTFS3_LZX_XPRESS struct folio *offs_folio; #endif + /* Alternative data stream */ + struct { + __le16 *name; + u8 len; + } ads; } file; }; @@ -421,6 +427,7 @@ struct ntfs_inode { } attr_list; size_t ni_flags; // NI_FLAG_XXX + struct ntfs_inode *base; /* ADS: points to base inode. Other: this. */ struct inode vfs_inode; }; @@ -444,6 +451,11 @@ enum REPARSE_SIGN { REPARSE_LINK = 3 }; +static inline bool is_ni_base(const struct ntfs_inode *ni) +{ + return ni == ni->base; +} + /* Functions from attrib.c */ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run, struct runs_tree *run_da, CLST vcn, CLST lcn, @@ -526,13 +538,21 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len, int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len, struct cpu_str *uni, u32 max_ulen, enum utf16_endian endian); -struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni, - struct ntfs_fnd *fnd); +struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni, + struct ntfs_fnd *fnd, u32 flags); +static inline struct inode *dir_search(struct inode *dir, + const struct cpu_str *uni) +{ + return dir_search_flags(dir, uni, NULL, 0); +} + bool dir_is_empty(struct inode *dir); extern const struct file_operations ntfs_dir_operations; -extern const struct file_operations ntfs_legacy_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, @@ -546,7 +566,6 @@ long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg); extern const struct inode_operations ntfs_special_inode_operations; extern const struct inode_operations ntfs_file_inode_operations; extern const struct file_operations ntfs_file_operations; -extern const struct file_operations ntfs_legacy_file_operations; /* Globals from frecord.c */ void ni_remove_mi(struct ntfs_inode *ni, struct mft_inode *mi); @@ -621,6 +640,7 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data); int ni_write_parents(struct ntfs_inode *ni, int sync); int ni_allocate_da_blocks(struct ntfs_inode *ni); int ni_allocate_da_blocks_locked(struct ntfs_inode *ni); +ssize_t ni_query_ads(struct ntfs_inode *ni, loff_t *pos, struct iov_iter *iter); /* Globals from fslog.c */ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes); @@ -678,7 +698,6 @@ static inline int ntfs_read_bh(struct ntfs_sb_info *sbi, { return ntfs_read_bh_ra(sbi, run, vbo, rhdr, bytes, nb, NULL); } - int ntfs_get_bh(struct ntfs_sb_info *sbi, const struct runs_tree *run, u64 vbo, u32 bytes, struct ntfs_buffers *nb); int ntfs_write_bh(struct ntfs_sb_info *sbi, struct NTFS_RECORD_HEADER *rhdr, @@ -771,8 +790,15 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi, const struct NTFS_DUP_INFO *dup, int sync); /* Globals from inode.c */ -struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref, - const struct cpu_str *name); +struct inode *ntfs_iget5_flags(struct super_block *sb, + const struct MFT_REF *ref, + const struct cpu_str *name, u32 flags); +static inline struct inode *ntfs_iget5(struct super_block *sb, + const struct MFT_REF *ref, + const struct cpu_str *name) +{ + return ntfs_iget5_flags(sb, ref, name, 0); +} int ntfs_set_size(struct inode *inode, u64 new_size); int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc); int ntfs_sync_inode(struct inode *inode); @@ -857,6 +883,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); @@ -882,7 +911,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); @@ -1214,27 +1244,27 @@ static inline void mi_clear(struct mft_inode *mi) static inline void ni_lock(struct ntfs_inode *ni) { - mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_NORMAL); } static inline void ni_lock_dir(struct ntfs_inode *ni) { - mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_PARENT); + mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_PARENT); } static inline void ni_lock_dir2(struct ntfs_inode *ni) { - mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_PARENT2); + mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_PARENT2); } static inline void ni_unlock(struct ntfs_inode *ni) { - mutex_unlock(&ni->ni_lock); + mutex_unlock(&ni->base->ni_lock); } static inline int ni_trylock(struct ntfs_inode *ni) { - return mutex_trylock(&ni->ni_lock); + return mutex_trylock(&ni->base->ni_lock); } static inline int attr_load_runs_attr(struct ntfs_inode *ni, @@ -1250,13 +1280,4 @@ static inline void le64_sub_cpu(__le64 *var, u64 val) *var = cpu_to_le64(le64_to_cpu(*var) - val); } -#if IS_ENABLED(CONFIG_NTFS_FS) -bool is_legacy_ntfs(struct super_block *sb); -#else -static inline bool is_legacy_ntfs(struct super_block *sb) -{ - return false; -} -#endif - #endif /* _LINUX_NTFS3_NTFS_FS_H */ diff --git a/fs/ntfs3/record.c b/fs/ntfs3/record.c index 32bdb034c2a3..4f12ce15b03b 100644 --- a/fs/ntfs3/record.c +++ b/fs/ntfs3/record.c @@ -202,7 +202,7 @@ struct ATTRIB *mi_enum_attr(struct ntfs_inode *ni, struct mft_inode *mi, u32 used = le32_to_cpu(rec->used); u32 t32, off, asize, prev_type; u16 t16; - u64 data_size, alloc_size, tot_size; + u64 svcn, evcn, data_size, alloc_size, tot_size; if (!attr) { u32 total = le32_to_cpu(rec->total); @@ -310,10 +310,38 @@ struct ATTRIB *mi_enum_attr(struct ntfs_inode *ni, struct mft_inode *mi, if (t32 && le16_to_cpu(attr->name_off) + t32 > t16) goto out; - /* Check start/end vcn. */ - if (le64_to_cpu(attr->nres.svcn) > le64_to_cpu(attr->nres.evcn) + 1) + /* + * Check start/end vcn. svcn == 0 with evcn == -1 (U64_MAX) is the + * sentinel for an empty non-resident attribute (no allocated + * clusters) and must be accepted: "svcn > evcn + 1" tolerates it, + * since "(u64)-1 + 1" is 0 and "0 > 0" is false. + * + * For a non-empty attribute evcn is a cluster index and must lie + * within the volume (sbi->used.bitmap.nbits, set up in + * ntfs_init_from_boot() before any caller of mi_enum_attr() runs). + * Bounding evcn also prevents a malformed value close to U64_MAX + * from slipping through the near-wrap "evcn + 1" upper bound. + */ + svcn = le64_to_cpu(attr->nres.svcn); + evcn = le64_to_cpu(attr->nres.evcn); + if (svcn > evcn + 1) goto out; + if (is_attr_ext(attr)) { + /* sparsed/compressed attribute. */ +#ifdef CONFIG_NTFS3_64BIT_CLUSTER + /* No limits. */ +#else + /* Check evcn fits into 32 bits. */ + if (evcn != U64_MAX && evcn >= (1ull << 32)) + goto out; +#endif + } else { + /* Check out of volume for normal attribute. */ + if (evcn != U64_MAX && evcn >= mi->sbi->used.bitmap.nbits) + goto out; + } + data_size = le64_to_cpu(attr->nres.data_size); if (le64_to_cpu(attr->nres.valid_size) > data_size) goto out; diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c index c0324cdc174d..6e3ef89fc666 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) @@ -1008,6 +1068,9 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino, if (size_size > sizeof(len)) return -EINVAL; + if (run_buf + size_size > run_last) + return -EINVAL; + len = run_unpack_s64(run_buf, size_size, 0); /* Skip size_size. */ run_buf += size_size; @@ -1020,6 +1083,9 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino, else if (offset_size <= sizeof(s64)) { s64 dlcn; + if (run_buf + offset_size > run_last) + return -EINVAL; + /* Initial value of dlcn is -1 or 0. */ dlcn = (run_buf[offset_size - 1] & 0x80) ? (s64)-1 : 0; dlcn = run_unpack_s64(run_buf, offset_size, dlcn); @@ -1059,9 +1125,15 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino, return -EOPNOTSUPP; } #endif - if (lcn != SPARSE_LCN64 && lcn + len > sbi->used.bitmap.nbits) { - /* LCN range is out of volume. */ - return -EINVAL; + if (lcn != SPARSE_LCN64) { + u64 lcn_end; + + if (check_add_overflow(lcn, len, &lcn_end)) + return -EINVAL; + if (lcn_end > sbi->used.bitmap.nbits) { + /* LCN range is out of volume. */ + return -EINVAL; + } } if (!run) @@ -1193,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; @@ -1269,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; @@ -1280,9 +1356,13 @@ 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/super.c b/fs/ntfs3/super.c index 174a7cb202a0..f4a42a0c73a4 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -23,6 +23,7 @@ * allocated_size - Total size of clusters allocated for non-resident content * total_size - Actual size of allocated clusters for sparse or compressed attributes * - Constraint: valid_size <= data_size <= allocated_size + * ADS - Alternative data stream: Named data attribute (0x80) * * WSL - Windows Subsystem for Linux * https://docs.microsoft.com/en-us/windows/wsl/file-permissions @@ -65,6 +66,7 @@ #include <linux/minmax.h> #include <linux/module.h> #include <linux/nls.h> +#include <linux/overflow.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/statfs.h> @@ -153,7 +155,7 @@ void ntfs_inode_printk(struct inode *inode, const char *fmt, ...) vaf.fmt = printk_skip_level(fmt); vaf.va = &args; - printk("%c%cntfs3(%s): ino=%lx,%s %pV\n", KERN_SOH_ASCII, level, + printk("%c%cntfs3(%s): ino=%llx,%s %pV\n", KERN_SOH_ASCII, level, sb->s_id, inode->i_ino, name ? name : "", &vaf); va_end(args); @@ -271,6 +273,8 @@ enum Opt { Opt_nocase, Opt_delalloc, Opt_delalloc_bool, + Opt_ads, + Opt_ads_bool, Opt_err, }; @@ -297,6 +301,8 @@ static const struct fs_parameter_spec ntfs_fs_parameters[] = { fsparam_flag("nocase", Opt_nocase), fsparam_flag("delalloc", Opt_delalloc), fsparam_bool("delalloc", Opt_delalloc_bool), + fsparam_flag("ads", Opt_ads), + fsparam_bool("ads", Opt_ads_bool), {} }; // clang-format on @@ -420,6 +426,12 @@ static int ntfs_fs_parse_param(struct fs_context *fc, case Opt_delalloc_bool: opts->delalloc = result.boolean; break; + case Opt_ads: + opts->ads = 1; + break; + case Opt_ads_bool: + opts->ads = result.boolean; + break; default: /* Should not be here unless we forget add case. */ return -EINVAL; @@ -434,12 +446,6 @@ static int ntfs_fs_reconfigure(struct fs_context *fc) struct ntfs_mount_options *new_opts = fc->fs_private; int ro_rw; - /* If ntfs3 is used as legacy ntfs enforce read-only mode. */ - if (is_legacy_ntfs(sb)) { - fc->sb_flags |= SB_RDONLY; - goto out; - } - ro_rw = sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY); if (ro_rw && (sbi->flags & NTFS_FLAGS_NEED_REPLAY)) { errorf(fc, @@ -466,7 +472,6 @@ static int ntfs_fs_reconfigure(struct fs_context *fc) return -EINVAL; } -out: sync_filesystem(sb); swap(sbi->options, fc->fs_private); @@ -798,6 +803,8 @@ static int ntfs_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",nocase"); if (opts->delalloc) seq_puts(m, ",delalloc"); + if (opts->ads) + seq_puts(m, ",ads"); return 0; } @@ -964,7 +971,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; - u64 sectors, clusters, mlcn, mlcn2, dev_size0; + u64 sectors, clusters, mlcn, mlcn2, mft_pos, mft2_pos, dev_size0; struct NTFS_BOOT *boot; struct buffer_head *bh; struct MFT_REC *rec; @@ -1033,7 +1040,15 @@ read_boot: mlcn2 = le64_to_cpu(boot->mft2_clst); sectors = le64_to_cpu(boot->sectors_per_volume); - if (mlcn * sct_per_clst >= sectors || mlcn2 * sct_per_clst >= sectors) { + /* + * Convert mlcn/mlcn2 to sector positions before comparing with + * 'sectors'. All three are u64 values that come from the boot + * sector, so use check_mul_overflow() to keep a wraparound from + * silently bypassing the comparison. + */ + if (check_mul_overflow(mlcn, (u64)sct_per_clst, &mft_pos) || + check_mul_overflow(mlcn2, (u64)sct_per_clst, &mft2_pos) || + mft_pos >= sectors || mft2_pos >= sectors) { ntfs_err( sb, "%s: start of MFT 0x%llx (0x%llx) is out of volume 0x%llx.", @@ -1181,7 +1196,10 @@ read_boot: rec->total = cpu_to_le32(sbi->record_size); ((struct ATTRIB *)Add2Ptr(rec, ao))->type = ATTR_END; - sb_set_blocksize(sb, min_t(u32, sbi->cluster_size, PAGE_SIZE)); + if (!sb_set_blocksize(sb, min_t(u32, sbi->cluster_size, PAGE_SIZE))) { + err = -EINVAL; + goto out; + } sbi->block_mask = sb->s_blocksize - 1; sbi->blocks_per_cluster = sbi->cluster_size >> sb->s_blocksize_bits; @@ -1193,7 +1211,7 @@ read_boot: #ifdef CONFIG_NTFS3_64BIT_CLUSTER if (clusters >= (1ull << (64 - cluster_bits))) sbi->maxbytes = -1; - sbi->maxbytes_sparse = -1; + sbi->maxbytes_sparse = MAX_LFS_FILESIZE; sb->s_maxbytes = MAX_LFS_FILESIZE; #else /* Maximum size for sparse file. */ @@ -1232,7 +1250,8 @@ out: /* * Try alternative boot (last sector) */ - sb_set_blocksize(sb, block_size); + if (!sb_set_blocksize(sb, block_size)) + return -EINVAL; hint = "Alternative boot"; dev_size = dev_size0; /* restore original size. */ goto read_boot; @@ -1339,8 +1358,13 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) le32_to_cpu(attr->res.data_size) >> 1, UTF16_LITTLE_ENDIAN, sbi->volume.label, sizeof(sbi->volume.label)); - if (err < 0) + if (err < 0) { sbi->volume.label[0] = 0; + } else if (err >= sizeof(sbi->volume.label)) { + sbi->volume.label[sizeof(sbi->volume.label) - 1] = 0; + } else { + sbi->volume.label[err] = 0; + } } else { /* Should we break mounting here? */ //err = -EINVAL; @@ -1426,16 +1450,50 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) tt = inode->i_size >> sbi->record_bits; sbi->mft.next_free = MFT_REC_USER; - err = wnd_init(&sbi->mft.bitmap, sb, tt); - if (err) - goto put_inode_out; - err = ni_load_all_mi(ni); if (err) { ntfs_err(sb, "Failed to load $MFT's subrecords (%d).", err); goto put_inode_out; } + /* Merge MFT bitmap runs from extent records loaded by ni_load_all_mi. */ + { + struct ATTRIB *a = NULL; + struct ATTR_LIST_ENTRY *le = NULL; + + while ((a = ni_enum_attr_ex(ni, a, &le, NULL))) { + CLST svcn, evcn; + u16 roff; + + if (a->type != ATTR_BITMAP || !a->non_res) + continue; + + svcn = le64_to_cpu(a->nres.svcn); + if (!svcn) + continue; /* Base record runs already loaded. */ + + evcn = le64_to_cpu(a->nres.evcn); + roff = le16_to_cpu(a->nres.run_off); + + err = run_unpack_ex(&sbi->mft.bitmap.run, sbi, + MFT_REC_MFT, svcn, evcn, svcn, + Add2Ptr(a, roff), + le32_to_cpu(a->size) - roff); + if (err < 0) { + ntfs_err( + sb, + "Failed to unpack $MFT bitmap extent (%d).", + err); + goto put_inode_out; + } + err = 0; + } + } + + err = wnd_init(&sbi->mft.bitmap, sb, tt); + if (err) + goto put_inode_out; + sbi->mft.ni = ni; /* Load $Bitmap. */ @@ -1673,7 +1731,7 @@ load_root: sb->s_root = d_make_root(inode); if (!sb->s_root) { err = -ENOMEM; - goto put_inode_out; + goto out; } if (boot2) { @@ -1699,8 +1757,6 @@ load_root: ntfs_create_procdir(sb); - if (is_legacy_ntfs(sb)) - sb->s_flags |= SB_RDONLY; return 0; put_inode_out: @@ -1823,7 +1879,7 @@ static const struct fs_context_operations ntfs_context_ops = { * This will called when mount/remount. We will first initialize * options so that if remount we can use just that. */ -static int __ntfs_init_fs_context(struct fs_context *fc) +static int ntfs_init_fs_context(struct fs_context *fc) { struct ntfs_mount_options *opts; struct ntfs_sb_info *sbi; @@ -1835,9 +1891,9 @@ static int __ntfs_init_fs_context(struct fs_context *fc) /* Default options. */ opts->fs_uid = current_uid(); opts->fs_gid = current_gid(); - opts->fs_fmask_inv = ~current_umask(); - opts->fs_dmask_inv = ~current_umask(); + opts->fs_fmask_inv = opts->fs_dmask_inv = ~current_umask(); opts->prealloc = 1; + opts->ads = 1; #ifdef CONFIG_NTFS3_FS_POSIX_ACL /* Set the default value 'acl' */ @@ -1877,11 +1933,6 @@ free_opts: return -ENOMEM; } -static int ntfs_init_fs_context(struct fs_context *fc) -{ - return __ntfs_init_fs_context(fc); -} - static void ntfs3_kill_sb(struct super_block *sb) { struct ntfs_sb_info *sbi = sb->s_fs_info; @@ -1902,48 +1953,6 @@ static struct file_system_type ntfs_fs_type = { .kill_sb = ntfs3_kill_sb, .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, }; - -#if IS_ENABLED(CONFIG_NTFS_FS) -static int ntfs_legacy_init_fs_context(struct fs_context *fc) -{ - int ret; - - ret = __ntfs_init_fs_context(fc); - /* If ntfs3 is used as legacy ntfs enforce read-only mode. */ - fc->sb_flags |= SB_RDONLY; - return ret; -} - -static struct file_system_type ntfs_legacy_fs_type = { - .owner = THIS_MODULE, - .name = "ntfs", - .init_fs_context = ntfs_legacy_init_fs_context, - .parameters = ntfs_fs_parameters, - .kill_sb = ntfs3_kill_sb, - .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, -}; -MODULE_ALIAS_FS("ntfs"); - -static inline void register_as_ntfs_legacy(void) -{ - int err = register_filesystem(&ntfs_legacy_fs_type); - if (err) - pr_warn("ntfs3: Failed to register legacy ntfs filesystem driver: %d\n", err); -} - -static inline void unregister_as_ntfs_legacy(void) -{ - unregister_filesystem(&ntfs_legacy_fs_type); -} -bool is_legacy_ntfs(struct super_block *sb) -{ - return sb->s_type == &ntfs_legacy_fs_type; -} -#else -static inline void register_as_ntfs_legacy(void) {} -static inline void unregister_as_ntfs_legacy(void) {} -#endif - // clang-format on static int __init init_ntfs_fs(void) @@ -1972,7 +1981,6 @@ static int __init init_ntfs_fs(void) goto out1; } - register_as_ntfs_legacy(); err = register_filesystem(&ntfs_fs_type); if (err) goto out; @@ -1992,7 +2000,6 @@ static void __exit exit_ntfs_fs(void) rcu_barrier(); kmem_cache_destroy(ntfs_inode_cachep); unregister_filesystem(&ntfs_fs_type); - unregister_as_ntfs_legacy(); ntfs3_exit_bitmap(); ntfs_remove_proc_root(); } diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index 3fffda784892..594ef6860b93 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -146,26 +146,29 @@ 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; + /* 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)); + if (ef->size) { ea_size = le32_to_cpu(ef->size); - if (ea_size > bytes) + /* ef->size must fit the list and cover the record. */ + if (ea_size > bytes || ea_size < need) goto out1; continue; } - /* Check if we can use fields ef->name_len and ef->elength. */ - if (bytes < offsetof(struct EA_FULL, name)) - goto out1; - - ea_size = ALIGN(struct_size(ef, name, - 1 + ef->name_len + - le16_to_cpu(ef->elength)), - 4); + ea_size = ALIGN(need, 4); if (ea_size > bytes) goto out1; } @@ -660,7 +663,6 @@ static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap, inode->i_mode = old_mode; goto out; } - inode->i_mode = mode; } set_cached_acl(inode, type, acl); inode_set_ctime_current(inode); @@ -851,6 +853,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 +875,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 +965,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); @@ -1031,7 +1047,7 @@ void ntfs_get_wsl_perm(struct inode *inode) i_gid_write(inode, (gid_t)le32_to_cpu(value[1])); inode->i_mode = le32_to_cpu(value[2]); - if (ntfs_get_ea(inode, "$LXDEV", sizeof("$$LXDEV") - 1, + if (ntfs_get_ea(inode, "$LXDEV", sizeof("$LXDEV") - 1, &value[0], sizeof(value), &sz) == sizeof(value[0])) { inode->i_rdev = le32_to_cpu(value[0]); |
