diff mbox series

ksmbd: use LOOKUP_BENEATH to prevent the out of share access

Message ID 20210923092459.740044-1-hyc.lee@gmail.com
State New
Headers show
Series ksmbd: use LOOKUP_BENEATH to prevent the out of share access | expand

Commit Message

Hyunchul Lee Sept. 23, 2021, 9:24 a.m. UTC
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@gmail.com>
Cc: Ralph Boehme <slow@samba.org>
Cc: Steve French <smfrench@gmail.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
---
 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(-)

Comments

Namjae Jeon Sept. 23, 2021, 10:42 a.m. UTC | #1
2021-09-23 18:24 GMT+09:00, Hyunchul Lee <hyc.lee@gmail.com>:
> 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@gmail.com>
> Cc: Ralph Boehme <slow@samba.org>
> Cc: Steve French <smfrench@gmail.com>
> Cc: Namjae Jeon <linkinjeon@kernel.org>
> Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
> ---
>  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
>
>
diff mbox series

Patch

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,
+			   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);