1034 lines · 25.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Processing of reparse points * * Part of this file is based on code from the NTFS-3G. * * Copyright (c) 2008-2021 Jean-Pierre Andre * Copyright (c) 2025 LG Electronics Co., Ltd. */ #include "ntfs.h"#include "layout.h"#include "attrib.h"#include "inode.h"#include "dir.h"#include "volume.h"#include "mft.h"#include "index.h"#include "lcnalloc.h"#include "reparse.h" struct wsl_link_reparse_data {	__le32	type;	char	link[];}; struct wof_reparse_data {	__le32 version;	__le32 provider;	__le32 provider_version;	__le32 compression_format;} __packed; #define WOF_CURRENT_VERSION			cpu_to_le32(1) #define WOF_PROVIDER_WIM			cpu_to_le32(1)#define WOF_PROVIDER_FILE			cpu_to_le32(2) #define WOF_PROVIDER_CURRENT_VERSION		cpu_to_le32(1) #define WOF_COMPRESSION_XPRESS4K		cpu_to_le32(0)#define WOF_COMPRESSION_LZX			cpu_to_le32(1)#define WOF_COMPRESSION_XPRESS8K		cpu_to_le32(2)#define WOF_COMPRESSION_XPRESS16K		cpu_to_le32(3) 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;	struct reparse_index_key key;	__le32 filling;}; __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 valid_reparse_buffer(struct ntfs_inode *ni,				 const struct reparse_point *reparse_attr,				 size_t size,				 size_t payload_min_len){	size_t expected; 	if (!ni || !reparse_attr)		return false; 	/* Minimum size must cover reparse_point header */	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; 	/* Calculate expected total size */	expected = sizeof(struct reparse_point) +		le16_to_cpu(reparse_attr->reparse_data_length); 	/* Add GUID size for non-Microsoft tags */	if (!(reparse_attr->reparse_tag & IO_REPARSE_TAG_IS_MICROSOFT))		expected += sizeof(struct guid); 	/* Buffer must exactly match the expected size */	return expected == size;} /* * Do some sanity checks on reparse data * * Microsoft reparse points have an 8-byte header whereas * non-Microsoft reparse points have a 24-byte header.  In each case, * 'reparse_data_length' must equal the number of non-header bytes. * * If the reparse data looks like a junction point or symbolic * link, more checks can be done. */static bool valid_reparse_data(struct ntfs_inode *ni,		const struct reparse_point *reparse_attr, size_t size){	if (size < sizeof(*reparse_attr))		return false; 	switch (reparse_attr->reparse_tag) {	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 (!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;	case IO_REPARSE_TAG_WOF: {		if (!valid_reparse_buffer(ni, reparse_attr, size,					  sizeof(struct wof_reparse_data)))			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(__le32 reparse_tag){	unsigned int mode = 0; 	switch (reparse_tag) {	case IO_REPARSE_TAG_MOUNT_POINT:	case IO_REPARSE_TAG_SYMLINK:	case IO_REPARSE_TAG_LX_SYMLINK:		mode = S_IFLNK;		break;	case IO_REPARSE_TAG_AF_UNIX:		mode = S_IFSOCK;		break;	case IO_REPARSE_TAG_LX_FIFO:		mode = S_IFIFO;		break;	case IO_REPARSE_TAG_LX_CHR:		mode = S_IFCHR;		break;	case IO_REPARSE_TAG_LX_BLK:		mode = S_IFBLK;	} 	return mode;} /* * Parse reparse point data and initialize its in-memory representation. */int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode){	s64 attr_size = 0;	int err = -EINVAL;	unsigned int lth;	struct reparse_point *reparse_attr; 	kvfree(ni->target);	ni->target = NULL;	ni->reparse_tag = 0;	ni->reparse_flags = 0;	*mode = 0; 	reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,					 &attr_size);	if (IS_ERR(reparse_attr)) {		err = PTR_ERR(reparse_attr);		ntfs_error(ni->vol->sb,			   "Failed to read reparse point: %d.", err);		return err;	}	if (!valid_reparse_data(ni, reparse_attr, attr_size)) {		ntfs_error(ni->vol->sb, "Invalid reparse point.");		err = -EFSCORRUPTED;		goto out;	} 	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:	{		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);			ni->target = kvzalloc(lth + 1, GFP_NOFS);			if (ni->target) {				memcpy(ni->target, wsl_link_data->link, lth);				ni->target[lth] = 0;				err = 0;			}		}		break;	}	case IO_REPARSE_TAG_WOF:	{#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION		const struct wof_reparse_data *wof_data =			(const struct wof_reparse_data *)reparse_attr->reparse_data; 		ni->itype.compressed.block_size_bits = 0;		ni->itype.compressed.block_size = 0;		if (wof_data->version == WOF_CURRENT_VERSION &&		    wof_data->provider == WOF_PROVIDER_FILE &&		    wof_data->provider_version ==			    WOF_PROVIDER_CURRENT_VERSION) {			switch (wof_data->compression_format) {			case WOF_COMPRESSION_XPRESS4K:				ni->itype.compressed.block_size_bits =					12;				break;			case WOF_COMPRESSION_XPRESS8K:				ni->itype.compressed.block_size_bits =					13;				break;			case WOF_COMPRESSION_XPRESS16K:				ni->itype.compressed.block_size_bits =					14;				break;			case WOF_COMPRESSION_LZX:				ni->itype.compressed.block_size_bits =					15;				break;			}		}		if (ni->itype.compressed.block_size_bits)			ni->itype.compressed.block_size =				1				<< ni->itype.compressed.block_size_bits;#endif		NInoSetWofCompressed(ni);		VFS_I(ni)->i_mode &= ~0222;		err = 0;		break;	}	default:		err = 0;	} 	if (!err) {		*mode = ntfs_reparse_tag_mode(			reparse_attr->reparse_tag);		ni->reparse_tag = reparse_attr->reparse_tag;	} out:	kvfree(reparse_attr); 	return err;} unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref){	s64 attr_size = 0;	struct reparse_point *reparse_attr;	unsigned int dt_type = DT_UNKNOWN;	struct inode *vi; 	vi = ntfs_iget(vol->sb, mref);	if (IS_ERR(vi))		return DT_UNKNOWN; 	reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi),			AT_REPARSE_POINT, NULL, 0, &attr_size);	if (IS_ERR(reparse_attr))		reparse_attr = NULL; 	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;			break;		case IO_REPARSE_TAG_AF_UNIX:			dt_type = DT_SOCK;			break;		case IO_REPARSE_TAG_LX_FIFO:			dt_type = DT_FIFO;			break;		case IO_REPARSE_TAG_LX_CHR:			dt_type = DT_CHR;			break;		case IO_REPARSE_TAG_LX_BLK:			dt_type = DT_BLK;		}	} 	kvfree(reparse_attr); 	iput(vi);	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 == '/') {

Lines 1–500 of 1034.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t