Pull ntfs3 updates from Konstantin Komarov:
 "Added:
    load ATTR_BITMAP run extents from $MFT extension records
    initialize err in attr_wof_frame_info
    reserve NUL byte when converting UTF-16 names
    reject restart table growth beyond U16_MAX entries
    validate dirty page table on log replay
    basic support for alternative data streams
    validate ef->size covers the record's name and value

  Fixed:
    slab-out-of-bounds write in ni_create_attr_list()
    out-of-bounds read of INDEX_ROOT in reparse/objid init
    boundary check in ntfs_dir_count()
    info-leak in ntfs_rename()
    lseek EINVAL on sparse/compressed files with 64-bit clusters
    info-leak on partial LZNT decompress in ni_read_frame()
    bound page_lcns[] index by the log record
    memory leak in indx_find_sort()
    integer overflow in MFT cluster validation
    reject out-of-range evcn in mi_enum_attr()
    out-of-bounds read in read_log_rec_buf()

  Changed;
    widen inode/record number storage to u64
    cosmetic fixes and improvements
    rename 'err' to 'ret' in read paths"

* tag 'ntfs3_for_7.3' of https://github.com/Paragon-Software-Group/linux-ntfs3: (21 commits)
  fs/ntfs3: validate ef->size covers the record's name and value
  fs/ntfs3: fix out-of-bounds read in read_log_rec_buf()
  fs/ntfs3: reject out-of-range evcn in mi_enum_attr()
  fs/ntfs3: fix integer overflow in MFT cluster validation
  fs/ntfs3: Add basic support for alternative data streams
  fs/ntfs3: Rename 'err' to 'ret' in read paths
  fs/ntfs3: Fix memory leak in indx_find_sort()
  fs/ntfs3: bound page_lcns[] index by the log record
  fs/ntfs3: validate dirty page table on log replay
  fs/ntfs3: reject restart table growth beyond U16_MAX entries
  fs/ntfs3: fix info-leak on partial LZNT decompress in ni_read_frame()
  fs/ntfs3: reserve NUL byte when converting UTF-16 names
  ntfs3: initialize err in attr_wof_frame_info
  fs/ntfs3: fix lseek EINVAL on sparse/compressed files with 64-bit clusters
  fs/ntfs3: load ATTR_BITMAP run extents from $MFT extension records
  ntfs3: fix info-leak in ntfs_rename()
  ntfs3: fix boundary check in ntfs_dir_count()
  fs/ntfs3: fix out-of-bounds read of INDEX_ROOT in reparse/objid init
  fs/ntfs3: fix slab-out-of-bounds write in ni_create_attr_list()
  fs/ntfs3: cosmetic fixes and improvements
  ...
This commit is contained in:
Linus Torvalds
2026-08-20 12:27:02 -07:00
17 changed files with 924 additions and 284 deletions

View File

