ksmbd: check if a mount point is crossed during path lookup
[ Upstream commit 2b57a4322b1b14348940744fdc02f9a86cbbdbeb ] Since commit 74d7970febf7 ("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: 74d7970febf7 ("ksmbd: fix racy issue from using ->d_parent and ->d_name") Signed-off-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
parent
d782f42eed
commit
0a9b91f45e
@ -352,7 +352,8 @@ enum KSMBD_TREE_CONN_STATUS {
|
||||
#define KSMBD_SHARE_FLAG_STREAMS BIT(11)
|
||||
#define KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS BIT(12)
|
||||
#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.
|
||||
|
@ -2475,8 +2475,9 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
|
||||
}
|
||||
}
|
||||
|
||||
static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
|
||||
int open_flags, umode_t posix_mode, bool is_dir)
|
||||
static int smb2_creat(struct ksmbd_work *work, struct path *parent_path,
|
||||
struct path *path, char *name, int open_flags,
|
||||
umode_t posix_mode, bool is_dir)
|
||||
{
|
||||
struct ksmbd_tree_connect *tcon = work->tcon;
|
||||
struct ksmbd_share_config *share = tcon->share_conf;
|
||||
@ -2503,7 +2504,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
|
||||
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) {
|
||||
pr_err("cannot get linux path (%s), err = %d\n",
|
||||
name, rc);
|
||||
@ -2573,7 +2574,7 @@ int smb2_open(struct ksmbd_work *work)
|
||||
struct ksmbd_tree_connect *tcon = work->tcon;
|
||||
struct smb2_create_req *req;
|
||||
struct smb2_create_rsp *rsp;
|
||||
struct path path;
|
||||
struct path path, parent_path;
|
||||
struct ksmbd_share_config *share = tcon->share_conf;
|
||||
struct ksmbd_file *fp = NULL;
|
||||
struct file *filp = NULL;
|
||||
@ -2794,7 +2795,8 @@ int smb2_open(struct ksmbd_work *work)
|
||||
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) {
|
||||
file_present = true;
|
||||
|
||||
@ -2914,7 +2916,8 @@ int smb2_open(struct ksmbd_work *work)
|
||||
|
||||
/*create file if not 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);
|
||||
if (rc) {
|
||||
if (rc == -ENOENT) {
|
||||
@ -3329,8 +3332,9 @@ int smb2_open(struct ksmbd_work *work)
|
||||
|
||||
err_out:
|
||||
if (file_present || created) {
|
||||
inode_unlock(d_inode(path.dentry->d_parent));
|
||||
dput(path.dentry);
|
||||
inode_unlock(d_inode(parent_path.dentry));
|
||||
path_put(&path);
|
||||
path_put(&parent_path);
|
||||
}
|
||||
ksmbd_revert_fsids(work);
|
||||
err_out1:
|
||||
@ -5553,7 +5557,7 @@ static int smb2_create_link(struct ksmbd_work *work,
|
||||
struct nls_table *local_nls)
|
||||
{
|
||||
char *link_name = NULL, *target_name = NULL, *pathname = NULL;
|
||||
struct path path;
|
||||
struct path path, parent_path;
|
||||
bool file_present = false;
|
||||
int rc;
|
||||
|
||||
@ -5583,7 +5587,7 @@ static int smb2_create_link(struct ksmbd_work *work,
|
||||
|
||||
ksmbd_debug(SMB, "target name is %s\n", target_name);
|
||||
rc = ksmbd_vfs_kern_path_locked(work, link_name, LOOKUP_NO_SYMLINKS,
|
||||
&path, 0);
|
||||
&parent_path, &path, 0);
|
||||
if (rc) {
|
||||
if (rc != -ENOENT)
|
||||
goto out;
|
||||
@ -5613,8 +5617,9 @@ static int smb2_create_link(struct ksmbd_work *work,
|
||||
rc = -EINVAL;
|
||||
out:
|
||||
if (file_present) {
|
||||
inode_unlock(d_inode(path.dentry->d_parent));
|
||||
inode_unlock(d_inode(parent_path.dentry));
|
||||
path_put(&path);
|
||||
path_put(&parent_path);
|
||||
}
|
||||
if (!IS_ERR(link_name))
|
||||
kfree(link_name);
|
||||
|
@ -64,13 +64,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,
|
||||
char *pathname, unsigned int flags,
|
||||
struct path *parent_path,
|
||||
struct path *path)
|
||||
{
|
||||
struct qstr last;
|
||||
struct filename *filename;
|
||||
struct path *root_share_path = &share_conf->vfs_path;
|
||||
int err, type;
|
||||
struct path parent_path;
|
||||
struct dentry *d;
|
||||
|
||||
if (pathname[0] == '\0') {
|
||||
@ -85,7 +85,7 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
|
||||
return PTR_ERR(filename);
|
||||
|
||||
err = vfs_path_parent_lookup(filename, flags,
|
||||
&parent_path, &last, &type,
|
||||
parent_path, &last, &type,
|
||||
root_share_path);
|
||||
if (err) {
|
||||
putname(filename);
|
||||
@ -93,13 +93,13 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
|
||||
}
|
||||
|
||||
if (unlikely(type != LAST_NORM)) {
|
||||
path_put(&parent_path);
|
||||
path_put(parent_path);
|
||||
putname(filename);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
inode_lock_nested(parent_path.dentry->d_inode, I_MUTEX_PARENT);
|
||||
d = lookup_one_qstr_excl(&last, parent_path.dentry, 0);
|
||||
inode_lock_nested(parent_path->dentry->d_inode, I_MUTEX_PARENT);
|
||||
d = lookup_one_qstr_excl(&last, parent_path->dentry, 0);
|
||||
if (IS_ERR(d))
|
||||
goto err_out;
|
||||
|
||||
@ -109,15 +109,22 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
|
||||
}
|
||||
|
||||
path->dentry = d;
|
||||
path->mnt = share_conf->vfs_path.mnt;
|
||||
path_put(&parent_path);
|
||||
putname(filename);
|
||||
path->mnt = mntget(parent_path->mnt);
|
||||
|
||||
if (test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_CROSSMNT)) {
|
||||
err = follow_down(path);
|
||||
if (err < 0) {
|
||||
path_put(path);
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
|
||||
putname(filename);
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
inode_unlock(parent_path.dentry->d_inode);
|
||||
path_put(&parent_path);
|
||||
inode_unlock(d_inode(parent_path->dentry));
|
||||
path_put(parent_path);
|
||||
putname(filename);
|
||||
return -ENOENT;
|
||||
}
|
||||
@ -1196,14 +1203,14 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name,
|
||||
* Return: 0 on success, otherwise error
|
||||
*/
|
||||
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
|
||||
unsigned int flags, struct path *path,
|
||||
bool caseless)
|
||||
unsigned int flags, struct path *parent_path,
|
||||
struct path *path, bool caseless)
|
||||
{
|
||||
struct ksmbd_share_config *share_conf = work->tcon->share_conf;
|
||||
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)
|
||||
return 0;
|
||||
|
||||
@ -1218,10 +1225,10 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
|
||||
path_len = strlen(filepath);
|
||||
remain_len = path_len;
|
||||
|
||||
parent_path = share_conf->vfs_path;
|
||||
path_get(&parent_path);
|
||||
*parent_path = share_conf->vfs_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 *next = strchrnul(filename, '/');
|
||||
size_t filename_len = next - filename;
|
||||
@ -1230,7 +1237,7 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
|
||||
if (filename_len == 0)
|
||||
break;
|
||||
|
||||
err = ksmbd_vfs_lookup_in_dir(&parent_path, filename,
|
||||
err = ksmbd_vfs_lookup_in_dir(parent_path, filename,
|
||||
filename_len,
|
||||
work->conn->um);
|
||||
if (err)
|
||||
@ -1247,8 +1254,8 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
|
||||
goto out2;
|
||||
else if (is_last)
|
||||
goto out1;
|
||||
path_put(&parent_path);
|
||||
parent_path = *path;
|
||||
path_put(parent_path);
|
||||
*parent_path = *path;
|
||||
|
||||
next[0] = '/';
|
||||
remain_len -= filename_len + 1;
|
||||
@ -1256,16 +1263,17 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
|
||||
|
||||
err = -EINVAL;
|
||||
out2:
|
||||
path_put(&parent_path);
|
||||
path_put(parent_path);
|
||||
out1:
|
||||
kfree(filepath);
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
err = ksmbd_vfs_lock_parent(parent_path.dentry, path->dentry);
|
||||
if (err)
|
||||
dput(path->dentry);
|
||||
path_put(&parent_path);
|
||||
err = ksmbd_vfs_lock_parent(parent_path->dentry, path->dentry);
|
||||
if (err) {
|
||||
path_put(path);
|
||||
path_put(parent_path);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
@ -115,8 +115,8 @@ int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name,
|
||||
int ksmbd_vfs_remove_xattr(struct user_namespace *user_ns,
|
||||
const struct path *path, char *attr_name);
|
||||
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
|
||||
unsigned int flags, struct path *path,
|
||||
bool caseless);
|
||||
unsigned int flags, struct path *parent_path,
|
||||
struct path *path, bool caseless);
|
||||
struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
|
||||
const char *name,
|
||||
unsigned int flags,
|
||||
|
Loading…
Reference in New Issue
Block a user