Re: [PATCH] ksmbd: use LOOKUP_BENEATH to prevent the out of share access

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



2021-09-23 18:24 GMT+09:00, Hyunchul Lee <hyc.lee@xxxxxxxxx>:
> instead of removing '..' in a given path, call
> kern_path with LOOKUP_BENEATH flag to prevent
> the out of share access.
>
> ran various test on this:
> smb2-cat-async smb://127.0.0.1/homes/../out_of_share
> smb2-cat-async smb://127.0.0.1/homes/foo/../../out_of_share
> smbclient //127.0.0.1/homes -c "mkdir ../foo2"
> smbclient //127.0.0.1/homes -c "rename bar ../bar"
>
> Cc: Ronnie Sahlberg <ronniesahlberg@xxxxxxxxx>
> Cc: Ralph Boehme <slow@xxxxxxxxx>
> Cc: Steve French <smfrench@xxxxxxxxx>
> Cc: Namjae Jeon <linkinjeon@xxxxxxxxxx>
> Signed-off-by: Hyunchul Lee <hyc.lee@xxxxxxxxx>
> ---
>  fs/ksmbd/misc.c    | 76 ++++++----------------------------------------
>  fs/ksmbd/misc.h    |  3 +-
>  fs/ksmbd/smb2pdu.c | 51 ++++++++++++++-----------------
>  fs/ksmbd/vfs.c     | 67 +++++++++++++++++++++++++---------------
>  fs/ksmbd/vfs.h     |  3 +-
>  5 files changed, 78 insertions(+), 122 deletions(-)
>
> diff --git a/fs/ksmbd/misc.c b/fs/ksmbd/misc.c
> index 3eac3c01749f..0b307ca28a19 100644
> --- a/fs/ksmbd/misc.c
> +++ b/fs/ksmbd/misc.c
> @@ -191,77 +191,19 @@ int get_nlink(struct kstat *st)
>  	return nlink;
>  }
>
> -char *ksmbd_conv_path_to_unix(char *path)
> +void ksmbd_conv_path_to_unix(char *path)
>  {
> -	size_t path_len, remain_path_len, out_path_len;
> -	char *out_path, *out_next;
> -	int i, pre_dotdot_cnt = 0, slash_cnt = 0;
> -	bool is_last;
> -
>  	strreplace(path, '\\', '/');
> -	path_len = strlen(path);
> -	remain_path_len = path_len;
> -	if (path_len == 0)
> -		return ERR_PTR(-EINVAL);
> -
> -	out_path = kzalloc(path_len + 2, GFP_KERNEL);
> -	if (!out_path)
> -		return ERR_PTR(-ENOMEM);
> -	out_path_len = 0;
> -	out_next = out_path;
> -
> -	do {
> -		char *name = path + path_len - remain_path_len;
> -		char *next = strchrnul(name, '/');
> -		size_t name_len = next - name;
> -
> -		is_last = !next[0];
> -		if (name_len == 2 && name[0] == '.' && name[1] == '.') {
> -			pre_dotdot_cnt++;
> -			/* handle the case that path ends with "/.." */
> -			if (is_last)
> -				goto follow_dotdot;
> -		} else {
> -			if (pre_dotdot_cnt) {
> -follow_dotdot:
> -				slash_cnt = 0;
> -				for (i = out_path_len - 1; i >= 0; i--) {
> -					if (out_path[i] == '/' &&
> -					    ++slash_cnt == pre_dotdot_cnt + 1)
> -						break;
> -				}
> -
> -				if (i < 0 &&
> -				    slash_cnt != pre_dotdot_cnt) {
> -					kfree(out_path);
> -					return ERR_PTR(-EINVAL);
> -				}
> -
> -				out_next = &out_path[i+1];
> -				*out_next = '\0';
> -				out_path_len = i + 1;
> -
> -			}
> -
> -			if (name_len != 0 &&
> -			    !(name_len == 1 && name[0] == '.') &&
> -			    !(name_len == 2 && name[0] == '.' && name[1] == '.')) {
> -				next[0] = '\0';
> -				sprintf(out_next, "%s/", name);
> -				out_next += name_len + 1;
> -				out_path_len += name_len + 1;
> -				next[0] = '/';
> -			}
> -			pre_dotdot_cnt = 0;
> -		}
> +}
>
> -		remain_path_len -= name_len + 1;
> -	} while (!is_last);
> +void ksmbd_strip_last_slash(char *path)
> +{
> +	int len = strlen(path);
>
> -	if (out_path_len > 0)
> -		out_path[out_path_len-1] = '\0';
> -	path[path_len] = '\0';
> -	return out_path;
> +	while (len && path[len - 1] == '/') {
> +		path[len - 1] = '\0';
> +		len--;
> +	}
>  }
>
>  void ksmbd_conv_path_to_windows(char *path)
> diff --git a/fs/ksmbd/misc.h b/fs/ksmbd/misc.h
> index b7b10139ada2..af8717d4d85b 100644
> --- a/fs/ksmbd/misc.h
> +++ b/fs/ksmbd/misc.h
> @@ -16,7 +16,8 @@ int ksmbd_validate_filename(char *filename);
>  int parse_stream_name(char *filename, char **stream_name, int *s_type);
>  char *convert_to_nt_pathname(char *filename, char *sharepath);
>  int get_nlink(struct kstat *st);
> -char *ksmbd_conv_path_to_unix(char *path);
> +void ksmbd_conv_path_to_unix(char *path);
> +void ksmbd_strip_last_slash(char *path);
>  void ksmbd_conv_path_to_windows(char *path);
>  char *ksmbd_extract_sharename(char *treename);
>  char *convert_to_unix_name(struct ksmbd_share_config *share, char *name);
> diff --git a/fs/ksmbd/smb2pdu.c b/fs/ksmbd/smb2pdu.c
> index a4b237d4042b..7c5d205bdb22 100644
> --- a/fs/ksmbd/smb2pdu.c
> +++ b/fs/ksmbd/smb2pdu.c
> @@ -642,7 +642,7 @@ static char *
>  smb2_get_name(struct ksmbd_share_config *share, const char *src,
>  	      const int maxlen, struct nls_table *local_nls)
>  {
> -	char *name, *norm_name, *unixname;
> +	char *name, *unixname;
>
>  	name = smb_strndup_from_utf16(src, maxlen, 1, local_nls);
>  	if (IS_ERR(name)) {
> @@ -651,15 +651,11 @@ smb2_get_name(struct ksmbd_share_config *share, const
> char *src,
>  	}
>
>  	/* change it to absolute unix name */
> -	norm_name = ksmbd_conv_path_to_unix(name);
> -	if (IS_ERR(norm_name)) {
> -		kfree(name);
> -		return norm_name;
> -	}
> -	kfree(name);
> +	ksmbd_conv_path_to_unix(name);
> +	ksmbd_strip_last_slash(name);
>
> -	unixname = convert_to_unix_name(share, norm_name);
> -	kfree(norm_name);
> +	unixname = convert_to_unix_name(share, name);
> +	kfree(name);
>  	if (!unixname) {
>  		pr_err("can not convert absolute name\n");
>  		return ERR_PTR(-ENOMEM);
> @@ -2412,7 +2408,7 @@ static int smb2_creat(struct ksmbd_work *work, struct
> path *path, char *name,
>  			return rc;
>  	}
>
> -	rc = ksmbd_vfs_kern_path(name, 0, path, 0);
> +	rc = ksmbd_vfs_kern_path(work, name, 0, path, 0);
>  	if (rc) {
>  		pr_err("cannot get linux path (%s), err = %d\n",
>  		       name, rc);
> @@ -2487,7 +2483,7 @@ int smb2_open(struct ksmbd_work *work)
>  	struct oplock_info *opinfo;
>  	__le32 *next_ptr = NULL;
>  	int req_op_level = 0, open_flags = 0, may_flags = 0, file_info = 0;
> -	int rc = 0, len = 0;
> +	int rc = 0;
>  	int contxt_cnt = 0, query_disk_id = 0;
>  	int maximal_access_ctxt = 0, posix_ctxt = 0;
>  	int s_type = 0;
> @@ -2559,17 +2555,12 @@ int smb2_open(struct ksmbd_work *work)
>  			goto err_out1;
>  		}
>  	} else {
> -		len = strlen(share->path);
> -		ksmbd_debug(SMB, "share path len %d\n", len);
> -		name = kmalloc(len + 1, GFP_KERNEL);
> +		name = kstrdup(share->path, GFP_KERNEL);
>  		if (!name) {
>  			rsp->hdr.Status = STATUS_NO_MEMORY;
>  			rc = -ENOMEM;
>  			goto err_out1;
>  		}
> -
> -		memcpy(name, share->path, len);
> -		*(name + len) = '\0';
>  	}
>
>  	req_op_level = req->RequestedOplockLevel;
> @@ -2692,7 +2683,7 @@ int smb2_open(struct ksmbd_work *work)
>  		goto err_out1;
>  	}
>
> -	rc = ksmbd_vfs_kern_path(name, LOOKUP_NO_SYMLINKS, &path, 1);
> +	rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS, &path, 1);
>  	if (!rc) {
>  		if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
>  			/*
> @@ -2721,7 +2712,7 @@ int smb2_open(struct ksmbd_work *work)
>  	}
>
>  	if (rc) {
> -		if (rc == -EACCES) {
> +		if (rc != -ENOENT) {
>  			ksmbd_debug(SMB,
>  				    "User does not have right permission\n");
>  			goto err_out;
> @@ -3229,7 +3220,7 @@ int smb2_open(struct ksmbd_work *work)
>  			rsp->hdr.Status = STATUS_INVALID_PARAMETER;
>  		else if (rc == -EOPNOTSUPP)
>  			rsp->hdr.Status = STATUS_NOT_SUPPORTED;
> -		else if (rc == -EACCES || rc == -ESTALE)
> +		else if (rc == -EACCES || rc == -ESTALE || rc == -EXDEV)
>  			rsp->hdr.Status = STATUS_ACCESS_DENIED;
>  		else if (rc == -ENOENT)
>  			rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
> @@ -4801,7 +4792,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work
> *work,
>  	int rc = 0, len;
>  	int fs_infoclass_size = 0;
>
> -	rc = ksmbd_vfs_kern_path(share->path, LOOKUP_NO_SYMLINKS, &path, 0);
> +	rc = ksmbd_vfs_kern_path(work, share->path, LOOKUP_NO_SYMLINKS, &path,
> 0);
>  	if (rc) {
>  		pr_err("cannot create vfs path\n");
>  		return -EIO;
> @@ -5378,10 +5369,12 @@ static int smb2_rename(struct ksmbd_work *work,
>  	}
>
>  	ksmbd_debug(SMB, "new name %s\n", new_name);
> -	rc = ksmbd_vfs_kern_path(new_name, LOOKUP_NO_SYMLINKS, &path, 1);
> -	if (rc)
> +	rc = ksmbd_vfs_kern_path(work, new_name, LOOKUP_NO_SYMLINKS, &path, 1);
> +	if (rc) {
> +		if (rc != -ENOENT)
> +			goto out;
>  		file_present = false;
> -	else
> +	} else
>  		path_put(&path);
>
>  	if (ksmbd_share_veto_filename(share, new_name)) {
> @@ -5456,10 +5449,12 @@ static int smb2_create_link(struct ksmbd_work
> *work,
>  	}
>
>  	ksmbd_debug(SMB, "target name is %s\n", target_name);
> -	rc = ksmbd_vfs_kern_path(link_name, LOOKUP_NO_SYMLINKS, &path, 0);
> -	if (rc)
> +	rc = ksmbd_vfs_kern_path(work, link_name, LOOKUP_NO_SYMLINKS, &path, 0);
> +	if (rc) {
> +		if (rc != -ENOENT)
> +			goto out;
>  		file_present = false;
> -	else
> +	} else
>  		path_put(&path);
>
>  	if (file_info->ReplaceIfExists) {
> @@ -5975,7 +5970,7 @@ int smb2_set_info(struct ksmbd_work *work)
>  	return 0;
>
>  err_out:
> -	if (rc == -EACCES || rc == -EPERM)
> +	if (rc == -EACCES || rc == -EPERM || rc == -EXDEV)
>  		rsp->hdr.Status = STATUS_ACCESS_DENIED;
>  	else if (rc == -EINVAL)
>  		rsp->hdr.Status = STATUS_INVALID_PARAMETER;
> diff --git a/fs/ksmbd/vfs.c b/fs/ksmbd/vfs.c
> index 3733e4944c1d..c5ff3d1e1ca4 100644
> --- a/fs/ksmbd/vfs.c
> +++ b/fs/ksmbd/vfs.c
> @@ -19,6 +19,8 @@
>  #include <linux/sched/xacct.h>
>  #include <linux/crc32c.h>
>
> +#include "../internal.h"	/* for vfs_path_lookup */
> +
>  #include "glob.h"
>  #include "oplock.h"
>  #include "connection.h"
> @@ -1213,33 +1215,48 @@ static int ksmbd_vfs_lookup_in_dir(struct path *dir,
> char *name, size_t namelen)
>   *
>   * Return:	0 on success, otherwise error
>   */
> -int ksmbd_vfs_kern_path(char *name, unsigned int flags, struct path *path,
> -			bool caseless)
> +int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name,
> +			unsigned int flags, struct path *path, bool caseless)
>  {
> +	struct ksmbd_share_config *share_conf = work->tcon->share_conf;
> +	char *filepath;
>  	int err;
>
> -	if (name[0] != '/')
> +	/* name must start with the share path */
> +	if (strlen(name) < share_conf->path_sz ||
> +	    strncmp(name, share_conf->path, share_conf->path_sz) != 0)
>  		return -EINVAL;
> +	else if (strlen(name) == share_conf->path_sz) {
> +		*path = share_conf->vfs_path;
> +		path_get(path);
> +		return 0;
> +	}
> +
> +	filepath = kstrdup(name + share_conf->path_sz + 1,
It seems to be for skipping the share path. When converting name, we
don't need append   path name given from request to share path if we
use vfs_path_lookup(). we can also optimize convert_to_nt_pathname()
function if share path is not included in name.

Thanks!
> +			   GFP_KERNEL);
> +	if (!filepath)
> +		return -ENOMEM;
>
> -	err = kern_path(name, flags, path);
> -	if (!err)
> +	flags |= LOOKUP_BENEATH;
> +	err = vfs_path_lookup(share_conf->vfs_path.dentry,
> +			      share_conf->vfs_path.mnt,
> +			      filepath,
> +			      flags,
> +			      path);
> +	if (!err) {
> +		kfree(filepath);
>  		return 0;
> +	}
>
>  	if (caseless) {
> -		char *filepath;
>  		struct path parent;
>  		size_t path_len, remain_len;
>
> -		filepath = kstrdup(name, GFP_KERNEL);
> -		if (!filepath)
> -			return -ENOMEM;
> -
>  		path_len = strlen(filepath);
> -		remain_len = path_len - 1;
> +		remain_len = path_len;
>
> -		err = kern_path("/", flags, &parent);
> -		if (err)
> -			goto out;
> +		parent = share_conf->vfs_path;
> +		path_get(&parent);
>
>  		while (d_can_lookup(parent.dentry)) {
>  			char *filename = filepath + path_len - remain_len;
> @@ -1252,21 +1269,21 @@ int ksmbd_vfs_kern_path(char *name, unsigned int
> flags, struct path *path,
>
>  			err = ksmbd_vfs_lookup_in_dir(&parent, filename,
>  						      filename_len);
> -			if (err) {
> -				path_put(&parent);
> +			path_put(&parent);
> +			if (err)
>  				goto out;
> -			}
>
> -			path_put(&parent);
>  			next[0] = '\0';
>
> -			err = kern_path(filepath, flags, &parent);
> +			err = vfs_path_lookup(share_conf->vfs_path.dentry,
> +					      share_conf->vfs_path.mnt,
> +					      filepath,
> +					      flags,
> +					      &parent);
>  			if (err)
>  				goto out;
> -
> -			if (is_last) {
> -				path->mnt = parent.mnt;
> -				path->dentry = parent.dentry;
> +			else if (is_last) {
> +				*path = parent;
>  				goto out;
>  			}
>
> @@ -1276,9 +1293,9 @@ int ksmbd_vfs_kern_path(char *name, unsigned int
> flags, struct path *path,
>
>  		path_put(&parent);
>  		err = -EINVAL;
> -out:
> -		kfree(filepath);
>  	}
> +out:
> +	kfree(filepath);
>  	return err;
>  }
>
> diff --git a/fs/ksmbd/vfs.h b/fs/ksmbd/vfs.h
> index 85db50abdb24..7089c39e9271 100644
> --- a/fs/ksmbd/vfs.h
> +++ b/fs/ksmbd/vfs.h
> @@ -152,7 +152,8 @@ int ksmbd_vfs_xattr_stream_name(char *stream_name, char
> **xattr_stream_name,
>  				size_t *xattr_stream_name_size, int s_type);
>  int ksmbd_vfs_remove_xattr(struct user_namespace *user_ns,
>  			   struct dentry *dentry, char *attr_name);
> -int ksmbd_vfs_kern_path(char *name, unsigned int flags, struct path *path,
> +int ksmbd_vfs_kern_path(struct ksmbd_work *work,
> +			char *name, unsigned int flags, struct path *path,
>  			bool caseless);
>  int ksmbd_vfs_empty_dir(struct ksmbd_file *fp);
>  void ksmbd_vfs_set_fadvise(struct file *filp, __le32 option);
> --
> 2.25.1
>
>



[Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux