Skip to content

Commit

Permalink
ksmbd: check if a mount point is crossed during path lookup
Browse files Browse the repository at this point in the history
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.

Fixes: 74d7970febf7 ("ksmbd: fix racy issue from using ->d_parent and ->d_name")
Signed-off-by: Namjae Jeon <[email protected]>
  • Loading branch information
namjaejeon committed Jul 21, 2023
1 parent 35bf1bd commit a459652
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 35 deletions.
3 changes: 2 additions & 1 deletion ksmbd_netlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 27 additions & 7 deletions smb2pdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2535,8 +2535,14 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
}
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
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)
#else
static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
int open_flags, umode_t posix_mode, bool is_dir)
#endif
{
struct ksmbd_tree_connect *tcon = work->tcon;
struct ksmbd_share_config *share = tcon->share_conf;
Expand Down Expand Up @@ -2564,7 +2570,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
rc = ksmbd_vfs_kern_path_locked(work, name, 0, path, 0);
rc = ksmbd_vfs_kern_path_locked(work, name, 0, parent_path, path, 0);
#else
rc = ksmbd_vfs_kern_path(work, name, 0, path, 0);
#endif
Expand Down Expand Up @@ -2665,6 +2671,9 @@ int smb2_open(struct ksmbd_work *work)
struct smb2_create_req *req;
struct smb2_create_rsp *rsp;
struct path path;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
struct path parent_path;
#endif
struct ksmbd_share_config *share = tcon->share_conf;
struct ksmbd_file *fp = NULL;
struct file *filp = NULL;
Expand Down Expand Up @@ -2890,7 +2899,8 @@ int smb2_open(struct ksmbd_work *work)
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
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);
#else
rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS, &path, 1);
#endif
Expand Down Expand Up @@ -3038,7 +3048,12 @@ 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,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
rc = smb2_creat(work, &parent_path, &path, name, open_flags,
#else
rc = smb2_creat(work, &path, name, open_flags,
#endif
posix_mode,
req->CreateOptions & FILE_DIRECTORY_FILE_LE);
if (rc) {
if (rc == -ENOENT) {
Expand Down Expand Up @@ -3502,8 +3517,9 @@ int smb2_open(struct ksmbd_work *work)
err_out:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
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);
}
#else
if (file_present || created)
Expand Down Expand Up @@ -6036,6 +6052,9 @@ static int smb2_create_link(struct ksmbd_work *work,
{
char *link_name = NULL, *target_name = NULL, *pathname = NULL;
struct path path;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
struct path parent_path;
#endif
bool file_present = false;
int rc;

Expand Down Expand Up @@ -6066,7 +6085,7 @@ static int smb2_create_link(struct ksmbd_work *work,
ksmbd_debug(SMB, "target name is %s\n", target_name);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
rc = ksmbd_vfs_kern_path_locked(work, link_name, LOOKUP_NO_SYMLINKS,
&path, 0);
&parent_path, &path, 0);
#else
rc = ksmbd_vfs_kern_path(work, link_name, LOOKUP_NO_SYMLINKS, &path, 0);
#endif
Expand Down Expand Up @@ -6108,8 +6127,9 @@ static int smb2_create_link(struct ksmbd_work *work,
out:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
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);
}
#endif
if (!IS_ERR(link_name))
Expand Down
58 changes: 33 additions & 25 deletions vfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,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') {
Expand All @@ -114,21 +114,21 @@ 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);
return err;
}

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;

Expand All @@ -138,15 +138,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, 0);
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;
}
Expand Down Expand Up @@ -2703,14 +2710,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;

Expand All @@ -2725,10 +2732,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;
Expand All @@ -2737,7 +2744,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)
Expand All @@ -2754,25 +2761,26 @@ 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;
}

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;
}
Expand Down
4 changes: 2 additions & 2 deletions vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ int ksmbd_vfs_remove_xattr(struct user_namespace *user_ns,
const struct path *path, char *attr_name);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
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);
#else
int ksmbd_vfs_kern_path(struct ksmbd_work *work,
char *name, unsigned int flags, struct path *path,
Expand Down

0 comments on commit a459652

Please sign in to comment.