@@ -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 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type,
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 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type,
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) {
@@ -1001,7 +1001,6 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
struct ATTRIB *attr, *attr_b;
struct ATTR_LIST_ENTRY *le, *le_b;
struct mft_inode *mi, *mi_b;
struct page *page;
CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0;
CLST alloc, evcn;
unsigned fr;
@@ -1026,7 +1025,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
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;
@@ -1036,11 +1036,15 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
u32 data_size = le32_to_cpu(attr_b->res.data_size);
*lcn = RESIDENT_LCN;
*len = data_size;
if (res && data_size) {
page = alloc_page(GFP_KERNEL);
if (!page) {
err = -ENOMEM;
} else {
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);
}
@@ -1067,7 +1071,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
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;
@@ -1140,8 +1145,9 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
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;
@@ -1155,8 +1161,9 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
if (vcn0 < svcn || evcn1 <= vcn0) {
struct ATTRIB *attr2;
attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL,
0, &vcn0, &mi);
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;
@@ -1269,8 +1276,9 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
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;
@@ -1300,7 +1308,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
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. */
@@ -1333,7 +1342,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
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;
@@ -1362,9 +1372,10 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
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;
}
@@ -1398,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;
@@ -1515,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;
@@ -1775,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;
@@ -1798,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;
@@ -1885,8 +1899,9 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
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;
@@ -1902,7 +1917,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
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(
@@ -1931,7 +1947,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
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;
@@ -1962,9 +1979,10 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
}
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;
}
@@ -2007,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;
@@ -2037,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)
@@ -2061,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;
@@ -2074,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;
@@ -2105,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;
@@ -2169,7 +2192,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
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)
@@ -2209,7 +2233,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
/* 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;
@@ -2232,7 +2257,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
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;
@@ -2293,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;
@@ -2364,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;
@@ -2416,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;
@@ -2454,7 +2482,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
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;
@@ -2512,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;
@@ -2559,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;
@@ -2604,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;
@@ -2647,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;
@@ -2709,7 +2744,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) {
goto bad_inode;
@@ -2754,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;

View File

@@ -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;

View File

@@ -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;
}
} else {
for (ret = 0, end = name + name_len; name < end;
ret++, name += slen) {
if (ret >= max_ulen)
return -ENAMETOOLONG;
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 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
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,15 +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 (sizeof(struct NTFS_DE) +
offsetof(struct ATTR_FILE_NAME, name) +
fname->name_len * sizeof(short) > 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;
}
@@ -576,6 +618,23 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
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)
{
@@ -615,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)

View File

@@ -753,7 +753,9 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
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;
@@ -820,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(
@@ -867,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. */
@@ -889,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);
}
/*
@@ -1420,7 +1439,8 @@ static int ntfs_file_release(struct inode *inode, struct file *file)
down_write(&ni->file.run_lock);
/* Deallocate preallocated. */
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,
inode->i_size, &ni->i_valid, false, NULL,
true);

View File

@@ -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 @@ int ni_load_mi_ex(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi)
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;
@@ -1335,6 +1367,7 @@ int ni_expand_list(struct ntfs_inode *ni)
bool is_mft = ni->mi.rno == MFT_REC_MFT;
struct MFT_REF ref;
ni = ni->base;
mi_get_ref(&ni->mi, &ref);
le = NULL;
@@ -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));
@@ -1988,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);
@@ -2249,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.
@@ -2443,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;
@@ -2498,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;
@@ -2623,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. */
@@ -2685,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);
@@ -2735,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;
@@ -2782,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:
@@ -2793,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);
if (err && ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
_ntfs_bad_inode(&ni->vfs_inode);
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;
}
@@ -2919,7 +2977,6 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data)
break;
}
}
}
vbo = (u64)vcn << cluster_bits;
@@ -2939,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;
@@ -2961,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;
}
@@ -3081,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;
}
@@ -3121,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;
@@ -3305,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;
}

View File

@@ -647,6 +647,14 @@ static inline void *enum_rstbl(struct RESTART_TABLE *t, void *c)
return NULL;
}
/*
* 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.
*/
@@ -789,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.
*/
@@ -853,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;
@@ -2276,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;
@@ -2613,7 +2646,6 @@ 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 (has_subnode) {
min_de = sizeof(struct NTFS_DE) + sizeof(u64);
@@ -2632,8 +2664,8 @@ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
return false;
}
e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
for (;;) {
const struct NTFS_DE *e = Add2Ptr(hdr, de_off);
u16 esize = le16_to_cpu(e->size);
u16 key_size = le16_to_cpu(e->key_size);
u16 data_size;
@@ -2649,7 +2681,6 @@ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
if (de_is_last(e)) {
if (key_size)
return false;
break;
}
@@ -2658,7 +2689,6 @@ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
return false;
de_off += esize;
e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
}
return true;
@@ -3544,8 +3574,7 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
* 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))
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);
@@ -3756,8 +3785,7 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
/* 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))
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);
@@ -4295,6 +4323,11 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
goto out;
}
if (!check_dp_table(rt)) {
err = -EINVAL;
goto out;
}
dptbl = kmemdup(rt, t32, GFP_NOFS);
if (!dptbl) {
err = -ENOMEM;
@@ -4653,11 +4686,11 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
}
/*
* 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.
*/
* 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;
@@ -5087,6 +5120,13 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
/* 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;

View File

@@ -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.
*/
@@ -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;
@@ -2664,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");

View File

@@ -612,8 +612,8 @@ 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);
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);
@@ -1325,6 +1325,7 @@ int indx_find_sort(struct ntfs_index *indx, struct ntfs_inode *ni,
/* Pop one level. */
if (n) {
fnd_pop(fnd);
kfree(n->index);
kfree(n);
}
@@ -2131,8 +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,
depth + 1);
r = indx_find_buffer(indx, ni, root, vbn, n, depth + 1);
if (r)
return r;
}

