Skip to content
Permalink
Browse files
ksmbd: fix racy issue from using ->d_parent and ->d_name
Al pointed out that ksmbd has racy issue from using ->d_parent and ->d_name
in ksmbd_vfs_unlink and smb2_vfs_rename(). and he suggested changing from
the way it start with dget_parent(), which can cause retry loop and
unexpected errors, to find the parent of child, lock it and then look for
a child in locked directory.

This patch introduce a new helper(vfs_path_parent_lookup()) to avoid
out of share access and export vfs functions like the following ones to use
vfs_path_parent_lookup() and filename_parentat().
 - __lookup_hash().
 - getname_kernel() and putname().
 - filename_parentat()

Suggested-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
  • Loading branch information
namjaejeon authored and intel-lab-lkp committed Feb 8, 2022
1 parent 555f3d7 commit f4cb65c1c670f5332092a7eb75d569bbd4e46a5f
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 392 deletions.
@@ -58,6 +58,9 @@ extern int finish_clean_context(struct fs_context *fc);
*/
extern int filename_lookup(int dfd, struct filename *name, unsigned flags,
struct path *path, struct path *root);
int vfs_path_parent_lookup(struct dentry *dentry, struct vfsmount *mnt,
struct filename *filename, unsigned int flags,
struct path *parent, struct qstr *last, int *type);
extern int vfs_path_lookup(struct dentry *, struct vfsmount *,
const char *, unsigned int, struct path *);
int do_rmdir(int dfd, struct filename *name);
@@ -161,8 +161,8 @@ void ksmbd_session_destroy(struct ksmbd_session *sess)
if (sess->user)
ksmbd_free_user(sess->user);

ksmbd_tree_conn_session_logoff(sess);
ksmbd_destroy_file_table(&sess->file_table);
ksmbd_tree_conn_session_logoff(sess);
ksmbd_session_rpc_clear_list(sess);
free_channel_list(sess);
kfree(sess->Preauth_HashValue);
@@ -2803,11 +2803,9 @@ int smb2_open(struct ksmbd_work *work)
if (!file_present) {
daccess = cpu_to_le32(GENERIC_ALL_FLAGS);
} else {
rc = ksmbd_vfs_query_maximal_access(user_ns,
path.dentry,
&daccess);
if (rc)
goto err_out;
ksmbd_vfs_query_maximal_access(share, user_ns,
path.dentry, name,
&daccess);
already_permitted = true;
}
maximal_access = daccess;
@@ -2869,8 +2867,7 @@ int smb2_open(struct ksmbd_work *work)

if ((daccess & FILE_DELETE_LE) ||
(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
rc = ksmbd_vfs_may_delete(user_ns,
path.dentry);
rc = ksmbd_vfs_may_delete(share, name);
if (rc)
goto err_out;
}
@@ -3187,8 +3184,8 @@ int smb2_open(struct ksmbd_work *work)
struct create_context *mxac_ccontext;

if (maximal_access == 0)
ksmbd_vfs_query_maximal_access(user_ns,
path.dentry,
ksmbd_vfs_query_maximal_access(share, user_ns,
path.dentry, name,
&maximal_access);
mxac_ccontext = (struct create_context *)(rsp->Buffer +
le32_to_cpu(rsp->CreateContextsLength));
@@ -5369,44 +5366,19 @@ int smb2_echo(struct ksmbd_work *work)

static int smb2_rename(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct user_namespace *user_ns,
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 = d_path(&fp->filp->f_path, 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;
@@ -5432,7 +5404,7 @@ static int smb2_rename(struct ksmbd_work *work,
if (rc)
goto out;

rc = ksmbd_vfs_setxattr(user_ns,
rc = ksmbd_vfs_setxattr(file_mnt_user_ns(fp->filp),
fp->filp->f_path.dentry,
xattr_stream_name,
NULL, 0, 0);
@@ -5447,47 +5419,23 @@ 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))
if (rc) {
kfree(new_name);
} else {
kfree(fp->filename);
fp->filename = new_name;
}
return rc;
}

@@ -5538,7 +5486,8 @@ static int smb2_create_link(struct ksmbd_work *work,

if (file_info->ReplaceIfExists) {
if (file_present) {
rc = ksmbd_vfs_remove_file(work, link_name);
rc = ksmbd_vfs_unlink(work->tcon->share_conf,
link_name);
if (rc) {
rc = -EINVAL;
ksmbd_debug(SMB, "cannot delete %s\n",
@@ -5738,12 +5687,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 user_namespace *user_ns;
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;
@@ -5753,30 +5696,7 @@ static int set_rename_info(struct ksmbd_work *work, struct ksmbd_file *fp,
le32_to_cpu(rename_info->FileNameLength))
return -EINVAL;

user_ns = file_mnt_user_ns(fp->filp);
if (ksmbd_stream_fd(fp))
goto next;

parent = dget_parent(dentry);
ret = ksmbd_vfs_lock_parent(user_ns, 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 (parent_fp) {
if (parent_fp->daccess & FILE_DELETE_LE) {
pr_err("parent dir is opened with delete access\n");
return -ESHARE;
}
}
next:
return smb2_rename(work, fp, user_ns, rename_info,
work->sess->conn->local_nls);
return smb2_rename(work, fp, rename_info, work->sess->conn->local_nls);
}

static int set_file_disposition_info(struct ksmbd_file *fp,

0 comments on commit f4cb65c

Please sign in to comment.