summaryrefslogtreecommitdiff
path: root/fs/ntfs3
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ntfs3')
-rw-r--r--fs/ntfs3/Kconfig10
-rw-r--r--fs/ntfs3/Makefile20
-rw-r--r--fs/ntfs3/attrib.c197
-rw-r--r--fs/ntfs3/attrlist.c15
-rw-r--r--fs/ntfs3/dir.c162
-rw-r--r--fs/ntfs3/file.c388
-rw-r--r--fs/ntfs3/frecord.c274
-rw-r--r--fs/ntfs3/fslog.c217
-rw-r--r--fs/ntfs3/fsntfs.c39
-rw-r--r--fs/ntfs3/index.c190
-rw-r--r--fs/ntfs3/inode.c455
-rw-r--r--fs/ntfs3/lznt.c4
-rw-r--r--fs/ntfs3/namei.c74
-rw-r--r--fs/ntfs3/ntfs.h3
-rw-r--r--fs/ntfs3/ntfs_fs.h73
-rw-r--r--fs/ntfs3/record.c34
-rw-r--r--fs/ntfs3/run.c94
-rw-r--r--fs/ntfs3/super.c153
-rw-r--r--fs/ntfs3/xattr.c40
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]);