Commit 2b57a432 authored by Namjae Jeon's avatar Namjae Jeon Committed by Steve French

ksmbd: check if a mount point is crossed during path lookup

Since commit 74d7970f ("ksmbd: fix racy issue from using ->d_parent and
->d_name"), ksmbd can not lookup cross mount points. If last component is
a cross mount point during path lookup, check if it is crossed to follow it
down. And allow path lookup to cross a mount point when a crossmnt
parameter is set to 'yes' in smb.conf.

Cc: stable@vger.kernel.org
Fixes: 74d7970f ("ksmbd: fix racy issue from using ->d_parent and ->d_name")
Signed-off-by: default avatarNamjae Jeon <linkinjeon@kernel.org>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 0266a2f7
...@@ -352,7 +352,8 @@ enum KSMBD_TREE_CONN_STATUS { ...@@ -352,7 +352,8 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_SHARE_FLAG_STREAMS BIT(11) #define KSMBD_SHARE_FLAG_STREAMS BIT(11)
#define KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS BIT(12) #define KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS BIT(12)
#define KSMBD_SHARE_FLAG_ACL_XATTR BIT(13) #define KSMBD_SHARE_FLAG_ACL_XATTR BIT(13)
#define KSMBD_SHARE_FLAG_UPDATE BIT(14) #define KSMBD_SHARE_FLAG_UPDATE BIT(14)
#define KSMBD_SHARE_FLAG_CROSSMNT BIT(15)
/* /*
* Tree connect request flags. * Tree connect request flags.
......
...@@ -2467,8 +2467,9 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon, ...@@ -2467,8 +2467,9 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
} }
} }
static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name, static int smb2_creat(struct ksmbd_work *work, struct path *parent_path,
int open_flags, umode_t posix_mode, bool is_dir) struct path *path, char *name, int open_flags,
umode_t posix_mode, bool is_dir)
{ {
struct ksmbd_tree_connect *tcon = work->tcon; struct ksmbd_tree_connect *tcon = work->tcon;
struct ksmbd_share_config *share = tcon->share_conf; struct ksmbd_share_config *share = tcon->share_conf;
...@@ -2495,7 +2496,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name, ...@@ -2495,7 +2496,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
return rc; return rc;
} }
rc = ksmbd_vfs_kern_path_locked(work, name, 0, path, 0); rc = ksmbd_vfs_kern_path_locked(work, name, 0, parent_path, path, 0);
if (rc) { if (rc) {
pr_err("cannot get linux path (%s), err = %d\n", pr_err("cannot get linux path (%s), err = %d\n",
name, rc); name, rc);
...@@ -2565,7 +2566,7 @@ int smb2_open(struct ksmbd_work *work) ...@@ -2565,7 +2566,7 @@ int smb2_open(struct ksmbd_work *work)
struct ksmbd_tree_connect *tcon = work->tcon; struct ksmbd_tree_connect *tcon = work->tcon;
struct smb2_create_req *req; struct smb2_create_req *req;
struct smb2_create_rsp *rsp; struct smb2_create_rsp *rsp;
struct path path; struct path path, parent_path;
struct ksmbd_share_config *share = tcon->share_conf; struct ksmbd_share_config *share = tcon->share_conf;
struct ksmbd_file *fp = NULL; struct ksmbd_file *fp = NULL;
struct file *filp = NULL; struct file *filp = NULL;
...@@ -2786,7 +2787,8 @@ int smb2_open(struct ksmbd_work *work) ...@@ -2786,7 +2787,8 @@ int smb2_open(struct ksmbd_work *work)
goto err_out1; goto err_out1;
} }
rc = ksmbd_vfs_kern_path_locked(work, name, LOOKUP_NO_SYMLINKS, &path, 1); rc = ksmbd_vfs_kern_path_locked(work, name, LOOKUP_NO_SYMLINKS,
&parent_path, &path, 1);
if (!rc) { if (!rc) {
file_present = true; file_present = true;
...@@ -2906,7 +2908,8 @@ int smb2_open(struct ksmbd_work *work) ...@@ -2906,7 +2908,8 @@ int smb2_open(struct ksmbd_work *work)
/*create file if not present */ /*create file if not present */
if (!file_present) { if (!file_present) {
rc = smb2_creat(work, &path, name, open_flags, posix_mode, rc = smb2_creat(work, &parent_path, &path, name, open_flags,
posix_mode,
req->CreateOptions & FILE_DIRECTORY_FILE_LE); req->CreateOptions & FILE_DIRECTORY_FILE_LE);
if (rc) { if (rc) {
if (rc == -ENOENT) { if (rc == -ENOENT) {
...@@ -3321,8 +3324,9 @@ int smb2_open(struct ksmbd_work *work) ...@@ -3321,8 +3324,9 @@ int smb2_open(struct ksmbd_work *work)
err_out: err_out:
if (file_present || created) { if (file_present || created) {
inode_unlock(d_inode(path.dentry->d_parent)); inode_unlock(d_inode(parent_path.dentry));
dput(path.dentry); path_put(&path);
path_put(&parent_path);
} }
ksmbd_revert_fsids(work); ksmbd_revert_fsids(work);
err_out1: err_out1:
...@@ -5545,7 +5549,7 @@ static int smb2_create_link(struct ksmbd_work *work, ...@@ -5545,7 +5549,7 @@ static int smb2_create_link(struct ksmbd_work *work,
struct nls_table *local_nls) struct nls_table *local_nls)
{ {
char *link_name = NULL, *target_name = NULL, *pathname = NULL; char *link_name = NULL, *target_name = NULL, *pathname = NULL;
struct path path; struct path path, parent_path;
bool file_present = false; bool file_present = false;
int rc; int rc;
...@@ -5575,7 +5579,7 @@ static int smb2_create_link(struct ksmbd_work *work, ...@@ -5575,7 +5579,7 @@ static int smb2_create_link(struct ksmbd_work *work,
ksmbd_debug(SMB, "target name is %s\n", target_name); ksmbd_debug(SMB, "target name is %s\n", target_name);
rc = ksmbd_vfs_kern_path_locked(work, link_name, LOOKUP_NO_SYMLINKS, rc = ksmbd_vfs_kern_path_locked(work, link_name, LOOKUP_NO_SYMLINKS,
&path, 0); &parent_path, &path, 0);
if (rc) { if (rc) {
if (rc != -ENOENT) if (rc != -ENOENT)
goto out; goto out;
...@@ -5605,8 +5609,9 @@ static int smb2_create_link(struct ksmbd_work *work, ...@@ -5605,8 +5609,9 @@ static int smb2_create_link(struct ksmbd_work *work,
rc = -EINVAL; rc = -EINVAL;
out: out:
if (file_present) { if (file_present) {
inode_unlock(d_inode(path.dentry->d_parent)); inode_unlock(d_inode(parent_path.dentry));
path_put(&path); path_put(&path);
path_put(&parent_path);
} }
if (!IS_ERR(link_name)) if (!IS_ERR(link_name))
kfree(link_name); kfree(link_name);
......
...@@ -63,13 +63,13 @@ int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child) ...@@ -63,13 +63,13 @@ int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child)
static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf, static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
char *pathname, unsigned int flags, char *pathname, unsigned int flags,
struct path *parent_path,
struct path *path) struct path *path)
{ {
struct qstr last; struct qstr last;
struct filename *filename; struct filename *filename;
struct path *root_share_path = &share_conf->vfs_path; struct path *root_share_path = &share_conf->vfs_path;
int err, type; int err, type;
struct path parent_path;
struct dentry *d; struct dentry *d;
if (pathname[0] == '\0') { if (pathname[0] == '\0') {
...@@ -84,7 +84,7 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf, ...@@ -84,7 +84,7 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
return PTR_ERR(filename); return PTR_ERR(filename);
err = vfs_path_parent_lookup(filename, flags, err = vfs_path_parent_lookup(filename, flags,
&parent_path, &last, &type, parent_path, &last, &type,
root_share_path); root_share_path);
if (err) { if (err) {
putname(filename); putname(filename);
...@@ -92,13 +92,13 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf, ...@@ -92,13 +92,13 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
} }
if (unlikely(type != LAST_NORM)) { if (unlikely(type != LAST_NORM)) {
path_put(&parent_path); path_put(parent_path);
putname(filename); putname(filename);
return -ENOENT; return -ENOENT;
} }
inode_lock_nested(parent_path.dentry->d_inode, I_MUTEX_PARENT); inode_lock_nested(parent_path->dentry->d_inode, I_MUTEX_PARENT);
d = lookup_one_qstr_excl(&last, parent_path.dentry, 0); d = lookup_one_qstr_excl(&last, parent_path->dentry, 0);
if (IS_ERR(d)) if (IS_ERR(d))
goto err_out; goto err_out;
...@@ -108,15 +108,22 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf, ...@@ -108,15 +108,22 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
} }
path->dentry = d; path->dentry = d;
path->mnt = share_conf->vfs_path.mnt; path->mnt = mntget(parent_path->mnt);
path_put(&parent_path);
putname(filename); if (test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_CROSSMNT)) {
err = follow_down(path, 0);
if (err < 0) {
path_put(path);
goto err_out;
}
}
putname(filename);
return 0; return 0;
err_out: err_out:
inode_unlock(parent_path.dentry->d_inode); inode_unlock(d_inode(parent_path->dentry));
path_put(&parent_path); path_put(parent_path);
putname(filename); putname(filename);
return -ENOENT; return -ENOENT;
} }
...@@ -1195,14 +1202,14 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name, ...@@ -1195,14 +1202,14 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name,
* Return: 0 on success, otherwise error * Return: 0 on success, otherwise error
*/ */
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name, int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
unsigned int flags, struct path *path, unsigned int flags, struct path *parent_path,
bool caseless) struct path *path, bool caseless)
{ {
struct ksmbd_share_config *share_conf = work->tcon->share_conf; struct ksmbd_share_config *share_conf = work->tcon->share_conf;
int err; int err;
struct path parent_path;
err = ksmbd_vfs_path_lookup_locked(share_conf, name, flags, path); err = ksmbd_vfs_path_lookup_locked(share_conf, name, flags, parent_path,
path);
if (!err) if (!err)
return 0; return 0;
...@@ -1217,10 +1224,10 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name, ...@@ -1217,10 +1224,10 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
path_len = strlen(filepath); path_len = strlen(filepath);
remain_len = path_len; remain_len = path_len;
parent_path = share_conf->vfs_path; *parent_path = share_conf->vfs_path;
path_get(&parent_path); path_get(parent_path);
while (d_can_lookup(parent_path.dentry)) { while (d_can_lookup(parent_path->dentry)) {
char *filename = filepath + path_len - remain_len; char *filename = filepath + path_len - remain_len;
char *next = strchrnul(filename, '/'); char *next = strchrnul(filename, '/');
size_t filename_len = next - filename; size_t filename_len = next - filename;
...@@ -1229,7 +1236,7 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name, ...@@ -1229,7 +1236,7 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
if (filename_len == 0) if (filename_len == 0)
break; break;
err = ksmbd_vfs_lookup_in_dir(&parent_path, filename, err = ksmbd_vfs_lookup_in_dir(parent_path, filename,
filename_len, filename_len,
work->conn->um); work->conn->um);
if (err) if (err)
...@@ -1246,8 +1253,8 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name, ...@@ -1246,8 +1253,8 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
goto out2; goto out2;
else if (is_last) else if (is_last)
goto out1; goto out1;
path_put(&parent_path); path_put(parent_path);
parent_path = *path; *parent_path = *path;
next[0] = '/'; next[0] = '/';
remain_len -= filename_len + 1; remain_len -= filename_len + 1;
...@@ -1255,16 +1262,17 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name, ...@@ -1255,16 +1262,17 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
err = -EINVAL; err = -EINVAL;
out2: out2:
path_put(&parent_path); path_put(parent_path);
out1: out1:
kfree(filepath); kfree(filepath);
} }
if (!err) { if (!err) {
err = ksmbd_vfs_lock_parent(parent_path.dentry, path->dentry); err = ksmbd_vfs_lock_parent(parent_path->dentry, path->dentry);
if (err) if (err) {
dput(path->dentry); path_put(path);
path_put(&parent_path); path_put(parent_path);
}
} }
return err; return err;
} }
......
...@@ -115,8 +115,8 @@ int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name, ...@@ -115,8 +115,8 @@ int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name,
int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap, int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
const struct path *path, char *attr_name); const struct path *path, char *attr_name);
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name, int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
unsigned int flags, struct path *path, unsigned int flags, struct path *parent_path,
bool caseless); struct path *path, bool caseless);
struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work, struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
const char *name, const char *name,
unsigned int flags, unsigned int flags,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment