diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems/ntfs.rst index 5c96b04a4d7a..4bfa392daec6 100644 --- a/Documentation/filesystems/ntfs.rst +++ b/Documentation/filesystems/ntfs.rst @@ -156,4 +156,17 @@ windows_names= Refuse creation/rename of files with characters or discard= Issue block device discard for clusters freed on file deletion/truncation to inform underlying storage. + +native_symlink=raw|rel Configure how absolute symbolic links and mount + points (junctions) are handled. Under "raw" + (default), the absolute target path is returned + as-is without translation. Under "rel", it is + rewritten as a relative path anchored at + the volume root. + +symlink=wsl|native Configure how symbolic links are created. Under + "wsl" (default), WSL (Windows Subsystem for + Linux) compatible symlinks are created. Under + "native", Windows native symbolic links are + created. ======================= ==================================================== diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile index 0ce4d9a9388a..e120c2e69862 100644 --- a/fs/ntfs/Makefile +++ b/fs/ntfs/Makefile @@ -5,6 +5,6 @@ obj-$(CONFIG_NTFS_FS) += ntfs.o ntfs-y := aops.o attrib.o collate.o dir.o file.o index.o inode.o \ mft.o mst.o namei.o runlist.o super.o unistr.o attrlist.o ea.o \ upcase.o bitmap.o lcnalloc.o logfile.o reparse.o compress.o \ - iomap.o debug.o sysctl.o quota.o object_id.o bdev-io.o + iomap.o debug.o sysctl.o object_id.o bdev-io.o ccflags-$(CONFIG_NTFS_DEBUG) += -DDEBUG diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 421c6cdcbb53..dd8828098511 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -16,6 +16,7 @@ * Copyright (c) 2010 Erik Larsson */ +#include #include #include @@ -588,6 +589,8 @@ static u32 ntfs_resident_attr_min_value_length(const __le32 type) sizeof(__le16) * 1; case AT_VOLUME_INFORMATION: return sizeof(struct volume_information); + case AT_INDEX_ROOT: + return sizeof(struct index_root); case AT_EA_INFORMATION: return sizeof(struct ea_information); default: @@ -595,6 +598,154 @@ static u32 ntfs_resident_attr_min_value_length(const __le32 type) } } +static bool ntfs_attr_type_is_resident_only(const __le32 type) +{ + switch (type) { + case AT_STANDARD_INFORMATION: + case AT_FILE_NAME: + case AT_OBJECT_ID: + case AT_VOLUME_NAME: + case AT_VOLUME_INFORMATION: + case AT_INDEX_ROOT: + case AT_EA_INFORMATION: + return true; + default: + return false; + } +} + +static bool ntfs_file_name_attr_value_is_valid(const u8 *value, const u32 value_length) +{ + const struct file_name_attr *fn; + u32 file_name_size; + + fn = (const struct file_name_attr *)value; + file_name_size = fn->file_name_length * sizeof(__le16); + + return file_name_size <= + value_length - offsetof(struct file_name_attr, file_name); +} + +static bool ntfs_volume_name_attr_value_is_valid(const u32 value_length) +{ + if (value_length & 1) + return false; + + return value_length <= NTFS_MAX_LABEL_LEN * sizeof(__le16); +} + +static bool ntfs_index_root_attr_value_is_valid(const u8 *value, const u32 value_length) +{ + const struct index_root *ir; + u32 index_size; + u32 entries_offset; + u32 index_length; + u32 allocated_size; + + ir = (const struct index_root *)value; + index_size = value_length - offsetof(struct index_root, index); + entries_offset = le32_to_cpu(ir->index.entries_offset); + index_length = le32_to_cpu(ir->index.index_length); + allocated_size = le32_to_cpu(ir->index.allocated_size); + + if ((entries_offset | index_length | allocated_size) & 7 || + entries_offset < sizeof(struct index_header) || + entries_offset > index_length || + index_length > allocated_size || + allocated_size > index_size || + index_length - entries_offset < sizeof(struct index_entry_header)) + return false; + + return true; +} + +struct ntfs_resident_attr_value { + const u8 *data; + u32 len; +}; + +static bool ntfs_resident_attr_value_get(const struct attr_record *a, + struct ntfs_resident_attr_value *value) +{ + u32 attr_len; + u16 value_offset; + + attr_len = le32_to_cpu(a->length); + if (attr_len < offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved)) + return false; + + value->len = le32_to_cpu(a->data.resident.value_length); + value_offset = le16_to_cpu(a->data.resident.value_offset); + + if (value->len > attr_len || value_offset > attr_len - value->len) + return false; + + value->data = (const u8 *)a + value_offset; + return true; +} + +static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a) +{ + u32 attr_len; + u32 min_len; + u16 mp_offset; + + attr_len = le32_to_cpu(a->length); + min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + + sizeof(a->data.non_resident.initialized_size); + if (attr_len < min_len) + return false; + + mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); + return mp_offset >= min_len && mp_offset <= attr_len; +} + +static bool ntfs_attr_value_is_valid(struct ntfs_volume *vol, + const struct attr_record *a, + const u64 mft_no) +{ + struct ntfs_resident_attr_value value; + u32 min_len; + + if (a->non_resident) { + if (ntfs_attr_type_is_resident_only(a->type)) + goto corrupt; + if (!ntfs_non_resident_attr_value_is_valid(a)) + goto corrupt; + return true; + } + + if (!ntfs_resident_attr_value_get(a, &value)) + goto corrupt; + + min_len = ntfs_resident_attr_min_value_length(a->type); + if (min_len && value.len < min_len) + goto corrupt; + + switch (a->type) { + case AT_FILE_NAME: + if (!ntfs_file_name_attr_value_is_valid(value.data, value.len)) + goto corrupt; + break; + case AT_VOLUME_NAME: + if (!ntfs_volume_name_attr_value_is_valid(value.len)) + goto corrupt; + break; + case AT_INDEX_ROOT: + if (!ntfs_index_root_attr_value_is_valid(value.data, value.len)) + goto corrupt; + break; + } + return true; + +corrupt: + ntfs_error(vol->sb, + "Corrupt %#x attribute in MFT record %llu\n", + le32_to_cpu(a->type), mft_no); + return false; +} + /* * ntfs_attr_find - find (next) attribute in mft record * @type: attribute type to find @@ -705,8 +856,11 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name, } } - if (type == AT_UNUSED) + if (type == AT_UNUSED) { + if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no)) + break; return 0; + } if (a->type != type) continue; /* @@ -747,37 +901,8 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name, } } - /* Validate attribute's value offset/length */ - if (!a->non_resident) { - u32 min_len; - u32 value_length = le32_to_cpu(a->data.resident.value_length); - u16 value_offset = le16_to_cpu(a->data.resident.value_offset); - - if (value_length > le32_to_cpu(a->length) || - value_offset > le32_to_cpu(a->length) - value_length) - break; - - min_len = ntfs_resident_attr_min_value_length(a->type); - if (min_len && value_length < min_len) { - ntfs_error(vol->sb, - "Too small %#x resident attribute value in MFT record %lld\n", - le32_to_cpu(a->type), (long long)ctx->ntfs_ino->mft_no); - break; - } - } else { - u32 min_len; - u16 mp_offset; - - min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + - sizeof(a->data.non_resident.initialized_size); - if (le32_to_cpu(a->length) < min_len) - break; - - mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); - if (mp_offset < min_len || - mp_offset > le32_to_cpu(a->length)) - break; - } + if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no)) + break; /* * The names match or @name not present and attribute is @@ -843,11 +968,71 @@ char *ntfs_attr_name_get(const struct ntfs_volume *vol, const __le16 *uname, return NULL; } +/* + * ntfs_attr_list_entry_is_valid - sanity check one $ATTRIBUTE_LIST entry + * @ale: the attribute-list entry to check + * @al_end: end of the attribute-list buffer @ale lives in + * + * Verify that @ale is a well-formed attr_list_entry wholly contained in + * [.., @al_end): its fixed header must lie in range before any field is + * dereferenced, its length must be a multiple of 8 that covers the fixed + * header plus the name, the name must lie within the buffer, the entry must + * be in use and carry a live MFT reference. Return true if valid. + */ +bool ntfs_attr_list_entry_is_valid(const struct attr_list_entry *ale, + const u8 *al_end) +{ + const u8 *al = (const u8 *)ale; + u16 ale_len; + + /* The fixed header must be in bounds before it is parsed. */ + if (al + offsetof(struct attr_list_entry, name) > al_end) + return false; + ale_len = le16_to_cpu(ale->length); + /* On-disk entries are 8-byte aligned (see struct attr_list_entry). */ + if (ale_len & 7) + return false; + if (ale->name_offset != sizeof(struct attr_list_entry)) + return false; + if ((u32)ale->name_offset + + (u32)ale->name_length * sizeof(__le16) > ale_len || + al + ale_len > al_end) + return false; + if (ale->type == AT_UNUSED) + return false; + if (MSEQNO_LE(ale->mft_reference) == 0) + return false; + return true; +} + +/* + * ntfs_attr_list_is_valid - sanity check an in-memory $ATTRIBUTE_LIST + * @al_start: start of the attribute list buffer + * @size: length of the attribute list in bytes + * + * Verify that [@al_start, @al_start + @size) is a sequence of valid + * attr_list_entry records (see ntfs_attr_list_entry_is_valid()) that tile the + * buffer exactly. Return true if valid, false otherwise. + */ +bool ntfs_attr_list_is_valid(const u8 *al_start, s64 size) +{ + const u8 *al = al_start; + const u8 *al_end = al_start + size; + + while (al < al_end) { + const struct attr_list_entry *ale = + (const struct attr_list_entry *)al; + + if (!ntfs_attr_list_entry_is_valid(ale, al_end)) + return false; + al += le16_to_cpu(ale->length); + } + return al == al_end; +} + int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size) { struct inode *attr_vi = NULL; - u8 *al; - struct attr_list_entry *ale; if (!al_start || size <= 0) return -EINVAL; @@ -869,19 +1054,7 @@ int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size } iput(attr_vi); - for (al = al_start; al < al_start + size; al += le16_to_cpu(ale->length)) { - ale = (struct attr_list_entry *)al; - if (ale->name_offset != sizeof(struct attr_list_entry)) - break; - if (le16_to_cpu(ale->length) <= ale->name_offset + ale->name_length || - al + le16_to_cpu(ale->length) > al_start + size) - break; - if (ale->type == AT_UNUSED) - break; - if (MSEQNO_LE(ale->mft_reference) == 0) - break; - } - if (al != al_start + size) { + if (!ntfs_attr_list_is_valid(al_start, size)) { ntfs_error(base_ni->vol->sb, "Corrupt attribute list, mft = %llu", base_ni->mft_no); return -EIO; @@ -1137,9 +1310,8 @@ static int ntfs_external_attr_find(const __le32 type, * we have reached the right one or the search has failed. */ if (lowest_vcn && (u8 *)next_al_entry >= al_start && - (u8 *)next_al_entry + 6 < al_end && - (u8 *)next_al_entry + le16_to_cpu( - next_al_entry->length) <= al_end && + ntfs_attr_list_entry_is_valid(next_al_entry, + al_end) && le64_to_cpu(next_al_entry->lowest_vcn) <= lowest_vcn && next_al_entry->type == al_entry->type && @@ -1252,22 +1424,8 @@ static int ntfs_external_attr_find(const __le32 type, ctx->attr = a; - if (a->non_resident) { - u32 min_len; - u16 mp_offset; - - min_len = offsetof(struct attr_record, - data.non_resident.initialized_size) + - sizeof(a->data.non_resident.initialized_size); - - if (le32_to_cpu(a->length) < min_len) - break; - - mp_offset = - le16_to_cpu(a->data.non_resident.mapping_pairs_offset); - if (mp_offset < min_len || mp_offset > attr_len) - break; - } + if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no)) + break; /* * If no @val specified or @val specified and it matches, we @@ -1279,19 +1437,6 @@ static int ntfs_external_attr_find(const __le32 type, u32 value_length = le32_to_cpu(a->data.resident.value_length); u16 value_offset = le16_to_cpu(a->data.resident.value_offset); - if (attr_len < offsetof(struct attr_record, data.resident.reserved) + - sizeof(a->data.resident.reserved)) - break; - if (value_length > attr_len || value_offset > attr_len - value_length) - break; - - value_length = ntfs_resident_attr_min_value_length(a->type); - if (value_length && le32_to_cpu(a->data.resident.value_length) < - value_length) { - pr_err("Too small resident attribute value in MFT record %lld, type %#x\n", - (long long)ctx->ntfs_ino->mft_no, a->type); - break; - } if (value_length == val_len && !memcmp((u8 *)a + value_offset, val, val_len)) { attr_found: @@ -1855,7 +2000,7 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size) if (IS_ERR(rl)) { err = PTR_ERR(rl); ntfs_debug("Failed to allocate cluster%s, error code %i.", - ntfs_bytes_to_cluster(vol, new_size) > 1 ? "s" : "", + str_plural(ntfs_bytes_to_cluster(vol, new_size)), err); goto folio_err_out; } @@ -4536,10 +4681,12 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) { struct inode *tvi; struct attr_record *a; + u32 value_len; a = ctx->attr; if (a->non_resident || a->type == AT_ATTRIBUTE_LIST) continue; + value_len = le32_to_cpu(a->data.resident.value_length); if (ntfs_attr_can_be_non_resident(vol, a->type)) continue; @@ -4551,6 +4698,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi if (le32_to_cpu(a->length) <= (sizeof(struct attr_record) - sizeof(s64)) + ((a->name_length * sizeof(__le16) + 7) & ~7) + 8) continue; + if (a->type == AT_DATA && !value_len) + continue; if (a->type == AT_DATA) tvi = ntfs_iget(sb, base_ni->mft_no); @@ -4563,8 +4712,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi continue; } - if (ntfs_attr_make_non_resident(NTFS_I(tvi), - le32_to_cpu(ctx->attr->data.resident.value_length))) { + if (ntfs_attr_make_non_resident(NTFS_I(tvi), value_len)) { iput(tvi); continue; } diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index f7acc7986b09..e2224fbfaabe 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -71,6 +71,10 @@ int ntfs_attr_lookup(const __le32 type, const __le16 *name, const u32 name_len, const u32 ic, const s64 lowest_vcn, const u8 *val, const u32 val_len, struct ntfs_attr_search_ctx *ctx); +bool ntfs_attr_list_entry_is_valid(const struct attr_list_entry *ale, + const u8 *al_end); +bool ntfs_attr_list_is_valid(const u8 *al_start, s64 size); + int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size); diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index c2594d4c83b0..afb13038ba42 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -118,6 +118,7 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr) int entry_len, entry_offset, err; struct mft_record *ni_mrec; u8 *old_al; + __le64 lowest_vcn; if (!ni || !attr) { ntfs_debug("Invalid arguments.\n"); @@ -158,17 +159,21 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr) ntfs_error(ni->vol->sb, "Failed to get search context"); goto err_out; } + if (attr->non_resident) + lowest_vcn = attr->data.non_resident.lowest_vcn; + else + lowest_vcn = 0; err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *) ((u8 *)attr + le16_to_cpu(attr->name_offset)) : AT_UNNAMED, attr->name_length, CASE_SENSITIVE, - (attr->non_resident) ? le64_to_cpu(attr->data.non_resident.lowest_vcn) : - 0, (attr->non_resident) ? NULL : ((u8 *)attr + + le64_to_cpu(lowest_vcn), + (attr->non_resident) ? NULL : ((u8 *)attr + le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ? 0 : le32_to_cpu(attr->data.resident.value_length), ctx); if (!err) { /* Found some extent, check it to be before new extent. */ - if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) { + if (ctx->al_entry->lowest_vcn == lowest_vcn) { err = -EEXIST; ntfs_debug("Such attribute already present in the attribute list.\n"); ntfs_attr_put_search_ctx(ctx); diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c index 20f5c7074bdd..4b6bd5f30c65 100644 --- a/fs/ntfs/dir.c +++ b/fs/ntfs/dir.c @@ -135,10 +135,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, /* Key length should not be zero if it is not last entry. */ if (!ie->key_length) goto dir_err_out; - /* Check the consistency of an index entry */ - if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME, - dir_ni->mft_no)) - goto dir_err_out; /* * We perform a case sensitive comparison and if that matches * we are done and return the mft reference of the inode (i.e. @@ -342,43 +338,20 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, dir_ni->mft_no); goto unm_err_out; } - /* Catch multi sector transfer fixup errors. */ - if (unlikely(!ntfs_is_indx_record(ia->magic))) { - ntfs_error(sb, - "Directory index record with vcn 0x%llx is corrupt. Corrupt inode 0x%llx. Run chkdsk.", - vcn, dir_ni->mft_no); - goto unm_err_out; - } - if (le64_to_cpu(ia->index_block_vcn) != vcn) { - ntfs_error(sb, - "Actual VCN (0x%llx) of index buffer is different from expected VCN (0x%llx). Directory inode 0x%llx is corrupt or driver bug.", - le64_to_cpu(ia->index_block_vcn), - vcn, dir_ni->mft_no); - goto unm_err_out; - } - if (le32_to_cpu(ia->index.allocated_size) + 0x18 != - dir_ni->itype.index.block_size) { - ntfs_error(sb, - "Index buffer (VCN 0x%llx) of directory inode 0x%llx has a size (%u) differing from the directory specified size (%u). Directory inode is corrupt or driver bug.", - vcn, dir_ni->mft_no, - le32_to_cpu(ia->index.allocated_size) + 0x18, - dir_ni->itype.index.block_size); - goto unm_err_out; - } index_end = (u8 *)ia + dir_ni->itype.index.block_size; if (index_end > kaddr + PAGE_SIZE) { ntfs_error(sb, - "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.", - vcn, dir_ni->mft_no); + "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.", + vcn, dir_ni->mft_no); goto unm_err_out; } + err = ntfs_index_block_inconsistent(vol, ia, + dir_ni->itype.index.block_size, + vcn, COLLATION_FILE_NAME, + dir_ni->mft_no); + if (err) + goto unm_err_out; index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length); - if (index_end > (u8 *)ia + dir_ni->itype.index.block_size) { - ntfs_error(sb, - "Size of index buffer (VCN 0x%llx) of directory inode 0x%llx exceeds maximum size.", - vcn, dir_ni->mft_no); - goto unm_err_out; - } /* The first index entry. */ ie = (struct index_entry *)((u8 *)&ia->index + le32_to_cpu(ia->index.entries_offset)); @@ -388,15 +361,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, * reach the last entry. */ for (;; ie = (struct index_entry *)((u8 *)ie + le16_to_cpu(ie->length))) { - /* Bounds checks. */ - if ((u8 *)ie < (u8 *)ia || - (u8 *)ie + sizeof(struct index_entry_header) > index_end || - (u8 *)ie + sizeof(struct index_entry_header) + le16_to_cpu(ie->key_length) > - index_end || (u8 *)ie + le16_to_cpu(ie->length) > index_end) { - ntfs_error(sb, "Index entry out of bounds in directory inode 0x%llx.", - dir_ni->mft_no); - goto unm_err_out; - } /* * The last entry cannot contain a name. It can however contain * a pointer to a child node in the B+tree so we just break out. @@ -406,10 +370,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, /* Key length should not be zero if it is not last entry. */ if (!ie->key_length) goto unm_err_out; - /* Check the consistency of an index entry */ - if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME, - dir_ni->mft_no)) - goto unm_err_out; /* * We perform a case sensitive comparison and if that matches * we are done and return the mft reference of the inode (i.e. @@ -892,6 +852,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor) ictx->vcn_size_bits = vol->cluster_size_bits; else ictx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS; + ictx->cr = ir->collation_rule; /* The first index entry. */ next = (struct index_entry *)((u8 *)&ir->index + @@ -929,13 +890,6 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor) if (!next) break; nextdir: - /* Check the consistency of an index entry */ - if (ntfs_index_entry_inconsistent(ictx, vol, next, COLLATION_FILE_NAME, - ndir->mft_no)) { - err = -EIO; - goto out; - } - if (ie_pos < actor->pos) { ie_pos += le16_to_cpu(next->length); continue; diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index c4a4a3e3e599..0cd192752b7c 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -53,11 +53,11 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name, loff_t offset, p_ea_size; unsigned int next; - if (ea_buf_size < sizeof(struct ea_attr)) - goto out; - offset = 0; do { + if (ea_buf_size - offset < sizeof(struct ea_attr)) + break; + p_ea = (const struct ea_attr *)&ea_buf[offset]; next = le32_to_cpu(p_ea->next_entry_offset); p_ea_size = next ? next : (ea_buf_size - offset); @@ -479,13 +479,13 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0) goto out; - if (ea_info_qsize < sizeof(struct ea_attr)) { - err = -EIO; - goto out; - } - offset = 0; do { + if (ea_info_qsize - offset < sizeof(struct ea_attr)) { + err = -EIO; + goto out; + } + p_ea = (const struct ea_attr *)&ea_buf[offset]; next = le32_to_cpu(p_ea->next_entry_offset); ea_size = next ? next : (ea_info_qsize - offset); diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index e8bea22b81a7..6a7b638e523d 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -22,6 +22,7 @@ #include "ea.h" #include "iomap.h" #include "bitmap.h" +#include "volume.h" #include @@ -675,10 +676,29 @@ static int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, static const char *ntfs_get_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done) { - if (!NTFS_I(inode)->target) + struct ntfs_inode *ni = NTFS_I(inode); + char *target; + int err; + + if (!dentry) + return ERR_PTR(-ECHILD); + + if (!ni->target) return ERR_PTR(-EINVAL); - return NTFS_I(inode)->target; + if (ni->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT || + (ni->reparse_tag == IO_REPARSE_TAG_SYMLINK && + !(ni->reparse_flags & cpu_to_le32(SYMLINK_FLAG_RELATIVE)))) { + if (NVolNativeSymlinkRel(ni->vol)) { + err = ntfs_translate_symlink_path(dentry, ni->target, &target); + if (err < 0) + return ERR_PTR(err); + set_delayed_call(done, kfree_link, target); + return target; + } + } + + return ni->target; } static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos, @@ -707,12 +727,21 @@ static int ntfs_ioctl_get_volume_label(struct file *filp, unsigned long arg) { struct ntfs_volume *vol = NTFS_SB(file_inode(filp)->i_sb); char __user *buf = (char __user *)arg; + char label[FSLABEL_MAX]; + ssize_t len; + mutex_lock(&vol->volume_label_lock); if (!vol->volume_label) { - if (copy_to_user(buf, "", 1)) - return -EFAULT; - } else if (copy_to_user(buf, vol->volume_label, - MIN(FSLABEL_MAX, strlen(vol->volume_label) + 1))) + label[0] = '\0'; + len = 0; + } else { + len = strscpy(label, vol->volume_label, sizeof(label)); + if (len == -E2BIG) + len = FSLABEL_MAX - 1; + } + mutex_unlock(&vol->volume_label_lock); + + if (copy_to_user(buf, label, len + 1)) return -EFAULT; return 0; } diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c index 146e011c1a41..c5f2cf75b750 100644 --- a/fs/ntfs/index.c +++ b/fs/ntfs/index.c @@ -28,41 +28,10 @@ * length must have been checked beforehand to not overflow from the * index record. */ -int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, - struct ntfs_volume *vol, const struct index_entry *ie, - __le32 collation_rule, u64 inum) +static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol, + const struct index_entry *ie, + __le32 collation_rule, u64 inum) { - if (icx) { - struct index_header *ih; - u8 *ie_start, *ie_end; - - if (icx->is_in_root) - ih = &icx->ir->index; - else - ih = &icx->ib->index; - - if ((le32_to_cpu(ih->index_length) > le32_to_cpu(ih->allocated_size)) || - (le32_to_cpu(ih->index_length) > icx->block_size)) { - ntfs_error(vol->sb, "%s Index entry(0x%p)'s length is too big.", - icx->is_in_root ? "Index root" : "Index block", - (u8 *)icx->entry); - return -EINVAL; - } - - ie_start = (u8 *)ih + le32_to_cpu(ih->entries_offset); - ie_end = (u8 *)ih + le32_to_cpu(ih->index_length); - - if (ie_start > (u8 *)ie || - ie_end <= (u8 *)ie + le16_to_cpu(ie->length) || - le16_to_cpu(ie->length) > le32_to_cpu(ih->allocated_size) || - le16_to_cpu(ie->length) > icx->block_size) { - ntfs_error(vol->sb, "Index entry(0x%p) is out of range from %s", - (u8 *)icx->entry, - icx->is_in_root ? "index root" : "index block"); - return -EIO; - } - } - if (ie->key_length && ((le16_to_cpu(ie->key_length) + offsetof(struct index_entry, key)) > le16_to_cpu(ie->length))) { @@ -303,6 +272,93 @@ static int ntfs_ie_end(struct index_entry *ie) return ie->flags & INDEX_ENTRY_END || !ie->length; } +static int ntfs_index_header_inconsistent(struct ntfs_volume *vol, + const struct index_header *ih, + u32 bytes_available, u64 inum) +{ + u32 entries_offset, index_length, allocated_size; + + if (bytes_available < sizeof(struct index_header)) { + ntfs_error(vol->sb, + "index block in inode %llu is smaller than an index header.", + (unsigned long long)inum); + return -EIO; + } + + entries_offset = le32_to_cpu(ih->entries_offset); + index_length = le32_to_cpu(ih->index_length); + allocated_size = le32_to_cpu(ih->allocated_size); + + if (entries_offset < sizeof(struct index_header) || + entries_offset > bytes_available) { + ntfs_error(vol->sb, + "Invalid index entry offset in inode %llu.", + (unsigned long long)inum); + return -EIO; + } + + if (index_length <= entries_offset) { + ntfs_error(vol->sb, + "No space for index entries in inode %llu.", + (unsigned long long)inum); + return -EIO; + } + + if (allocated_size < index_length) { + ntfs_error(vol->sb, + "Index entries overflow in inode %llu.", + (unsigned long long)inum); + return -EIO; + } + + if (allocated_size > bytes_available || index_length > bytes_available) { + ntfs_error(vol->sb, + "Index entries in inode %llu exceed the available buffer.", + (unsigned long long)inum); + return -EIO; + } + + return 0; +} + +int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol, + const struct index_header *ih, + __le32 collation_rule, u64 inum) +{ + struct index_entry *ie; + u8 *index_end = (u8 *)ih + le32_to_cpu(ih->index_length); + + for (ie = ntfs_ie_get_first((struct index_header *)ih); + ; ie = ntfs_ie_get_next(ie)) { + if ((u8 *)ie + sizeof(struct index_entry_header) > index_end || + (u8 *)ie + le16_to_cpu(ie->length) > index_end) { + ntfs_error(vol->sb, + "Index entry out of bounds in inode %llu.", + (unsigned long long)inum); + return -EIO; + } + + if (le16_to_cpu(ie->length) < sizeof(struct index_entry_header)) { + ntfs_error(vol->sb, + "Index entry too small in inode %llu.", + inum); + return -EIO; + } + + if (ntfs_ie_end(ie)) + break; + + if (!ie->key_length) + return -EIO; + + if (ntfs_index_entry_inconsistent(vol, ie, + collation_rule, inum)) + return -EIO; + } + + return 0; +} + /* * Find the last entry in the index block */ @@ -437,7 +493,7 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie) * The size of block is assumed to have been checked to be what is * defined in the index root. * - * Returns 0 if no error was found -1 otherwise (with errno unchanged) + * Returns 0 if no error was found, -EIO otherwise * * |<--->| offsetof(struct index_block, index) * | |<--->| sizeof(struct index_header) @@ -452,21 +508,21 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie) * * size(struct index_header) <= ent_offset < ind_length <= alloc_size < bk_size */ -static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx, - struct index_block *ib, s64 vcn) +int ntfs_index_block_inconsistent(struct ntfs_volume *vol, + const struct index_block *ib, + u32 block_size, s64 vcn, __le32 cr, + u64 inum) { u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) + offsetof(struct index_block, index); - struct super_block *sb = icx->idx_ni->vol->sb; - unsigned long long inum = icx->idx_ni->mft_no; + struct super_block *sb = vol->sb; ntfs_debug("Entering\n"); if (!ntfs_is_indx_record(ib->magic)) { - ntfs_error(sb, "Corrupt index block signature: vcn %lld inode %llu\n", - vcn, (unsigned long long)icx->idx_ni->mft_no); - return -1; + vcn, (unsigned long long)inum); + return -EIO; } if (le64_to_cpu(ib->index_block_vcn) != vcn) { @@ -474,34 +530,44 @@ static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx, "Corrupt index block: s64 (%lld) is different from expected s64 (%lld) in inode %llu\n", (long long)le64_to_cpu(ib->index_block_vcn), vcn, inum); - return -1; + return -EIO; } - if (ib_size != icx->block_size) { + if (ib_size != block_size) { ntfs_error(sb, - "Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n", - vcn, inum, ib_size, icx->block_size); - return -1; - } - - if (le32_to_cpu(ib->index.entries_offset) < sizeof(struct index_header)) { - ntfs_error(sb, "Invalid index entry offset in inode %lld\n", inum); - return -1; - } - if (le32_to_cpu(ib->index.index_length) <= - le32_to_cpu(ib->index.entries_offset)) { - ntfs_error(sb, "No space for index entries in inode %lld\n", inum); - return -1; - } - if (le32_to_cpu(ib->index.allocated_size) < - le32_to_cpu(ib->index.index_length)) { - ntfs_error(sb, "Index entries overflow in inode %lld\n", inum); - return -1; + "Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n", + vcn, inum, ib_size, block_size); + return -EIO; } + if (ntfs_index_header_inconsistent(vol, &ib->index, + block_size - + offsetof(struct index_block, index), + inum)) + return -EIO; + if (ntfs_index_entries_inconsistent(vol, &ib->index, cr, inum)) + return -EIO; return 0; } +int ntfs_index_root_inconsistent(struct ntfs_volume *vol, + const struct attr_record *a, + const struct index_root *ir, u64 inum) +{ + u32 value_length = le32_to_cpu(a->data.resident.value_length); + + if (value_length < offsetof(struct index_root, index)) { + ntfs_error(vol->sb, "$INDEX_ROOT in inode %llu is too small.", + (unsigned long long)inum); + return -EIO; + } + + return ntfs_index_header_inconsistent(vol, &ir->index, + value_length - + offsetof(struct index_root, index), + inum); +} + static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name, u32 name_len, struct ntfs_attr_search_ctx **ctx) { @@ -665,13 +731,14 @@ static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_bl else ntfs_error(icx->idx_ni->vol->sb, "Failed to read full index block at %lld\n", pos); - return -1; + return -EIO; } post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size); - if (ntfs_index_block_inconsistent(icx, dst, vcn)) - return -1; - + if (ntfs_index_block_inconsistent(icx->idx_ni->vol, dst, + icx->block_size, vcn, icx->cr, + icx->idx_ni->mft_no)) + return -EIO; return 0; } @@ -1173,6 +1240,8 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx) struct index_entry *ie; struct index_block *ib = NULL; s64 new_ib_vcn; + u32 index_length; + u32 old_value_length; int ix_root_size; int ret = 0; @@ -1220,6 +1289,21 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx) goto clear_bmp; } + old_value_length = le32_to_cpu(ctx->attr->data.resident.value_length); + index_length = le32_to_cpu(ir->index.entries_offset) + + sizeof(struct index_entry_header) + sizeof(s64); + ix_root_size = offsetof(struct index_root, index) + index_length; + /* Grow the resident value before publishing the larger root header. */ + if (ix_root_size > old_value_length) { + ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size); + if (ret) + goto resize_failed; + + icx->idx_ni->data_size = ix_root_size; + icx->idx_ni->initialized_size = ix_root_size; + icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7; + } + ntfs_ir_nill(ir); ie = ntfs_ie_get_first(&ir->index); @@ -1228,48 +1312,49 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx) ir->index.flags = LARGE_INDEX; NInoSetIndexAllocPresent(icx->idx_ni); - ir->index.index_length = cpu_to_le32(le32_to_cpu(ir->index.entries_offset) + - le16_to_cpu(ie->length)); + ir->index.index_length = cpu_to_le32(index_length); ir->index.allocated_size = ir->index.index_length; - ix_root_size = sizeof(struct index_root) - sizeof(struct index_header) + - le32_to_cpu(ir->index.allocated_size); - ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size); - if (ret) { - /* - * When there is no space to build a non-resident - * index, we may have to move the root to an extent - */ - if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) { - ntfs_attr_put_search_ctx(ctx); - ctx = NULL; - ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx); - if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size - - le32_to_cpu(ctx->attr->data.resident.value_length))) { - if (ntfs_attrlist_update(ctx->base_ntfs_ino ? - ctx->base_ntfs_ino : ctx->ntfs_ino)) - goto clear_bmp; - ntfs_attr_put_search_ctx(ctx); - ctx = NULL; - goto retry; - } - } - goto clear_bmp; - } else { - icx->idx_ni->data_size = icx->idx_ni->initialized_size = ix_root_size; - icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7; + if (ix_root_size <= old_value_length) { + ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size); + if (ret) + goto resize_failed; + + icx->idx_ni->data_size = ix_root_size; + icx->idx_ni->initialized_size = ix_root_size; + icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7; } ntfs_ie_set_vcn(ie, new_ib_vcn); + goto err_out; +resize_failed: + /* + * When there is no space to build a non-resident + * index, we may have to move the root to an extent + */ + if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) { + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx); + if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size - + le32_to_cpu(ctx->attr->data.resident.value_length))) { + if (ntfs_attrlist_update(ctx->base_ntfs_ino ? + ctx->base_ntfs_ino : ctx->ntfs_ino)) + goto clear_bmp; + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + goto retry; + } + } +clear_bmp: + ntfs_ibm_clear(icx, new_ib_vcn); + goto err_out; err_out: kvfree(ib); if (ctx) ntfs_attr_put_search_ctx(ctx); out: return ret; -clear_bmp: - ntfs_ibm_clear(icx, new_ib_vcn); - goto err_out; } /* @@ -1280,9 +1365,16 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx) static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size) { int ret; + u32 old_allocated_size; + bool shrink; ntfs_debug("Entering\n"); + old_allocated_size = le32_to_cpu(icx->ir->index.allocated_size); + shrink = data_size < old_allocated_size; + if (shrink) + icx->ir->index.allocated_size = cpu_to_le32(data_size); + /* * INDEX_ROOT must be resident and its entries can be moved to * struct index_block, so ENOSPC isn't a real error. @@ -1294,9 +1386,14 @@ static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size) if (!icx->ir) return -ENOENT; - icx->ir->index.allocated_size = cpu_to_le32(data_size); - } else if (ret != -ENOSPC) - ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT"); + if (!shrink) + icx->ir->index.allocated_size = cpu_to_le32(data_size); + } else { + if (shrink) + icx->ir->index.allocated_size = cpu_to_le32(old_allocated_size); + if (ret != -ENOSPC) + ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT"); + } return ret; } diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h index e68d6fabaf9f..9a03f53bba47 100644 --- a/fs/ntfs/index.h +++ b/fs/ntfs/index.h @@ -89,8 +89,16 @@ struct ntfs_index_context { bool sync_write; }; -int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol, - const struct index_entry *ie, __le32 collation_rule, u64 inum); +int ntfs_index_root_inconsistent(struct ntfs_volume *vol, + const struct attr_record *a, + const struct index_root *ir, u64 inum); +int ntfs_index_block_inconsistent(struct ntfs_volume *vol, + const struct index_block *ib, + u32 block_size, s64 vcn, + __le32 cr, u64 inum); +int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol, + const struct index_header *ih, + __le32 collation_rule, u64 inum); struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name, u32 name_len); void ntfs_index_ctx_put(struct ntfs_index_context *ictx); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 360bebd1ee3f..c2715521e562 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -488,6 +488,8 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni) ni->flags = 0; ni->mft_lcn[0] = LCN_RL_NOT_MAPPED; ni->mft_lcn_count = 0; + ni->reparse_tag = 0; + ni->reparse_flags = 0; ni->target = NULL; ni->i_dealloc_clusters = 0; } @@ -848,6 +850,12 @@ static int ntfs_read_locked_inode(struct inode *vi) a->data.resident.value_offset), le32_to_cpu( a->data.resident.value_length)); + /* A resident list is not validated on load; check it now. */ + if (!ntfs_attr_list_is_valid(ni->attr_list, + ni->attr_list_size)) { + ntfs_error(vi->i_sb, "Corrupt attribute list."); + goto unm_err_out; + } } } skip_attr_list_load: @@ -857,8 +865,26 @@ static int ntfs_read_locked_inode(struct inode *vi) ntfs_ea_get_wsl_inode(vi, &dev, flags); } - if (m->flags & MFT_RECORD_IS_DIRECTORY) { + if (ni->flags & FILE_ATTR_REPARSE_POINT) { + unsigned int mode; + + mode = ntfs_make_symlink(ni); + if (mode) + vi->i_mode |= mode; + else { + vi->i_mode &= ~S_IFLNK; + if (m->flags & MFT_RECORD_IS_DIRECTORY) + vi->i_mode |= S_IFDIR; + else + vi->i_mode |= S_IFREG; + } + } else if (m->flags & MFT_RECORD_IS_DIRECTORY) { vi->i_mode |= S_IFDIR; + } else { + vi->i_mode |= S_IFREG; + } + + if (S_ISDIR(vi->i_mode)) { /* * Apply the directory permissions mask set in the mount * options. @@ -868,18 +894,6 @@ static int ntfs_read_locked_inode(struct inode *vi) if (vi->i_nlink > 1) set_nlink(vi, 1); } else { - if (ni->flags & FILE_ATTR_REPARSE_POINT) { - unsigned int mode; - - mode = ntfs_make_symlink(ni); - if (mode) - vi->i_mode |= mode; - else { - vi->i_mode &= ~S_IFLNK; - vi->i_mode |= S_IFREG; - } - } else - vi->i_mode |= S_IFREG; /* Apply the file permissions mask set in the mount options. */ vi->i_mode &= ~vol->fmask; } @@ -888,9 +902,8 @@ static int ntfs_read_locked_inode(struct inode *vi) * If an attribute list is present we now have the attribute list value * in ntfs_ino->attr_list and it is ntfs_ino->attr_list_size bytes. */ - if (S_ISDIR(vi->i_mode)) { + if (m->flags & MFT_RECORD_IS_DIRECTORY) { struct index_root *ir; - u8 *ir_end, *index_end; view_index_meta: /* It is a directory, find index root attribute. */ @@ -940,10 +953,9 @@ static int ntfs_read_locked_inode(struct inode *vi) } ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset)); - ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length); - index_end = (u8 *)&ir->index + - le32_to_cpu(ir->index.index_length); - if (index_end > ir_end) { + if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no) || + ntfs_index_entries_inconsistent(ni->vol, &ir->index, + ir->collation_rule, ni->mft_no)) { ntfs_error(vi->i_sb, "Directory index is corrupt."); goto unm_err_out; } @@ -1014,7 +1026,7 @@ static int ntfs_read_locked_inode(struct inode *vi) m = NULL; ctx = NULL; /* Setup the operations for this inode. */ - ntfs_set_vfs_operations(vi, S_IFDIR, 0); + ntfs_set_vfs_operations(vi, vi->i_mode, 0); if (ir->index.flags & LARGE_INDEX) NInoSetIndexAllocPresent(ni); } else { @@ -1195,6 +1207,9 @@ static int ntfs_read_locked_inode(struct inode *vi) else vi->i_blocks = ni->allocated_size >> 9; + if (S_ISLNK(vi->i_mode) && ni->target) + vi->i_size = strlen(ni->target); + ntfs_debug("Done."); return 0; unm_err_out: @@ -1483,7 +1498,6 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) struct attr_record *a; struct ntfs_attr_search_ctx *ctx; struct index_root *ir; - u8 *ir_end, *index_end; int err = 0; ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no); @@ -1534,9 +1548,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) } ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset)); - ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length); - index_end = (u8 *)&ir->index + le32_to_cpu(ir->index.index_length); - if (index_end > ir_end) { + if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no) || + ntfs_index_entries_inconsistent(vol, &ir->index, + ir->collation_rule, ni->mft_no)) { ntfs_error(vi->i_sb, "Index is corrupt."); goto unm_err_out; } @@ -1994,10 +2008,7 @@ int ntfs_read_inode_mount(struct inode *vi) /* Catch the end of the attribute list. */ if ((u8 *)al_entry == al_end) goto em_put_err_out; - if (!al_entry->length) - goto em_put_err_out; - if ((u8 *)al_entry + 6 > al_end || - (u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end) + if (!ntfs_attr_list_entry_is_valid(al_entry, al_end)) goto em_put_err_out; next_al_entry = (struct attr_list_entry *)((u8 *)al_entry + le16_to_cpu(al_entry->length)); @@ -2367,6 +2378,14 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root) seq_puts(sf, ",discard"); if (NVolDisableSparse(vol)) seq_puts(sf, ",disable_sparse"); + if (NVolNativeSymlinkRel(vol)) + seq_puts(sf, ",native_symlink=rel"); + else + seq_puts(sf, ",native_symlink=raw"); + if (NVolSymlinkNative(vol)) + seq_puts(sf, ",symlink=native"); + else + seq_puts(sf, ",symlink=wsl"); if (vol->sb->s_flags & SB_POSIXACL) seq_puts(sf, ",acl"); return 0; diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index 67942b97fac6..9aacd5787ffe 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -142,6 +142,8 @@ struct ntfs_inode { struct ntfs_inode *base_ntfs_ino; } ext; unsigned int i_dealloc_clusters; + __le32 reparse_tag; + __le32 reparse_flags; char *target; }; diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c index dc7d8c893a69..52eecf5cb256 100644 --- a/fs/ntfs/iomap.c +++ b/fs/ntfs/iomap.c @@ -89,7 +89,6 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo u32 attr_len; int err = 0; char *kattr; - struct page *ipage; if (NInoAttr(ni)) base_ni = ni->ext.base_ntfs_ino; @@ -130,18 +129,10 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); - ipage = alloc_page(GFP_NOFS | __GFP_ZERO); - if (!ipage) { - err = -ENOMEM; - goto out; - } - - memcpy(page_address(ipage), kattr, attr_len); iomap->type = IOMAP_INLINE; - iomap->inline_data = page_address(ipage); + iomap->inline_data = kattr; iomap->offset = 0; iomap->length = attr_len; - iomap->private = ipage; out: if (ctx) @@ -286,21 +277,8 @@ static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t leng srcmap, true); } -static int ntfs_read_iomap_end(struct inode *inode, loff_t pos, loff_t length, - ssize_t written, unsigned int flags, struct iomap *iomap) -{ - if (iomap->type == IOMAP_INLINE) { - struct page *ipage = iomap->private; - - put_page(ipage); - } - - return written; -} - const struct iomap_ops ntfs_read_iomap_ops = { .iomap_begin = ntfs_read_iomap_begin, - .iomap_end = ntfs_read_iomap_end, }; /* @@ -358,7 +336,6 @@ static const struct iomap_ops ntfs_zero_read_iomap_ops = { const struct iomap_ops ntfs_seek_iomap_ops = { .iomap_begin = ntfs_seek_iomap_begin, - .iomap_end = ntfs_read_iomap_end, }; int ntfs_dio_zero_range(struct inode *inode, loff_t offset, loff_t length) @@ -659,7 +636,6 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset, u32 attr_len; int err = 0; char *kattr; - struct page *ipage; ctx = ntfs_attr_get_search_ctx(ni, NULL); if (!ctx) { @@ -680,24 +656,18 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset, attr_len = le32_to_cpu(a->data.resident.value_length); kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset); - ipage = alloc_page(GFP_NOFS | __GFP_ZERO); - if (!ipage) { - err = -ENOMEM; - goto out; - } - - memcpy(page_address(ipage), kattr, attr_len); iomap->type = IOMAP_INLINE; - iomap->inline_data = page_address(ipage); + iomap->inline_data = kattr; iomap->offset = 0; - /* iomap requires there is only one INLINE_DATA extent */ iomap->length = attr_len; - iomap->private = ipage; out: if (ctx) ntfs_attr_put_search_ctx(ctx); - mutex_unlock(&ni->mrec_lock); + + if (err) + mutex_unlock(&ni->mrec_lock); + return err; } @@ -778,43 +748,10 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos, unsigned int flags, struct iomap *iomap) { struct ntfs_inode *ni = NTFS_I(inode); - struct ntfs_attr_search_ctx *ctx; - u32 attr_len; - int err; - char *kattr; - struct page *ipage = iomap->private; - mutex_lock(&ni->mrec_lock); - ctx = ntfs_attr_get_search_ctx(ni, NULL); - if (!ctx) { - written = -ENOMEM; - goto err_out; - } - - err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, - CASE_SENSITIVE, 0, NULL, 0, ctx); - if (err) { - if (err == -ENOENT) - err = -EIO; - written = err; - goto err_out; - } - - /* The total length of the attribute value. */ - attr_len = le32_to_cpu(ctx->attr->data.resident.value_length); - if (pos >= attr_len || pos + written > attr_len) - goto err_out; - - kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); - memcpy(kattr + pos, iomap_inline_data(iomap, pos), written); - mark_mft_record_dirty(ctx->ntfs_ino); -err_out: - if (ctx) - ntfs_attr_put_search_ctx(ctx); - put_page(ipage); + mark_mft_record_dirty(ni); mutex_unlock(&ni->mrec_lock); return written; - } static int ntfs_write_iomap_end(struct inode *inode, loff_t pos, loff_t length, diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h index d94f914e830f..9438fd9b668e 100644 --- a/fs/ntfs/layout.h +++ b/fs/ntfs/layout.h @@ -2267,6 +2267,8 @@ enum { IO_REPARSE_PLUGIN_SELECT = cpu_to_le32(0xffff0fff), }; +#define SYMLINK_FLAG_RELATIVE 1 + /* * struct reparse_point - $REPARSE_POINT attribute content (0xc0)\ * @@ -2287,6 +2289,23 @@ struct reparse_point { u8 reparse_data[]; } __packed; +struct mount_point_reparse_data { + __le16 substitute_name_offset; + __le16 substitute_name_length; + __le16 print_name_offset; + __le16 print_name_length; + __le16 path_buffer[]; +} __packed; + +struct symlink_reparse_data { + __le16 substitute_name_offset; + __le16 substitute_name_length; + __le16 print_name_offset; + __le16 print_name_length; + __le32 flags; + __le16 path_buffer[]; +} __packed; + /* * struct ea_information - $EA_INFORMATION attribute content (0xd0) * diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c index d3f25d8e29f9..024ddee42dc8 100644 --- a/fs/ntfs/logfile.c +++ b/fs/ntfs/logfile.c @@ -132,7 +132,7 @@ static bool ntfs_check_restart_area(struct inode *vi, struct restart_page_header { u64 file_size; struct restart_area *ra; - u16 ra_ofs, ra_len, ca_ofs; + u32 ra_ofs, ra_len, ca_ofs; u8 fs_bits; ntfs_debug("Entering."); @@ -622,8 +622,7 @@ bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp) ntfs_debug("Done."); return true; err_out: - if (rstr1_ph) - kvfree(rstr1_ph); + kvfree(rstr1_ph); return false; } diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index a7d10ee41b34..a5019e80951b 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -743,23 +743,6 @@ static int ntfs_test_inode_wb(struct inode *vi, u64 ino, void *data) * * If the mft record is not a FILE record or it is a base mft record, we can * safely write it and return 'true'. - * - * We now know the mft record is an extent mft record. We check if the inode - * corresponding to its base mft record is in icache. If it is not, we cannot - * safely determine the state of the extent inode, so we return 'false'. - * - * We now have the base inode for the extent mft record. We check if it has an - * ntfs inode for the extent mft record attached. If not, it is safe to write - * the extent mft record and we return 'true'. - * - * If the extent inode is attached, we check if it is dirty. If so, we return - * 'false' (letting the standard write_inode path handle it). - * - * If it is not dirty, we attempt to lock the extent mft record. If the lock - * was already taken, it is not safe to write and we return 'false'. - * - * If we manage to obtain the lock we have exclusive access to the extent mft - * record. We set @locked_ni to the now locked ntfs inode and return 'true'. */ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, const struct mft_record *m, struct ntfs_inode **locked_ni, @@ -768,8 +751,7 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, struct super_block *sb = vol->sb; struct inode *mft_vi = vol->mft_ino; struct inode *vi; - struct ntfs_inode *ni, *eni, **extent_nis; - int i; + struct ntfs_inode *ni; struct ntfs_attr na = {0}; ntfs_debug("Entering for inode 0x%llx.", mft_no); @@ -849,100 +831,10 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, mft_no); return true; } - /* - * This is an extent mft record. Check if the inode corresponding to - * its base mft record is in icache and obtain a reference to it if it - * is. - */ - na.mft_no = MREF_LE(m->base_mft_record); - na.state = 0; - ntfs_debug("Mft record 0x%llx is an extent record. Looking for base inode 0x%llx in icache.", - mft_no, na.mft_no); - if (!na.mft_no) { - /* Balance the below iput(). */ - vi = igrab(mft_vi); - WARN_ON(vi != mft_vi); - } else { - vi = find_inode_nowait(sb, na.mft_no, ntfs_test_inode_wb, &na); - if (na.state == NI_BeingDeleted || na.state == NI_BeingCreated) - return false; - } - if (!vi) - return false; - ntfs_debug("Base inode 0x%llx is in icache.", na.mft_no); - /* - * The base inode is in icache. Check if it has the extent inode - * corresponding to this extent mft record attached. - */ - ni = NTFS_I(vi); - mutex_lock(&ni->extent_lock); - if (ni->nr_extents <= 0) { - /* - * The base inode has no attached extent inodes, write this - * extent mft record. - */ - mutex_unlock(&ni->extent_lock); - *ref_vi = vi; - ntfs_debug("Base inode 0x%llx has no attached extent inodes, write the extent record.", - na.mft_no); - return true; - } - /* Iterate over the attached extent inodes. */ - extent_nis = ni->ext.extent_ntfs_inos; - for (eni = NULL, i = 0; i < ni->nr_extents; ++i) { - if (mft_no == extent_nis[i]->mft_no) { - /* - * Found the extent inode corresponding to this extent - * mft record. - */ - eni = extent_nis[i]; - break; - } - } - /* - * If the extent inode was not attached to the base inode, write this - * extent mft record. - */ - if (!eni) { - mutex_unlock(&ni->extent_lock); - *ref_vi = vi; - ntfs_debug("Extent inode 0x%llx is not attached to its base inode 0x%llx, write the extent record.", - mft_no, na.mft_no); - return true; - } - ntfs_debug("Extent inode 0x%llx is attached to its base inode 0x%llx.", - mft_no, na.mft_no); - /* Take a reference to the extent ntfs inode. */ - atomic_inc(&eni->count); - mutex_unlock(&ni->extent_lock); - - /* if extent inode is dirty, write_inode will write it */ - if (NInoDirty(eni)) { - atomic_dec(&eni->count); - *ref_vi = vi; - return false; - } - - /* - * Found the extent inode coresponding to this extent mft record. - * Try to take the mft record lock. - */ - if (unlikely(!mutex_trylock(&eni->mrec_lock))) { - atomic_dec(&eni->count); - *ref_vi = vi; - ntfs_debug("Extent mft record 0x%llx is already locked, do not write it.", - mft_no); - return false; - } - ntfs_debug("Managed to lock extent mft record 0x%llx, write it.", - mft_no); - /* - * The write has to occur while we hold the mft record lock so return - * the locked extent ntfs inode. - */ - *locked_ni = eni; - return true; + ntfs_debug("Mft record 0x%llx is an extent record, skip it.", + mft_no); + return false; } static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk."; @@ -2791,19 +2683,6 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w unsigned int mft_record_off = 0; s64 vcn_off = vcn; - /* - * Skip $MFT extent mft records and let them being written - * by writeback to avioid deadlocks. the $MFT runlist - * lock must be taken before $MFT extent mrec_lock is taken. - */ - if (tni && tni->nr_extents < 0 && - tni->ext.base_ntfs_ino == NTFS_I(vol->mft_ino)) { - mutex_unlock(&tni->mrec_lock); - atomic_dec(&tni->count); - iput(vol->mft_ino); - continue; - } - /* * The record should be written. If a locked ntfs * inode was returned, add it to the array of locked diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 3e21753b9f88..a19626a135bd 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -393,7 +393,7 @@ static int ntfs_sd_add_everyone(struct ntfs_inode *ni) static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dir, __le16 *name, u8 name_len, mode_t mode, dev_t dev, - __le16 *target, int target_len) + const char *target, int target_len) { struct ntfs_inode *dir_ni = NTFS_I(dir); struct ntfs_volume *vol = dir_ni->vol; @@ -607,7 +607,10 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d goto err_out; if (S_ISLNK(mode)) { - err = ntfs_reparse_set_wsl_symlink(ni, target, target_len); + if (NVolSymlinkNative(vol)) + err = ntfs_reparse_set_native_symlink(ni, target, target_len); + else + err = ntfs_reparse_set_wsl_symlink(ni, target, target_len); if (!err) rollback_reparse = true; } else if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISSOCK(mode) || @@ -1408,9 +1411,7 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, int err = 0; struct ntfs_inode *ni; __le16 *usrc; - __le16 *utarget; int usrc_len; - int utarget_len; int symlen = strlen(symname); if (NVolShutdown(vol)) @@ -1431,23 +1432,12 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, goto out; } - utarget_len = ntfs_nlstoucs(vol, symname, symlen, &utarget, - PATH_MAX); - if (utarget_len < 0) { - if (utarget_len != -ENAMETOOLONG) - ntfs_error(sb, "Failed to convert target name to Unicode."); - err = -ENOMEM; - kmem_cache_free(ntfs_name_cache, usrc); - goto out; - } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); ni = __ntfs_create(idmap, dir, usrc, usrc_len, S_IFLNK | 0777, 0, - utarget, utarget_len); + symname, symlen); kmem_cache_free(ntfs_name_cache, usrc); - kvfree(utarget); if (IS_ERR(ni)) { err = PTR_ERR(ni); goto out; @@ -1531,8 +1521,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir, if (uname_len < 0) { if (uname_len != -ENAMETOOLONG) ntfs_error(sb, "Failed to convert name to unicode."); - err = -ENOMEM; - goto out; + return -ENOMEM; } if (!(vol->vol_flags & VOLUME_IS_DIRTY)) @@ -1562,7 +1551,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir, mutex_unlock(&ni->mrec_lock); out: - kfree(uname); + kmem_cache_free(ntfs_name_cache, uname); return err; } diff --git a/fs/ntfs/quota.c b/fs/ntfs/quota.c deleted file mode 100644 index b443243b58fb..000000000000 --- a/fs/ntfs/quota.c +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * NTFS kernel quota ($Quota) handling. - * - * Copyright (c) 2004 Anton Altaparmakov - */ - -#include "index.h" -#include "quota.h" -#include "debug.h" -#include "ntfs.h" - -/* - * ntfs_mark_quotas_out_of_date - mark the quotas out of date on an ntfs volume - * @vol: ntfs volume on which to mark the quotas out of date - * - * Mark the quotas out of date on the ntfs volume @vol and return 'true' on - * success and 'false' on error. - */ -bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol) -{ - struct ntfs_index_context *ictx; - struct quota_control_entry *qce; - const __le32 qid = QUOTA_DEFAULTS_ID; - int err; - - ntfs_debug("Entering."); - if (NVolQuotaOutOfDate(vol)) - goto done; - if (!vol->quota_ino || !vol->quota_q_ino) { - ntfs_error(vol->sb, "Quota inodes are not open."); - return false; - } - inode_lock(vol->quota_q_ino); - ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), I30, 4); - if (!ictx) { - ntfs_error(vol->sb, "Failed to get index context."); - goto err_out; - } - err = ntfs_index_lookup(&qid, sizeof(qid), ictx); - if (err) { - if (err == -ENOENT) - ntfs_error(vol->sb, "Quota defaults entry is not present."); - else - ntfs_error(vol->sb, "Lookup of quota defaults entry failed."); - goto err_out; - } - if (ictx->data_len < offsetof(struct quota_control_entry, sid)) { - ntfs_error(vol->sb, "Quota defaults entry size is invalid. Run chkdsk."); - goto err_out; - } - qce = (struct quota_control_entry *)ictx->data; - if (le32_to_cpu(qce->version) != QUOTA_VERSION) { - ntfs_error(vol->sb, - "Quota defaults entry version 0x%x is not supported.", - le32_to_cpu(qce->version)); - goto err_out; - } - ntfs_debug("Quota defaults flags = 0x%x.", le32_to_cpu(qce->flags)); - /* If quotas are already marked out of date, no need to do anything. */ - if (qce->flags & QUOTA_FLAG_OUT_OF_DATE) - goto set_done; - /* - * If quota tracking is neither requested, nor enabled and there are no - * pending deletes, no need to mark the quotas out of date. - */ - if (!(qce->flags & (QUOTA_FLAG_TRACKING_ENABLED | - QUOTA_FLAG_TRACKING_REQUESTED | - QUOTA_FLAG_PENDING_DELETES))) - goto set_done; - /* - * Set the QUOTA_FLAG_OUT_OF_DATE bit thus marking quotas out of date. - * This is verified on WinXP to be sufficient to cause windows to - * rescan the volume on boot and update all quota entries. - */ - qce->flags |= QUOTA_FLAG_OUT_OF_DATE; - /* Ensure the modified flags are written to disk. */ - ntfs_index_entry_mark_dirty(ictx); -set_done: - ntfs_index_ctx_put(ictx); - inode_unlock(vol->quota_q_ino); - /* - * We set the flag so we do not try to mark the quotas out of date - * again on remount. - */ - NVolSetQuotaOutOfDate(vol); -done: - ntfs_debug("Done."); - return true; -err_out: - if (ictx) - ntfs_index_ctx_put(ictx); - inode_unlock(vol->quota_q_ino); - return false; -} diff --git a/fs/ntfs/quota.h b/fs/ntfs/quota.h deleted file mode 100644 index 4b7322661a32..000000000000 --- a/fs/ntfs/quota.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Defines for NTFS kernel quota ($Quota) handling. - * - * Copyright (c) 2004 Anton Altaparmakov - */ - -#ifndef _LINUX_NTFS_QUOTA_H -#define _LINUX_NTFS_QUOTA_H - -#include "volume.h" - -bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol); - -#endif /* _LINUX_NTFS_QUOTA_H */ diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 74713716813f..fa523dc3691e 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -24,6 +24,47 @@ struct wsl_link_reparse_data { char link[]; }; +static bool reparse_name_is_valid(size_t size, size_t name_off, u16 len) +{ + if ((name_off | len) & 1) + return false; + + return name_off + len <= size; +} + +/* + * Windows-native reparse payloads store pathnames as UTF-16 strings with '\\' + * separators. Convert the on-disk UTF-16 target into the mount's NLS and + * normalize path separators. + */ +static int ntfs_reparse_target_to_nls(struct ntfs_volume *vol, + const __le16 *uname, u16 ulen, + char **target) +{ + int err, i; + + *target = NULL; + ulen >>= 1; + if (!ulen) + return -EINVAL; + + if (!uname[ulen - 1]) + ulen--; + + err = ntfs_ucstonls(vol, uname, ulen, (unsigned char **)target, 0); + if (err < 0) { + ntfs_attr_name_free((unsigned char **)target); + return err; + } + + for (i = 0; i < err; i++) { + if ((*target)[i] == '\\') + (*target)[i] = '/'; + } + + return 0; +} + /* Index entry in $Extend/$Reparse */ struct reparse_index { struct index_entry_header header; @@ -38,8 +79,10 @@ __le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0}; * Check if the reparse point attribute buffer is valid. * Returns true if valid, false otherwise. */ -static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni, - const struct reparse_point *reparse_attr, size_t size) +static bool valid_reparse_buffer(struct ntfs_inode *ni, + const struct reparse_point *reparse_attr, + size_t size, + size_t payload_min_len) { size_t expected; @@ -50,6 +93,11 @@ static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni, if (size < sizeof(struct reparse_point)) return false; + /* The payload must contain the fixed fields for the current tag. */ + if (payload_min_len && + le16_to_cpu(reparse_attr->reparse_data_length) < payload_min_len) + return false; + /* Reserved zero tag is invalid */ if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO) return false; @@ -79,35 +127,97 @@ static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni, static bool valid_reparse_data(struct ntfs_inode *ni, const struct reparse_point *reparse_attr, size_t size) { - const struct wsl_link_reparse_data *wsl_reparse_data = - (const struct wsl_link_reparse_data *)reparse_attr->reparse_data; - unsigned int data_len = le16_to_cpu(reparse_attr->reparse_data_length); - - if (ntfs_is_valid_reparse_buffer(ni, reparse_attr, size) == false) + if (size < sizeof(*reparse_attr)) return false; switch (reparse_attr->reparse_tag) { - case IO_REPARSE_TAG_LX_SYMLINK: - if (data_len <= sizeof(wsl_reparse_data->type) || - wsl_reparse_data->type != cpu_to_le32(2)) + case IO_REPARSE_TAG_MOUNT_POINT: + { + struct mount_point_reparse_data *data; + size_t data_offs; + + if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data))) + return false; + + data = (struct mount_point_reparse_data *)reparse_attr->reparse_data; + data_offs = offsetof(struct reparse_point, reparse_data) + + offsetof(struct mount_point_reparse_data, path_buffer); + + if (!reparse_name_is_valid(size, + data_offs + + le16_to_cpu(data->substitute_name_offset), + le16_to_cpu(data->substitute_name_length)) || + !reparse_name_is_valid(size, + data_offs + + le16_to_cpu(data->print_name_offset), + le16_to_cpu(data->print_name_length))) return false; break; + } + case IO_REPARSE_TAG_SYMLINK: + { + struct symlink_reparse_data *data; + size_t data_offs; + + if (!valid_reparse_buffer(ni, reparse_attr, size, + sizeof(*data))) + return false; + + data = (struct symlink_reparse_data *)reparse_attr->reparse_data; + data_offs = offsetof(struct reparse_point, reparse_data) + + offsetof(struct symlink_reparse_data, path_buffer); + + if (!reparse_name_is_valid(size, + data_offs + + le16_to_cpu(data->substitute_name_offset), + le16_to_cpu(data->substitute_name_length)) || + !reparse_name_is_valid(size, + data_offs + + le16_to_cpu(data->print_name_offset), + le16_to_cpu(data->print_name_length))) + return false; + break; + } + case IO_REPARSE_TAG_LX_SYMLINK: + { + struct wsl_link_reparse_data *data; + + if (!valid_reparse_buffer(ni, reparse_attr, size, + sizeof(*data))) + return false; + + data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data; + + if (le16_to_cpu(reparse_attr->reparse_data_length) <= sizeof(data->type) || + data->type != cpu_to_le32(2)) + return false; + break; + } case IO_REPARSE_TAG_AF_UNIX: case IO_REPARSE_TAG_LX_FIFO: case IO_REPARSE_TAG_LX_CHR: case IO_REPARSE_TAG_LX_BLK: - if (data_len || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN)) + if (!valid_reparse_buffer(ni, reparse_attr, size, 0)) return false; + if (le16_to_cpu(reparse_attr->reparse_data_length) || + !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN)) + return false; + break; + default: + if (!valid_reparse_buffer(ni, reparse_attr, size, 0)) + return false; + break; } return true; } -static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr) +static unsigned int ntfs_reparse_tag_mode(__le32 reparse_tag) { unsigned int mode = 0; - switch (reparse_attr->reparse_tag) { + switch (reparse_tag) { + case IO_REPARSE_TAG_MOUNT_POINT: case IO_REPARSE_TAG_SYMLINK: case IO_REPARSE_TAG_LX_SYMLINK: mode = S_IFLNK; @@ -134,32 +244,75 @@ static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr) unsigned int ntfs_make_symlink(struct ntfs_inode *ni) { s64 attr_size = 0; + int err; unsigned int lth; struct reparse_point *reparse_attr; - struct wsl_link_reparse_data *wsl_link_data; unsigned int mode = 0; + kvfree(ni->target); + ni->target = NULL; + ni->reparse_tag = 0; + ni->reparse_flags = 0; + reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0, &attr_size); - if (reparse_attr && attr_size && + if (reparse_attr && valid_reparse_data(ni, reparse_attr, attr_size)) { + err = -EINVAL; + switch (reparse_attr->reparse_tag) { + case IO_REPARSE_TAG_MOUNT_POINT: + { + struct mount_point_reparse_data *data = + (struct mount_point_reparse_data *)reparse_attr->reparse_data; + const __le16 *name = (const __le16 *)((u8 *)data->path_buffer + + le16_to_cpu(data->substitute_name_offset)); + + err = ntfs_reparse_target_to_nls(ni->vol, + name, + le16_to_cpu(data->substitute_name_length), + &ni->target); + break; + } + case IO_REPARSE_TAG_SYMLINK: + { + struct symlink_reparse_data *data = + (struct symlink_reparse_data *)reparse_attr->reparse_data; + const __le16 *name = (const __le16 *)((u8 *)data->path_buffer + + le16_to_cpu(data->substitute_name_offset)); + + err = ntfs_reparse_target_to_nls(ni->vol, + name, + le16_to_cpu(data->substitute_name_length), + &ni->target); + if (!err) + ni->reparse_flags = data->flags; + break; + } case IO_REPARSE_TAG_LX_SYMLINK: - wsl_link_data = + { + struct wsl_link_reparse_data *wsl_link_data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data; + if (wsl_link_data->type == cpu_to_le32(2)) { lth = le16_to_cpu(reparse_attr->reparse_data_length) - - sizeof(wsl_link_data->type); + sizeof(wsl_link_data->type); ni->target = kvzalloc(lth + 1, GFP_NOFS); if (ni->target) { memcpy(ni->target, wsl_link_data->link, lth); ni->target[lth] = 0; - mode = ntfs_reparse_tag_mode(reparse_attr); + err = 0; } } break; + } default: - mode = ntfs_reparse_tag_mode(reparse_attr); + err = 0; + } + + if (!err) { + mode = ntfs_reparse_tag_mode(reparse_attr->reparse_tag); + ni->reparse_tag = reparse_attr->reparse_tag; } } else ni->flags &= ~FILE_ATTR_REPARSE_POINT; @@ -184,8 +337,9 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi), AT_REPARSE_POINT, NULL, 0, &attr_size); - if (reparse_attr && attr_size) { + if (reparse_attr && attr_size >= sizeof(*reparse_attr)) { switch (reparse_attr->reparse_tag) { + case IO_REPARSE_TAG_MOUNT_POINT: case IO_REPARSE_TAG_SYMLINK: case IO_REPARSE_TAG_LX_SYMLINK: dt_type = DT_LNK; @@ -211,6 +365,115 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr return dt_type; } +static bool ntfs_is_drive_letter(const char *target) +{ + return ((target[0] >= 'A' && target[0] <= 'Z') || + (target[0] >= 'a' && target[0] <= 'z')) && + target[1] == ':'; +} + +/* + * ntfs_translate_symlink_path + * + * @dentry: dentry of the symlink/junction being resolved + * @target: NUL-terminated NLS target string with '\\' already normalized to '/' + * @translated: out parameter, set to a newly kmalloc'd relative path on success + * + * Windows junctions (IO_REPARSE_TAG_MOUNT_POINT) and non-relative symlinks + * (IO_REPARSE_TAG_SYMLINK without SYMLINK_FLAG_RELATIVE) store substitute + * names such as "/??/C:/foo", "//?/C:/foo", "/foo", or "C:/foo". Linux + * cannot continue pathname lookup from those syntaxes, so rewrite them as a + * path relative to the symlink's containing directory on this NTFS volume, + * anchored at the volume root via "../". + * + * Note: bind-mounted subtrees of the volume may resolve to unexpected + * locations because the computed "../" depth is relative to the NTFS volume + * root, not the bind-mounted subtree root. + * + * Return: 0 on success with *translated set to a newly allocated string the + * caller must kfree(); negative errno on failure. + */ +int ntfs_translate_symlink_path(struct dentry *dentry, const char *target, + char **translated) +{ + char *buf, *link_path, *out, *p; + const char *path, *tail; + unsigned int up_levels = 0; + size_t tail_len, out_len; + int err; + + if (!dentry || !target || !translated) + return -EINVAL; + + path = target; + /* reject UNC path. */ + if (path[0] == '/' && path[1] == '/' && + !(path[2] == '?' && path[3] == '/')) + return -EOPNOTSUPP; + + /* target starts with "/??/" or "//?/"? */ + if ((path[0] == '/' && path[1] == '?' && path[2] == '?' && path[3] == '/') || + (path[0] == '/' && path[1] == '/' && path[2] == '?' && path[3] == '/')) + path += 4; + + /* target must start with a drive character or '/'. */ + if (ntfs_is_drive_letter(path)) { + if (path[2] && path[2] != '/') + return -EOPNOTSUPP; + tail = path + 2; + if (*tail == '/') + tail++; + } else if (*path == '/') { + tail = path + 1; + } else { + return -EOPNOTSUPP; + } + + tail_len = strlen(tail); + + buf = kmalloc(PATH_MAX, GFP_NOFS); + if (!buf) + return -ENOMEM; + + link_path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(link_path)) { + err = PTR_ERR(link_path); + goto out; + } + + /* count '/' after the leading slash. */ + for (p = link_path + 1; *p; p++) + if (*p == '/') + up_levels++; + + /* build "./" + ("../" * up_levels) + tail. */ + out_len = 2 + up_levels * 3 + tail_len; + if (out_len >= PATH_MAX) { + err = -ENAMETOOLONG; + goto out; + } + + out = kmalloc(out_len + 1, GFP_NOFS); + if (!out) { + err = -ENOMEM; + goto out; + } + + memcpy(out, "./", 2); + p = out + 2; + while (up_levels--) { + memcpy(p, "../", 3); + p += 3; + } + memcpy(p, tail, tail_len + 1); + + *translated = out; + err = 0; +out: + kfree(buf); + return err; +} + /* * Set the index for new reparse data */ @@ -496,40 +759,163 @@ static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t * Set reparse data for a WSL type symlink */ int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni, - const __le16 *target, int target_len) + const char *target, int target_len) { int err = 0; - int len; int reparse_len; - unsigned char *utarget = NULL; struct reparse_point *reparse; struct wsl_link_reparse_data *data; - len = ntfs_ucstonls(ni->vol, target, target_len, &utarget, 0); - if (len <= 0) - return -EINVAL; - - reparse_len = sizeof(struct reparse_point) + sizeof(data->type) + len; + reparse_len = sizeof(struct reparse_point) + sizeof(data->type) + + target_len; reparse = kvzalloc(reparse_len, GFP_NOFS); + if (!reparse) + return -ENOMEM; + + ni->target = kstrdup(target, GFP_NOFS); + if (!ni->target) { + kvfree(reparse); + return -ENOMEM; + } + + data = (struct wsl_link_reparse_data *)reparse->reparse_data; + reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK; + reparse->reparse_data_length = + cpu_to_le16(sizeof(data->type) + target_len); + reparse->reserved = 0; + data->type = cpu_to_le32(2); + memcpy(data->link, target, target_len); + err = ntfs_set_ntfs_reparse_data(ni, + (char *)reparse, reparse_len); + kvfree(reparse); + if (err) { + kfree(ni->target); + ni->target = NULL; + } else { + ni->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK; + ni->reparse_flags = 0; + } + return err; +} + +int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni, + const char *target, int target_len) +{ + int err = 0; + bool is_absolute, prt_sub_shared = true; + char *sub_name = NULL; + char *prt_name = NULL; + __le16 *sub_name_utf16 = NULL; + __le16 *prt_name_utf16 = NULL; + int sub_len, prt_len; + int total_data_len, total_reparse_len; + struct reparse_point *reparse = NULL; + struct symlink_reparse_data *data; + int i; + + /* Determine if target is absolute (starts with drive letter like C:/ or C:\) */ + is_absolute = target_len > 2 && + ntfs_is_drive_letter(target) && + (target[2] == '/' || target[2] == '\\'); + + + /* Normalize and prepare NLS paths */ + prt_name = kstrdup(target, GFP_NOFS); + if (!prt_name) + return -ENOMEM; + + /* Replace '/' with '\' */ + for (i = 0; i < target_len; i++) { + if (prt_name[i] == '/') + prt_name[i] = '\\'; + } + + if (is_absolute) { + /* Prepend '\??\' to Substitutename */ + sub_name = kmalloc(target_len + 5, GFP_NOFS); + if (!sub_name) { + err = -ENOMEM; + goto out; + } + snprintf(sub_name, target_len + 5, "\\??\\%s", prt_name); + prt_sub_shared = false; + } else { + /* For relative symlinks (including absolute paths without drive letters), + * SubstituteName and PrintName are identical. + */ + sub_name = prt_name; + } + + /* Convert NLS paths to UTF-16 */ + sub_len = ntfs_nlstoucs(ni->vol, sub_name, strlen(sub_name), + &sub_name_utf16, PATH_MAX); + if (sub_len < 0) { + err = sub_len; + goto out; + } + + prt_len = ntfs_nlstoucs(ni->vol, prt_name, strlen(prt_name), + &prt_name_utf16, PATH_MAX); + if (prt_len < 0) { + err = prt_len; + goto out; + } + + /* Check for buffer size limits */ + total_data_len = sizeof(struct symlink_reparse_data) + + (sub_len + prt_len) * sizeof(__le16); + if (total_data_len > 16384) { /* 16KB max reparse tag size */ + err = -EFBIG; + goto out; + } + + total_reparse_len = sizeof(struct reparse_point) + total_data_len; + reparse = kvzalloc(total_reparse_len, GFP_NOFS); if (!reparse) { err = -ENOMEM; - kfree(utarget); - } else { - data = (struct wsl_link_reparse_data *)reparse->reparse_data; - reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK; - reparse->reparse_data_length = - cpu_to_le16(sizeof(data->type) + len); - reparse->reserved = 0; - data->type = cpu_to_le32(2); - memcpy(data->link, utarget, len); - err = ntfs_set_ntfs_reparse_data(ni, - (char *)reparse, reparse_len); - kvfree(reparse); - if (!err) - ni->target = utarget; - else - kfree(utarget); + goto out; } + + /* Pack fields in reparse buffer */ + reparse->reparse_tag = IO_REPARSE_TAG_SYMLINK; + reparse->reparse_data_length = cpu_to_le16(total_data_len); + reparse->reserved = 0; + + data = (struct symlink_reparse_data *)reparse->reparse_data; + data->substitute_name_offset = 0; + data->substitute_name_length = cpu_to_le16(sub_len * sizeof(__le16)); + data->print_name_offset = data->substitute_name_length; + data->print_name_length = cpu_to_le16(prt_len * sizeof(__le16)); + data->flags = is_absolute ? 0 : cpu_to_le32(SYMLINK_FLAG_RELATIVE); + + /* Copy names to path_buffer */ + memcpy(data->path_buffer, sub_name_utf16, sub_len * sizeof(__le16)); + memcpy(data->path_buffer + sub_len, prt_name_utf16, prt_len * sizeof(__le16)); + + err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, total_reparse_len); + if (!err) { + int len = strlen(sub_name); + + for (i = 0; i < len; i++) { + if (sub_name[i] == '\\') + sub_name[i] = '/'; + } + ni->target = sub_name; + sub_name = NULL; + if (prt_sub_shared) + prt_name = NULL; + ni->reparse_tag = IO_REPARSE_TAG_SYMLINK; + ni->reparse_flags = is_absolute ? 0 : + cpu_to_le32(SYMLINK_FLAG_RELATIVE); + } + +out: + kfree(prt_name); + if (!prt_sub_shared) + kfree(sub_name); + kvfree(sub_name_utf16); + kvfree(prt_name_utf16); + kvfree(reparse); return err; } @@ -568,6 +954,10 @@ int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode) err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, reparse_len); kvfree(reparse); + if (!err) { + ni->reparse_tag = reparse_tag; + ni->reparse_flags = 0; + } } return err; diff --git a/fs/ntfs/reparse.h b/fs/ntfs/reparse.h index 28da40257f2a..c11a5bb7e6a5 100644 --- a/fs/ntfs/reparse.h +++ b/fs/ntfs/reparse.h @@ -11,8 +11,12 @@ extern __le16 reparse_index_name[]; unsigned int ntfs_make_symlink(struct ntfs_inode *ni); unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref); +int ntfs_translate_symlink_path(struct dentry *dentry, const char *target, + char **translated); int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni, - const __le16 *target, int target_len); + const char *target, int target_len); +int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni, + const char *symname, int symlen); int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode); int ntfs_delete_reparse_index(struct ntfs_inode *ni); int ntfs_remove_ntfs_reparse_data(struct ntfs_inode *ni); diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index e7de3d01257e..cbb6576cf725 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -763,7 +763,7 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume * buf = (u8 *)attr + le16_to_cpu(attr->data.non_resident.mapping_pairs_offset); attr_end = (u8 *)attr + le32_to_cpu(attr->length); - if (unlikely(buf < (u8 *)attr || buf > attr_end)) { + if (unlikely(buf < (u8 *)attr || buf >= attr_end)) { ntfs_error(vol->sb, "Corrupt attribute."); return ERR_PTR(-EIO); } @@ -811,7 +811,7 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume * */ b = *buf & 0xf; if (b) { - if (unlikely(buf + b > attr_end)) + if (unlikely(buf + b >= attr_end)) goto io_error; for (deltaxcn = (s8)buf[b--]; b; b--) deltaxcn = (deltaxcn << 8) + buf[b]; @@ -855,12 +855,16 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume * u8 b2 = *buf & 0xf; b = b2 + ((*buf >> 4) & 0xf); - if (buf + b > attr_end) + if (buf + b >= attr_end) goto io_error; for (deltaxcn = (s8)buf[b--]; b > b2; b--) deltaxcn = (deltaxcn << 8) + buf[b]; /* Change the current lcn to its new value. */ - lcn += deltaxcn; + if (unlikely(check_add_overflow(lcn, deltaxcn, &lcn))) { + ntfs_error(vol->sb, + "LCN overflow in mapping pairs array."); + goto err_out; + } #ifdef DEBUG /* * On NTFS 1.2-, apparently can have lcn == -1 to @@ -1817,7 +1821,7 @@ struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int d !ntfs_rle_contain(s_rl, start_vcn)) return ERR_PTR(-EINVAL); - begin_split = s_rl->vcn != start_vcn ? true : false; + begin_split = s_rl->vcn != start_vcn; e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn); if (!e_rl || @@ -1825,10 +1829,10 @@ struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int d !ntfs_rle_contain(e_rl, end_vcn)) return ERR_PTR(-EINVAL); - end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false; + end_split = e_rl->vcn + e_rl->length - 1 != end_vcn; /* @s_rl has to be split into left, punched hole, and right */ - one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false; + one_split_3 = e_rl == s_rl && begin_split && end_split; punch_cnt = (int)(e_rl - s_rl) + 1; @@ -1968,7 +1972,7 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i !ntfs_rle_contain(s_rl, start_vcn)) return ERR_PTR(-EINVAL); - begin_split = s_rl->vcn != start_vcn ? true : false; + begin_split = s_rl->vcn != start_vcn; e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn); if (!e_rl || @@ -1976,10 +1980,10 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i !ntfs_rle_contain(e_rl, end_vcn)) return ERR_PTR(-EINVAL); - end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false; + end_split = e_rl->vcn + e_rl->length - 1 != end_vcn; /* @s_rl has to be split into left, collapsed, and right */ - one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false; + one_split_3 = e_rl == s_rl && begin_split && end_split; punch_cnt = (int)(e_rl - s_rl) + 1; *punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element), diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 9e321cc2febe..8abe7bee4c0d 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -17,7 +17,6 @@ #include "sysctl.h" #include "logfile.h" -#include "quota.h" #include "index.h" #include "ntfs.h" #include "ea.h" @@ -44,6 +43,28 @@ static const struct constant_table ntfs_param_enums[] = { {} }; +enum { + NATIVE_SYMLINK_RAW, + NATIVE_SYMLINK_REL, +}; + +static const struct constant_table ntfs_native_symlink_enums[] = { + { "raw", NATIVE_SYMLINK_RAW }, + { "rel", NATIVE_SYMLINK_REL }, + {} +}; + +enum { + SYMLINK_WSL, + SYMLINK_NATIVE, +}; + +static const struct constant_table ntfs_symlink_enums[] = { + { "wsl", SYMLINK_WSL }, + { "native", SYMLINK_NATIVE }, + {} +}; + enum { Opt_uid, Opt_gid, @@ -67,6 +88,8 @@ enum { Opt_acl, Opt_discard, Opt_nocase, + Opt_native_symlink, + Opt_symlink, }; static const struct fs_parameter_spec ntfs_parameters[] = { @@ -92,6 +115,8 @@ static const struct fs_parameter_spec ntfs_parameters[] = { fsparam_flag("discard", Opt_discard), fsparam_flag("sparse", Opt_sparse), fsparam_flag("nocase", Opt_nocase), + fsparam_enum("native_symlink", Opt_native_symlink, ntfs_native_symlink_enums), + fsparam_enum("symlink", Opt_symlink, ntfs_symlink_enums), {} }; @@ -216,6 +241,18 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param) else NVolClearDisableSparse(vol); break; + case Opt_native_symlink: + if (result.uint_32 == NATIVE_SYMLINK_REL) + NVolSetNativeSymlinkRel(vol); + else + NVolClearNativeSymlinkRel(vol); + break; + case Opt_symlink: + if (result.uint_32 == SYMLINK_NATIVE) + NVolSetSymlinkNative(vol); + else + NVolClearSymlinkNative(vol); + break; case Opt_sparse: break; default: @@ -240,8 +277,7 @@ static int ntfs_reconfigure(struct fs_context *fc) * flags are set. Also, empty the logfile journal as it would become * stale as soon as something is written to the volume and mark the * volume dirty so that chkdsk is run if the volume is not umounted - * cleanly. Finally, mark the quotas out of date so Windows rescans - * the volume on boot and updates them. + * cleanly. * * When remounting read-only, mark the volume clean if no volume errors * have occurred. @@ -274,12 +310,6 @@ static int ntfs_reconfigure(struct fs_context *fc) NVolSetErrors(vol); return -EROFS; } - if (!ntfs_mark_quotas_out_of_date(vol)) { - ntfs_error(sb, "Failed to mark quotas out of date%s", - es); - NVolSetErrors(vol); - return -EROFS; - } } else if (!sb_rdonly(sb) && (fc->sb_flags & SB_RDONLY)) { /* Remounting read-only. */ if (!NVolErrors(vol)) { @@ -452,25 +482,36 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label) goto out; } - if (!ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0, - ctx)) - ntfs_attr_record_rm(ctx); + ret = ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0, + ctx); + if (!ret) + ret = ntfs_attr_record_rm(ctx); + else if (ret == -ENOENT) + ret = 0; ntfs_attr_put_search_ctx(ctx); + if (ret) + goto out; ret = ntfs_resident_attr_record_add(vol_ni, AT_VOLUME_NAME, AT_UNNAMED, 0, (u8 *)uname, uname_len * sizeof(__le16), 0); out: + if (ret >= 0) { + char *old_label; + + mutex_lock(&vol->volume_label_lock); + old_label = vol->volume_label; + vol->volume_label = new_label; + mutex_unlock(&vol->volume_label_lock); + + kfree(old_label); + mark_inode_dirty_sync(vol->vol_ino); + ret = 0; + } mutex_unlock(&vol_ni->mrec_lock); kvfree(uname); - if (ret >= 0) { - kfree(vol->volume_label); - vol->volume_label = new_label; - mark_inode_dirty_sync(vol->vol_ino); - ret = 0; - } else { + if (ret < 0) kfree(new_label); - } return ret; } @@ -1175,73 +1216,6 @@ static int check_windows_hibernation_status(struct ntfs_volume *vol) return ret; } -/* - * load_and_init_quota - load and setup the quota file for a volume if present - * @vol: ntfs super block describing device whose quota file to load - * - * Return 'true' on success or 'false' on error. If $Quota is not present, we - * leave vol->quota_ino as NULL and return success. - */ -static bool load_and_init_quota(struct ntfs_volume *vol) -{ - static const __le16 Quota[7] = { cpu_to_le16('$'), - cpu_to_le16('Q'), cpu_to_le16('u'), - cpu_to_le16('o'), cpu_to_le16('t'), - cpu_to_le16('a'), 0 }; - static __le16 Q[3] = { cpu_to_le16('$'), - cpu_to_le16('Q'), 0 }; - struct ntfs_name *name = NULL; - u64 mref; - struct inode *tmp_ino; - - ntfs_debug("Entering."); - /* - * Find the inode number for the quota file by looking up the filename - * $Quota in the extended system files directory $Extend. - */ - inode_lock(vol->extend_ino); - mref = ntfs_lookup_inode_by_name(NTFS_I(vol->extend_ino), Quota, 6, - &name); - inode_unlock(vol->extend_ino); - kfree(name); - if (IS_ERR_MREF(mref)) { - /* - * If the file does not exist, quotas are disabled and have - * never been enabled on this volume, just return success. - */ - if (MREF_ERR(mref) == -ENOENT) { - ntfs_debug("$Quota not present. Volume does not have quotas enabled."); - /* - * No need to try to set quotas out of date if they are - * not enabled. - */ - NVolSetQuotaOutOfDate(vol); - return true; - } - /* A real error occurred. */ - ntfs_error(vol->sb, "Failed to find inode number for $Quota."); - return false; - } - /* Get the inode. */ - tmp_ino = ntfs_iget(vol->sb, MREF(mref)); - if (IS_ERR(tmp_ino)) { - if (!IS_ERR(tmp_ino)) - iput(tmp_ino); - ntfs_error(vol->sb, "Failed to load $Quota."); - return false; - } - vol->quota_ino = tmp_ino; - /* Get the $Q index allocation attribute. */ - tmp_ino = ntfs_index_iget(vol->quota_ino, Q, 2); - if (IS_ERR(tmp_ino)) { - ntfs_error(vol->sb, "Failed to load $Quota/$Q index."); - return false; - } - vol->quota_q_ino = tmp_ino; - ntfs_debug("Done."); - return true; -} - /* * load_and_init_attrdef - load the attribute definitions table for a volume * @vol: ntfs super block describing device whose attrdef to load @@ -1323,7 +1297,6 @@ static bool load_and_init_upcase(struct ntfs_volume *vol) u8 *addr; pgoff_t index, max_index; unsigned int size; - int i, max; ntfs_debug("Entering."); /* Read upcase table and setup vol->upcase and vol->upcase_len. */ @@ -1374,16 +1347,11 @@ static bool load_and_init_upcase(struct ntfs_volume *vol) mutex_unlock(&ntfs_lock); return true; } - max = default_upcase_len; - if (max > vol->upcase_len) - max = vol->upcase_len; - for (i = 0; i < max; i++) - if (vol->upcase[i] != default_upcase[i]) - break; - if (i == max) { + if (default_upcase_len == vol->upcase_len && + !memcmp(vol->upcase, default_upcase, + default_upcase_len * sizeof(*default_upcase))) { kvfree(vol->upcase); vol->upcase = default_upcase; - vol->upcase_len = max; ntfs_nr_upcase_users++; mutex_unlock(&ntfs_lock); ntfs_debug("Volume specified $UpCase matches default. Using default."); @@ -1531,6 +1499,7 @@ static bool load_system_files(struct ntfs_volume *vol) vol->volume_label = NULL; } + ntfs_attr_reinit_search_ctx(ctx); if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx) || ctx->attr->non_resident || ctx->attr->flags) { ntfs_attr_put_search_ctx(ctx); @@ -1659,18 +1628,6 @@ static bool load_system_files(struct ntfs_volume *vol) ntfs_error(sb, "Failed to load $Extend."); goto iput_sec_err_out; } - /* Find the quota file, load it if present, and set it up. */ - if (!load_and_init_quota(vol) && - vol->on_errors == ON_ERRORS_REMOUNT_RO) { - static const char *es1 = "Failed to load $Quota"; - static const char *es2 = ". Run chkdsk."; - - sb->s_flags |= SB_RDONLY; - ntfs_error(sb, "%s. Mounting read-only%s", es1, es2); - /* This will prevent a read-write remount. */ - NVolSetErrors(vol); - } - return true; iput_sec_err_out: @@ -1727,7 +1684,7 @@ static void ntfs_volume_free(struct ntfs_volume *vol) vol->upcase = NULL; } - if (!ntfs_nr_upcase_users && default_upcase) { + if (!ntfs_nr_upcase_users) { kvfree(default_upcase); default_upcase = NULL; } @@ -1742,8 +1699,7 @@ static void ntfs_volume_free(struct ntfs_volume *vol) unload_nls(vol->nls_map); - if (vol->lcn_empty_bits_per_page) - kvfree(vol->lcn_empty_bits_per_page); + kvfree(vol->lcn_empty_bits_per_page); kfree(vol->volume_label); kfree(vol); } @@ -1768,10 +1724,6 @@ static void ntfs_put_super(struct super_block *sb) /* NTFS 3.0+ specific. */ if (vol->major_ver >= 3) { - if (vol->quota_q_ino) - ntfs_commit_inode(vol->quota_q_ino); - if (vol->quota_ino) - ntfs_commit_inode(vol->quota_ino); if (vol->extend_ino) ntfs_commit_inode(vol->extend_ino); if (vol->secure_ino) @@ -1820,14 +1772,6 @@ static void ntfs_put_super(struct super_block *sb) /* NTFS 3.0+ specific clean up. */ if (vol->major_ver >= 3) { - if (vol->quota_q_ino) { - iput(vol->quota_q_ino); - vol->quota_q_ino = NULL; - } - if (vol->quota_ino) { - iput(vol->quota_ino); - vol->quota_ino = NULL; - } if (vol->extend_ino) { iput(vol->extend_ino); vol->extend_ino = NULL; @@ -1954,7 +1898,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol) struct address_space *mapping = vol->lcnbmp_ino->i_mapping; struct folio *folio; pgoff_t index, max_index; - struct file_ra_state *ra; + struct file_ra_state ra = { 0 }; ntfs_debug("Entering."); /* Serialize accesses to the cluster bitmap. */ @@ -1962,11 +1906,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol) if (NVolFreeClusterKnown(vol)) return atomic64_read(&vol->free_clusters); - ra = kzalloc(sizeof(*ra), GFP_NOFS); - if (!ra) - return 0; - - file_ra_state_init(ra, mapping); + file_ra_state_init(&ra, mapping); /* * Convert the number of bits into bytes rounded up, then convert into @@ -1985,7 +1925,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol) * Get folio from page cache, getting it from backing store * if necessary, and increment the use count. */ - folio = ntfs_get_locked_folio(mapping, index, max_index, ra); + folio = ntfs_get_locked_folio(mapping, index, max_index, &ra); /* Ignore pages which errored synchronously. */ if (IS_ERR(folio)) { @@ -2024,7 +1964,6 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol) else atomic64_set(&vol->free_clusters, nr_free); - kfree(ra); NVolSetFreeClusterKnown(vol); wake_up_all(&vol->free_waitq); ntfs_debug("Exiting."); @@ -2079,15 +2018,11 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, struct address_space *mapping = vol->mftbmp_ino->i_mapping; struct folio *folio; pgoff_t index; - struct file_ra_state *ra; + struct file_ra_state ra = { 0 }; ntfs_debug("Entering."); - ra = kzalloc(sizeof(*ra), GFP_NOFS); - if (!ra) - return 0; - - file_ra_state_init(ra, mapping); + file_ra_state_init(&ra, mapping); /* Use multiples of 4 bytes, thus max_size is PAGE_SIZE / 4. */ ntfs_debug("Reading $MFT/$BITMAP, max_index = 0x%lx, max_size = 0x%lx.", @@ -2099,7 +2034,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, * Get folio from page cache, getting it from backing store * if necessary, and increment the use count. */ - folio = ntfs_get_locked_folio(mapping, index, max_index, ra); + folio = ntfs_get_locked_folio(mapping, index, max_index, &ra); /* Ignore pages which errored synchronously. */ if (IS_ERR(folio)) { @@ -2131,7 +2066,6 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, else atomic64_set(&vol->free_mft_records, nr_free); - kfree(ra); ntfs_debug("Exiting."); return nr_free; } @@ -2476,14 +2410,6 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) vol->vol_ino = NULL; /* NTFS 3.0+ specific clean up. */ if (vol->major_ver >= 3) { - if (vol->quota_q_ino) { - iput(vol->quota_q_ino); - vol->quota_q_ino = NULL; - } - if (vol->quota_ino) { - iput(vol->quota_ino); - vol->quota_ino = NULL; - } if (vol->extend_ino) { iput(vol->extend_ino); vol->extend_ino = NULL; @@ -2530,8 +2456,6 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) } /* Error exit code path. */ unl_upcase_iput_tmp_ino_err_out_now: - if (vol->lcn_empty_bits_per_page) - kvfree(vol->lcn_empty_bits_per_page); /* * Decrease the number of upcase users and destroy the global default * upcase table if necessary. @@ -2551,6 +2475,9 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) /* Errors at this stage are irrelevant. */ err_out_now: sb->s_fs_info = NULL; + kvfree(vol->lcn_empty_bits_per_page); + kfree(vol->volume_label); + unload_nls(vol->nls_map); kfree(vol); ntfs_debug("Failed, returning -EINVAL."); lockdep_on(); @@ -2631,6 +2558,7 @@ static int ntfs_init_fs_context(struct fs_context *fc) NVolSetCaseSensitive(vol); init_rwsem(&vol->mftbmp_lock); init_rwsem(&vol->lcnbmp_lock); + mutex_init(&vol->volume_label_lock); fc->s_fs_info = vol; fc->ops = &ntfs_context_ops; @@ -2649,7 +2577,7 @@ MODULE_ALIAS_FS("ntfs"); static int ntfs_workqueue_init(void) { - ntfs_wq = alloc_workqueue("ntfs-bg-io", 0, 0); + ntfs_wq = alloc_workqueue("ntfs-bg-io", WQ_PERCPU, 0); if (!ntfs_wq) return -ENOMEM; return 0; diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index af41427ec622..65fd3908af26 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -72,12 +72,11 @@ * @vol_flags: Volume flags. * @major_ver: Ntfs major version of volume. * @minor_ver: Ntfs minor version of volume. + * @volume_label_lock: protects @volume_label. * @volume_label: volume label. * @root_ino: The VFS inode of the root directory. * @secure_ino: The VFS inode of $Secure (NTFS3.0+ only, otherwise NULL). * @extend_ino: The VFS inode of $Extend (NTFS3.0+ only, otherwise NULL). - * @quota_ino: The VFS inode of $Quota. - * @quota_q_ino: Attribute inode for $Quota/$Q. * @nls_map: NLS (National Language Support) table. * @nls_utf8: NLS table for UTF-8. * @free_waitq: Wait queue for threads waiting for free clusters or MFT records. @@ -133,6 +132,7 @@ struct ntfs_volume { struct inode *logfile_ino; struct inode *lcnbmp_ino; struct rw_semaphore lcnbmp_lock; + struct mutex volume_label_lock; struct inode *vol_ino; __le16 vol_flags; u8 major_ver; @@ -141,8 +141,6 @@ struct ntfs_volume { struct inode *root_ino; struct inode *secure_ino; struct inode *extend_ino; - struct inode *quota_ino; - struct inode *quota_q_ino; struct nls_table *nls_map; bool nls_utf8; wait_queue_head_t free_waitq; @@ -165,7 +163,6 @@ struct ntfs_volume { * Otherwise be case insensitive but still * create file names in POSIX namespace. * NV_LogFileEmpty LogFile journal is empty. - * NV_QuotaOutOfDate Quota is out of date. * NV_UsnJrnlStamped UsnJrnl has been stamped. * NV_ReadOnly Volume is mounted read-only. * NV_Compression Volume supports compression. @@ -180,13 +177,13 @@ struct ntfs_volume { * * NV_Discard Issue discard/TRIM commands for freed clusters. * NV_DisableSparse Disable creation of sparse regions. + * NV_NativeSymlinkRel Translate absolute Windows reparse targets (native_symlink=rel). */ enum { NV_Errors, NV_ShowSystemFiles, NV_CaseSensitive, NV_LogFileEmpty, - NV_QuotaOutOfDate, NV_UsnJrnlStamped, NV_ReadOnly, NV_Compression, @@ -198,6 +195,8 @@ enum { NV_CheckWindowsNames, NV_Discard, NV_DisableSparse, + NV_NativeSymlinkRel, + NV_SymlinkNative, }; /* @@ -223,7 +222,6 @@ DEFINE_NVOL_BIT_OPS(Errors) DEFINE_NVOL_BIT_OPS(ShowSystemFiles) DEFINE_NVOL_BIT_OPS(CaseSensitive) DEFINE_NVOL_BIT_OPS(LogFileEmpty) -DEFINE_NVOL_BIT_OPS(QuotaOutOfDate) DEFINE_NVOL_BIT_OPS(UsnJrnlStamped) DEFINE_NVOL_BIT_OPS(ReadOnly) DEFINE_NVOL_BIT_OPS(Compression) @@ -235,6 +233,8 @@ DEFINE_NVOL_BIT_OPS(HideDotFiles) DEFINE_NVOL_BIT_OPS(CheckWindowsNames) DEFINE_NVOL_BIT_OPS(Discard) DEFINE_NVOL_BIT_OPS(DisableSparse) +DEFINE_NVOL_BIT_OPS(NativeSymlinkRel) +DEFINE_NVOL_BIT_OPS(SymlinkNative) static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr) {