Make ntfs_make_symlink() parse native Windows symbolic link reparse payloads when the SYMLINK_FLAG_RELATIVE bit is set. Implement the following changes: * Add a dedicated on-disk layout definition for symbolic link reparse data. * validate the UTF-16 name ranges before decoding them. * convert the substitute name into the mount's NLS and normalize path separators. Signed-off-by: Hyunchul Lee --- fs/ntfs/inode.c | 36 +++++++++------- fs/ntfs/layout.h | 11 +++++ fs/ntfs/reparse.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 143 insertions(+), 26 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index efb34a5e94d9..8894f33b46ca 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -863,8 +863,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. @@ -874,18 +892,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; } @@ -894,7 +900,7 @@ 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; view_index_meta: @@ -1018,7 +1024,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 { diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h index d94f914e830f..94af6efa04af 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,15 @@ struct reparse_point { u8 reparse_data[]; } __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/reparse.c b/fs/ntfs/reparse.c index 74713716813f..4cc37f1c9c90 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,24 +127,54 @@ 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); + switch (reparse_attr->reparse_tag) { + case IO_REPARSE_TAG_SYMLINK: + { + struct symlink_reparse_data *data; + size_t data_offs; - if (ntfs_is_valid_reparse_buffer(ni, reparse_attr, size) == false) - return false; + if (!valid_reparse_buffer(ni, reparse_attr, size, + sizeof(*data))) + return false; - switch (reparse_attr->reparse_tag) { + 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: - if (data_len <= sizeof(wsl_reparse_data->type) || - wsl_reparse_data->type != cpu_to_le32(2)) + { + 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; } @@ -134,16 +212,38 @@ 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; + reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0, &attr_size); if (reparse_attr && attr_size && valid_reparse_data(ni, reparse_attr, attr_size)) { switch (reparse_attr->reparse_tag) { + 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)); + + mode = ntfs_reparse_tag_mode(reparse_attr); + if (!(data->flags & cpu_to_le32(SYMLINK_FLAG_RELATIVE))) + break; + + err = ntfs_reparse_target_to_nls(ni->vol, name, + le16_to_cpu(data->substitute_name_length), + &ni->target); + if (err < 0) + mode = 0; + break; + } case IO_REPARSE_TAG_LX_SYMLINK: wsl_link_data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data; -- 2.43.0