Skip to content

Commit

Permalink
ksmbd: fix racy issue from using ->d_parent and ->d_name
Browse files Browse the repository at this point in the history
Al pointed out that ksmbd has racy issue from using ->d_parent and ->d_name
in ksmbd_vfs_unlink and smb2_vfs_rename(). and use new lock_rename_child()
to lock stable parent while underlying rename racy.
Introduce vfs_path_parent_lookup helper to avoid out of share access and
export vfs functions like the following ones to use
vfs_path_parent_lookup().
 - rename __lookup_hash() to lookup_one_qstr_excl().
 - export lookup_one_qstr_excl().
 - export getname_kernel() and putname().

vfs_path_parent_lookup() is used for parent lookup of destination file
using absolute pathname given from FILE_RENAME_INFORMATION request.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
Namjae Jeon authored and Steve French committed Apr 24, 2023
1 parent af36c51 commit 74d7970
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 386 deletions.
147 changes: 35 additions & 112 deletions fs/ksmbd/smb2pdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2408,7 +2408,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
return rc;
}

rc = ksmbd_vfs_kern_path(work, name, 0, path, 0);
rc = ksmbd_vfs_kern_path_locked(work, name, 0, path, 0);
if (rc) {
pr_err("cannot get linux path (%s), err = %d\n",
name, rc);
Expand Down Expand Up @@ -2699,8 +2699,10 @@ int smb2_open(struct ksmbd_work *work)
goto err_out1;
}

rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS, &path, 1);
rc = ksmbd_vfs_kern_path_locked(work, name, LOOKUP_NO_SYMLINKS, &path, 1);
if (!rc) {
file_present = true;

if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
/*
* If file exists with under flags, return access
Expand All @@ -2709,34 +2711,30 @@ int smb2_open(struct ksmbd_work *work)
if (req->CreateDisposition == FILE_OVERWRITE_IF_LE ||
req->CreateDisposition == FILE_OPEN_IF_LE) {
rc = -EACCES;
path_put(&path);
goto err_out;
}

if (!test_tree_conn_flag(tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
ksmbd_debug(SMB,
"User does not have write permission\n");
rc = -EACCES;
path_put(&path);
goto err_out;
}
} else if (d_is_symlink(path.dentry)) {
rc = -EACCES;
path_put(&path);
goto err_out;
}
}

if (rc) {
file_present = true;
idmap = mnt_idmap(path.mnt);
} else {
if (rc != -ENOENT)
goto err_out;
ksmbd_debug(SMB, "can not get linux path for %s, rc = %d\n",
name, rc);
rc = 0;
} else {
file_present = true;
idmap = mnt_idmap(path.mnt);
}

if (stream_name) {
if (req->CreateOptions & FILE_DIRECTORY_FILE_LE) {
if (s_type == DATA_STREAM) {
Expand Down Expand Up @@ -2864,8 +2862,9 @@ int smb2_open(struct ksmbd_work *work)

if ((daccess & FILE_DELETE_LE) ||
(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
rc = ksmbd_vfs_may_delete(idmap,
path.dentry);
rc = inode_permission(idmap,
d_inode(path.dentry->d_parent),
MAY_EXEC | MAY_WRITE);
if (rc)
goto err_out;
}
Expand Down Expand Up @@ -3236,10 +3235,13 @@ int smb2_open(struct ksmbd_work *work)
}

err_out:
if (file_present || created)
path_put(&path);
if (file_present || created) {
inode_unlock(d_inode(path.dentry->d_parent));
dput(path.dentry);
}
ksmbd_revert_fsids(work);
err_out1:

if (rc) {
if (rc == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
Expand Down Expand Up @@ -5390,44 +5392,19 @@ int smb2_echo(struct ksmbd_work *work)

static int smb2_rename(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct mnt_idmap *idmap,
struct smb2_file_rename_info *file_info,
struct nls_table *local_nls)
{
struct ksmbd_share_config *share = fp->tcon->share_conf;
char *new_name = NULL, *abs_oldname = NULL, *old_name = NULL;
char *pathname = NULL;
struct path path;
bool file_present = true;
int rc;
char *new_name = NULL;
int rc, flags = 0;

ksmbd_debug(SMB, "setting FILE_RENAME_INFO\n");
pathname = kmalloc(PATH_MAX, GFP_KERNEL);
if (!pathname)
return -ENOMEM;

abs_oldname = file_path(fp->filp, pathname, PATH_MAX);
if (IS_ERR(abs_oldname)) {
rc = -EINVAL;
goto out;
}
old_name = strrchr(abs_oldname, '/');
if (old_name && old_name[1] != '\0') {
old_name++;
} else {
ksmbd_debug(SMB, "can't get last component in path %s\n",
abs_oldname);
rc = -ENOENT;
goto out;
}

new_name = smb2_get_name(file_info->FileName,
le32_to_cpu(file_info->FileNameLength),
local_nls);
if (IS_ERR(new_name)) {
rc = PTR_ERR(new_name);
goto out;
}
if (IS_ERR(new_name))
return PTR_ERR(new_name);

if (strchr(new_name, ':')) {
int s_type;
Expand All @@ -5453,7 +5430,7 @@ static int smb2_rename(struct ksmbd_work *work,
if (rc)
goto out;

rc = ksmbd_vfs_setxattr(idmap,
rc = ksmbd_vfs_setxattr(file_mnt_idmap(fp->filp),
fp->filp->f_path.dentry,
xattr_stream_name,
NULL, 0, 0);
Expand All @@ -5468,47 +5445,18 @@ static int smb2_rename(struct ksmbd_work *work,
}

ksmbd_debug(SMB, "new name %s\n", new_name);
rc = ksmbd_vfs_kern_path(work, new_name, LOOKUP_NO_SYMLINKS, &path, 1);
if (rc) {
if (rc != -ENOENT)
goto out;
file_present = false;
} else {
path_put(&path);
}

if (ksmbd_share_veto_filename(share, new_name)) {
rc = -ENOENT;
ksmbd_debug(SMB, "Can't rename vetoed file: %s\n", new_name);
goto out;
}

if (file_info->ReplaceIfExists) {
if (file_present) {
rc = ksmbd_vfs_remove_file(work, new_name);
if (rc) {
if (rc != -ENOTEMPTY)
rc = -EINVAL;
ksmbd_debug(SMB, "cannot delete %s, rc %d\n",
new_name, rc);
goto out;
}
}
} else {
if (file_present &&
strncmp(old_name, path.dentry->d_name.name, strlen(old_name))) {
rc = -EEXIST;
ksmbd_debug(SMB,
"cannot rename already existing file\n");
goto out;
}
}
if (!file_info->ReplaceIfExists)
flags = RENAME_NOREPLACE;

rc = ksmbd_vfs_fp_rename(work, fp, new_name);
rc = ksmbd_vfs_rename(work, &fp->filp->f_path, new_name, flags);
out:
kfree(pathname);
if (!IS_ERR(new_name))
kfree(new_name);
kfree(new_name);
return rc;
}

Expand Down Expand Up @@ -5548,18 +5496,17 @@ static int smb2_create_link(struct ksmbd_work *work,
}

ksmbd_debug(SMB, "target name is %s\n", target_name);
rc = ksmbd_vfs_kern_path(work, link_name, LOOKUP_NO_SYMLINKS, &path, 0);
rc = ksmbd_vfs_kern_path_locked(work, link_name, LOOKUP_NO_SYMLINKS,
&path, 0);
if (rc) {
if (rc != -ENOENT)
goto out;
file_present = false;
} else {
path_put(&path);
}

if (file_info->ReplaceIfExists) {
if (file_present) {
rc = ksmbd_vfs_remove_file(work, link_name);
rc = ksmbd_vfs_remove_file(work, &path);
if (rc) {
rc = -EINVAL;
ksmbd_debug(SMB, "cannot delete %s\n",
Expand All @@ -5579,6 +5526,10 @@ static int smb2_create_link(struct ksmbd_work *work,
if (rc)
rc = -EINVAL;
out:
if (file_present) {
inode_unlock(d_inode(path.dentry->d_parent));
path_put(&path);
}
if (!IS_ERR(link_name))
kfree(link_name);
kfree(pathname);
Expand Down Expand Up @@ -5756,12 +5707,6 @@ static int set_rename_info(struct ksmbd_work *work, struct ksmbd_file *fp,
struct smb2_file_rename_info *rename_info,
unsigned int buf_len)
{
struct mnt_idmap *idmap;
struct ksmbd_file *parent_fp;
struct dentry *parent;
struct dentry *dentry = fp->filp->f_path.dentry;
int ret;

if (!(fp->daccess & FILE_DELETE_LE)) {
pr_err("no right to delete : 0x%x\n", fp->daccess);
return -EACCES;
Expand All @@ -5771,32 +5716,10 @@ static int set_rename_info(struct ksmbd_work *work, struct ksmbd_file *fp,
le32_to_cpu(rename_info->FileNameLength))
return -EINVAL;

idmap = file_mnt_idmap(fp->filp);
if (ksmbd_stream_fd(fp))
goto next;

parent = dget_parent(dentry);
ret = ksmbd_vfs_lock_parent(idmap, parent, dentry);
if (ret) {
dput(parent);
return ret;
}

parent_fp = ksmbd_lookup_fd_inode(d_inode(parent));
inode_unlock(d_inode(parent));
dput(parent);
if (!le32_to_cpu(rename_info->FileNameLength))
return -EINVAL;

if (parent_fp) {
if (parent_fp->daccess & FILE_DELETE_LE) {
pr_err("parent dir is opened with delete access\n");
ksmbd_fd_put(work, parent_fp);
return -ESHARE;
}
ksmbd_fd_put(work, parent_fp);
}
next:
return smb2_rename(work, fp, idmap, rename_info,
work->conn->local_nls);
return smb2_rename(work, fp, rename_info, work->conn->local_nls);
}

static int set_file_disposition_info(struct ksmbd_file *fp,
Expand Down
Loading

0 comments on commit 74d7970

Please sign in to comment.