View File

@@ -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 @@ static struct inode *ntfs_read_mft(struct inode *inode,
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 @@ static struct inode *ntfs_read_mft(struct inode *inode,
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 @@ static struct inode *ntfs_read_mft(struct inode *inode,
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 @@ static struct inode *ntfs_read_mft(struct inode *inode,
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;
}
@@ -495,16 +504,136 @@ static struct inode *ntfs_read_mft(struct inode *inode,
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;
}
/*
@@ -514,44 +643,120 @@ static struct inode *ntfs_read_mft(struct inode *inode,
*/
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;
return ino_get(ref) == inode->i_ino;
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 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;
inode = iget5_locked(sb, ino_get(ref), ntfs_test_inode, ntfs_set_inode,
(void *)ref);
if (unlikely(!inode))
return ERR_PTR(-ENOMEM);
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;
}
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;
}
@@ -606,15 +811,17 @@ static void ntfs_iomap_read_end_io(struct bio *bio)
}
static void ntfs_iomap_bio_submit_read(const struct iomap_iter *iter,
struct iomap_read_folio_ctx *ctx)
struct iomap_read_folio_ctx *ctx)
{
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 = 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)
{
@@ -698,8 +905,8 @@ int ntfs_set_size(struct inode *inode, u64 new_size)
ni->i_valid = new_size;
/* last 'true' means keep preallocated. */
err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
&ni->i_valid, true);
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);
@@ -793,7 +1000,8 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
if (lcn == RESIDENT_LCN) {
if (offset >= clen) {
__free_page(virt_to_page(res));
if (res)
__free_page(virt_to_page(res));
if (flags & IOMAP_REPORT) {
/* special code for report. */
return -ENOENT;
@@ -884,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;
@@ -1206,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;

View File

@@ -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 (index < ARRAY_SIZE(s_max_off) - 1 && 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))) {

View File

@@ -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);
@@ -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);
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);
err = ntfs_unlink_inode(dir, dentry);
ni_unlock(ni);
if (!err)
drop_nlink(inode);
}
return err;
}
@@ -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;

View File

@@ -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];

View File

@@ -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. */
@@ -401,7 +402,7 @@ struct ntfs_inode {
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,8 +538,14 @@ 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;
@@ -622,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);
@@ -679,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,
@@ -772,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);
@@ -886,8 +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, size_t run_buf_size,
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);
@@ -1219,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,

View File

@@ -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;

View File

@@ -1265,8 +1265,8 @@ 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, size_t run_buf_size,
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;
@@ -1279,7 +1279,7 @@ int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size,
if (size_size > 8 || offset_size > 8)
return -EINVAL;
if (run_buf + size_size + offset_size > run_last)
if (run_buf + size_size + offset_size > run_last)
return -EINVAL;
len = run_unpack_s64(run_buf, size_size, 0);
@@ -1357,7 +1357,8 @@ 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));
SPARSE_LCN :
(r->lcn + (end - r->vcn));
*done += len;
r->len = d;

View File

@@ -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>
@@ -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;
@@ -791,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;
}
@@ -957,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;
@@ -1026,7 +1040,15 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size,
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.",
@@ -1189,7 +1211,7 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size,
#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. */
@@ -1458,7 +1480,10 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
Add2Ptr(a, roff),
le32_to_cpu(a->size) - roff);
if (err < 0) {
ntfs_err(sb, "Failed to unpack $MFT bitmap extent (%d).", err);
ntfs_err(
sb,
"Failed to unpack $MFT bitmap extent (%d).",
err);
goto put_inode_out;
}
err = 0;
@@ -1866,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' */
@@ -1928,7 +1953,6 @@ static struct file_system_type ntfs_fs_type = {
.kill_sb = ntfs3_kill_sb,
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
};
// clang-format on
static int __init init_ntfs_fs(void)

View File

@@ -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;
if (ef->size) {
ea_size = le32_to_cpu(ef->size);
if (ea_size > bytes)
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);
/* 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);
/* ef->size must fit the list and cover the record. */
if (ea_size > bytes || ea_size < need)
goto out1;
continue;
}
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);