Commit 1ae78a14 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '6.4-rc-ksmbd-server-fixes' of git://git.samba.org/ksmbd

Pull ksmbd server updates from Steve French:

 - SMB3.1.1 negotiate context fixes and cleanup

 - new lock_rename_child VFS helper

 - ksmbd fix to avoid unlink race and to use the new VFS helper to avoid
   rename race

* tag '6.4-rc-ksmbd-server-fixes' of git://git.samba.org/ksmbd:
  ksmbd: fix racy issue from using ->d_parent and ->d_name
  ksmbd: remove unused compression negotiate ctx packing
  ksmbd: avoid duplicate negotiate ctx offset increments
  ksmbd: set NegotiateContextCount once instead of every inc
  fs: introduce lock_rename_child() helper
  ksmbd: remove internal.h include
parents 4e1c80ae 74d7970f
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -59,8 +59,6 @@ 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);
extern int vfs_path_lookup(struct dentry *, struct vfsmount *,
			   const char *, unsigned int, struct path *);
int do_rmdir(int dfd, struct filename *name);
int do_unlinkat(int dfd, struct filename *name);
int may_linkat(struct mnt_idmap *idmap, const struct path *link);
+49 −154
Original line number Diff line number Diff line
@@ -756,19 +756,6 @@ static void build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt,
	pneg_ctxt->Ciphers[0] = cipher_type;
}

static void build_compression_ctxt(struct smb2_compression_capabilities_context *pneg_ctxt,
				   __le16 comp_algo)
{
	pneg_ctxt->ContextType = SMB2_COMPRESSION_CAPABILITIES;
	pneg_ctxt->DataLength =
		cpu_to_le16(sizeof(struct smb2_compression_capabilities_context)
			- sizeof(struct smb2_neg_context));
	pneg_ctxt->Reserved = cpu_to_le32(0);
	pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(1);
	pneg_ctxt->Flags = cpu_to_le32(0);
	pneg_ctxt->CompressionAlgorithms[0] = comp_algo;
}

static void build_sign_cap_ctxt(struct smb2_signing_capabilities *pneg_ctxt,
				__le16 sign_algo)
{
@@ -808,7 +795,7 @@ static void assemble_neg_contexts(struct ksmbd_conn *conn,
				  struct smb2_negotiate_rsp *rsp,
				  void *smb2_buf_len)
{
	char *pneg_ctxt = (char *)rsp +
	char * const pneg_ctxt = (char *)rsp +
			le32_to_cpu(rsp->NegotiateContextOffset);
	int neg_ctxt_cnt = 1;
	int ctxt_size;
@@ -817,61 +804,46 @@ static void assemble_neg_contexts(struct ksmbd_conn *conn,
		    "assemble SMB2_PREAUTH_INTEGRITY_CAPABILITIES context\n");
	build_preauth_ctxt((struct smb2_preauth_neg_context *)pneg_ctxt,
			   conn->preauth_info->Preauth_HashId);
	rsp->NegotiateContextCount = cpu_to_le16(neg_ctxt_cnt);
	inc_rfc1001_len(smb2_buf_len, AUTH_GSS_PADDING);
	ctxt_size = sizeof(struct smb2_preauth_neg_context);
	/* Round to 8 byte boundary */
	pneg_ctxt += round_up(sizeof(struct smb2_preauth_neg_context), 8);

	if (conn->cipher_type) {
		/* Round to 8 byte boundary */
		ctxt_size = round_up(ctxt_size, 8);
		ksmbd_debug(SMB,
			    "assemble SMB2_ENCRYPTION_CAPABILITIES context\n");
		build_encrypt_ctxt((struct smb2_encryption_neg_context *)pneg_ctxt,
		build_encrypt_ctxt((struct smb2_encryption_neg_context *)
				   (pneg_ctxt + ctxt_size),
				   conn->cipher_type);
		rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
		neg_ctxt_cnt++;
		ctxt_size += sizeof(struct smb2_encryption_neg_context) + 2;
		/* Round to 8 byte boundary */
		pneg_ctxt +=
			round_up(sizeof(struct smb2_encryption_neg_context) + 2,
				 8);
	}

	if (conn->compress_algorithm) {
		ctxt_size = round_up(ctxt_size, 8);
		ksmbd_debug(SMB,
			    "assemble SMB2_COMPRESSION_CAPABILITIES context\n");
		/* Temporarily set to SMB3_COMPRESS_NONE */
		build_compression_ctxt((struct smb2_compression_capabilities_context *)pneg_ctxt,
				       conn->compress_algorithm);
		rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
		ctxt_size += sizeof(struct smb2_compression_capabilities_context) + 2;
		/* Round to 8 byte boundary */
		pneg_ctxt += round_up(sizeof(struct smb2_compression_capabilities_context) + 2,
				      8);
	}
	/* compression context not yet supported */
	WARN_ON(conn->compress_algorithm != SMB3_COMPRESS_NONE);

	if (conn->posix_ext_supported) {
		ctxt_size = round_up(ctxt_size, 8);
		ksmbd_debug(SMB,
			    "assemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n");
		build_posix_ctxt((struct smb2_posix_neg_context *)pneg_ctxt);
		rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
		build_posix_ctxt((struct smb2_posix_neg_context *)
				 (pneg_ctxt + ctxt_size));
		neg_ctxt_cnt++;
		ctxt_size += sizeof(struct smb2_posix_neg_context);
		/* Round to 8 byte boundary */
		pneg_ctxt += round_up(sizeof(struct smb2_posix_neg_context), 8);
	}

	if (conn->signing_negotiated) {
		ctxt_size = round_up(ctxt_size, 8);
		ksmbd_debug(SMB,
			    "assemble SMB2_SIGNING_CAPABILITIES context\n");
		build_sign_cap_ctxt((struct smb2_signing_capabilities *)pneg_ctxt,
		build_sign_cap_ctxt((struct smb2_signing_capabilities *)
				    (pneg_ctxt + ctxt_size),
				    conn->signing_algorithm);
		rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
		neg_ctxt_cnt++;
		ctxt_size += sizeof(struct smb2_signing_capabilities) + 2;
	}

	rsp->NegotiateContextCount = cpu_to_le16(neg_ctxt_cnt);
	inc_rfc1001_len(smb2_buf_len, ctxt_size);
}

@@ -2436,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);
@@ -2727,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
@@ -2737,7 +2711,6 @@ 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;
			}

@@ -2745,26 +2718,23 @@ int smb2_open(struct ksmbd_work *work)
				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) {
@@ -2892,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;
			}
@@ -3264,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;
@@ -5418,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;
@@ -5481,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);
@@ -5496,46 +5445,17 @@ 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);
	return rc;
}
@@ -5576,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",
@@ -5607,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);
@@ -5784,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;
@@ -5799,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,
+188 −249
Original line number Diff line number Diff line
@@ -18,8 +18,7 @@
#include <linux/vmalloc.h>
#include <linux/sched/xacct.h>
#include <linux/crc32c.h>

#include "../internal.h"	/* for vfs_path_lookup */
#include <linux/namei.h>

#include "glob.h"
#include "oplock.h"
@@ -37,19 +36,6 @@
#include "mgmt/user_session.h"
#include "mgmt/user_config.h"

static char *extract_last_component(char *path)
{
	char *p = strrchr(path, '/');

	if (p && p[1] != '\0') {
		*p = '\0';
		p++;
	} else {
		p = NULL;
	}
	return p;
}

static void ksmbd_vfs_inherit_owner(struct ksmbd_work *work,
				    struct inode *parent_inode,
				    struct inode *inode)
@@ -63,65 +49,77 @@ static void ksmbd_vfs_inherit_owner(struct ksmbd_work *work,

/**
 * ksmbd_vfs_lock_parent() - lock parent dentry if it is stable
 *
 * the parent dentry got by dget_parent or @parent could be
 * unstable, we try to lock a parent inode and lookup the
 * child dentry again.
 *
 * the reference count of @parent isn't incremented.
 */
int ksmbd_vfs_lock_parent(struct mnt_idmap *idmap, struct dentry *parent,
			  struct dentry *child)
int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child)
{
	struct dentry *dentry;
	int ret = 0;

	inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
	dentry = lookup_one(idmap, child->d_name.name, parent,
			    child->d_name.len);
	if (IS_ERR(dentry)) {
		ret = PTR_ERR(dentry);
		goto out_err;
	}

	if (dentry != child) {
		ret = -ESTALE;
		dput(dentry);
		goto out_err;
	if (child->d_parent != parent) {
		inode_unlock(d_inode(parent));
		return -ENOENT;
	}

	dput(dentry);
	return 0;
out_err:
	inode_unlock(d_inode(parent));
	return ret;
}

int ksmbd_vfs_may_delete(struct mnt_idmap *idmap,
			 struct dentry *dentry)
static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
					char *pathname, unsigned int flags,
					struct path *path)
{
	struct dentry *parent;
	int ret;
	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;

	parent = dget_parent(dentry);
	ret = ksmbd_vfs_lock_parent(idmap, parent, dentry);
	if (ret) {
		dput(parent);
		return ret;
	if (pathname[0] == '\0') {
		pathname = share_conf->path;
		root_share_path = NULL;
	} else {
		flags |= LOOKUP_BENEATH;
	}

	ret = inode_permission(idmap, d_inode(parent),
			       MAY_EXEC | MAY_WRITE);
	filename = getname_kernel(pathname);
	if (IS_ERR(filename))
		return PTR_ERR(filename);

	inode_unlock(d_inode(parent));
	dput(parent);
	return ret;
	err = vfs_path_parent_lookup(filename, flags,
				     &parent_path, &last, &type,
				     root_share_path);
	putname(filename);
	if (err)
		return err;

	if (unlikely(type != LAST_NORM)) {
		path_put(&parent_path);
		return -ENOENT;
	}

	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;

	if (d_is_negative(d)) {
		dput(d);
		goto err_out;
	}

	path->dentry = d;
	path->mnt = share_conf->vfs_path.mnt;
	path_put(&parent_path);

	return 0;

err_out:
	inode_unlock(parent_path.dentry->d_inode);
	path_put(&parent_path);
	return -ENOENT;
}

int ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap,
				   struct dentry *dentry, __le32 *daccess)
{
	struct dentry *parent;
	int ret = 0;

	*daccess = cpu_to_le32(FILE_READ_ATTRIBUTES | READ_CONTROL);
@@ -138,18 +136,9 @@ int ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap,
	if (!inode_permission(idmap, d_inode(dentry), MAY_OPEN | MAY_EXEC))
		*daccess |= FILE_EXECUTE_LE;

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

	if (!inode_permission(idmap, d_inode(parent), MAY_EXEC | MAY_WRITE))
	if (!inode_permission(idmap, d_inode(dentry->d_parent), MAY_EXEC | MAY_WRITE))
		*daccess |= FILE_DELETE_LE;

	inode_unlock(d_inode(parent));
	dput(parent);
	return ret;
}

@@ -582,54 +571,32 @@ int ksmbd_vfs_fsync(struct ksmbd_work *work, u64 fid, u64 p_id)
 *
 * Return:	0 on success, otherwise error
 */
int ksmbd_vfs_remove_file(struct ksmbd_work *work, char *name)
int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path)
{
	struct mnt_idmap *idmap;
	struct path path;
	struct dentry *parent;
	struct dentry *parent = path->dentry->d_parent;
	int err;

	if (ksmbd_override_fsids(work))
		return -ENOMEM;

	err = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS, &path, false);
	if (err) {
		ksmbd_debug(VFS, "can't get %s, err %d\n", name, err);
		ksmbd_revert_fsids(work);
		return err;
	}

	idmap = mnt_idmap(path.mnt);
	parent = dget_parent(path.dentry);
	err = ksmbd_vfs_lock_parent(idmap, parent, path.dentry);
	if (err) {
		dput(parent);
		path_put(&path);
		ksmbd_revert_fsids(work);
		return err;
	}

	if (!d_inode(path.dentry)->i_nlink) {
	if (!d_inode(path->dentry)->i_nlink) {
		err = -ENOENT;
		goto out_err;
	}

	if (S_ISDIR(d_inode(path.dentry)->i_mode)) {
		err = vfs_rmdir(idmap, d_inode(parent), path.dentry);
	idmap = mnt_idmap(path->mnt);
	if (S_ISDIR(d_inode(path->dentry)->i_mode)) {
		err = vfs_rmdir(idmap, d_inode(parent), path->dentry);
		if (err && err != -ENOTEMPTY)
			ksmbd_debug(VFS, "%s: rmdir failed, err %d\n", name,
				    err);
			ksmbd_debug(VFS, "rmdir failed, err %d\n", err);
	} else {
		err = vfs_unlink(idmap, d_inode(parent), path.dentry, NULL);
		err = vfs_unlink(idmap, d_inode(parent), path->dentry, NULL);
		if (err)
			ksmbd_debug(VFS, "%s: unlink failed, err %d\n", name,
				    err);
			ksmbd_debug(VFS, "unlink failed, err %d\n", err);
	}

out_err:
	inode_unlock(d_inode(parent));
	dput(parent);
	path_put(&path);
	ksmbd_revert_fsids(work);
	return err;
}
@@ -688,149 +655,114 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname,
	return err;
}

static int ksmbd_validate_entry_in_use(struct dentry *src_dent)
int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
		     char *newname, int flags)
{
	struct dentry *dst_dent;

	spin_lock(&src_dent->d_lock);
	list_for_each_entry(dst_dent, &src_dent->d_subdirs, d_child) {
		struct ksmbd_file *child_fp;
	struct dentry *old_parent, *new_dentry, *trap;
	struct dentry *old_child = old_path->dentry;
	struct path new_path;
	struct qstr new_last;
	struct renamedata rd;
	struct filename *to;
	struct ksmbd_share_config *share_conf = work->tcon->share_conf;
	struct ksmbd_file *parent_fp;
	int new_type;
	int err, lookup_flags = LOOKUP_NO_SYMLINKS;

		if (d_really_is_negative(dst_dent))
			continue;
	if (ksmbd_override_fsids(work))
		return -ENOMEM;

		child_fp = ksmbd_lookup_fd_inode(d_inode(dst_dent));
		if (child_fp) {
			spin_unlock(&src_dent->d_lock);
			ksmbd_debug(VFS, "Forbid rename, sub file/dir is in use\n");
			return -EACCES;
		}
	to = getname_kernel(newname);
	if (IS_ERR(to)) {
		err = PTR_ERR(to);
		goto revert_fsids;
	}
	spin_unlock(&src_dent->d_lock);

	return 0;
retry:
	err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH,
				     &new_path, &new_last, &new_type,
				     &share_conf->vfs_path);
	if (err)
		goto out1;

	if (old_path->mnt != new_path.mnt) {
		err = -EXDEV;
		goto out2;
	}

static int __ksmbd_vfs_rename(struct ksmbd_work *work,
			      struct mnt_idmap *src_idmap,
			      struct dentry *src_dent_parent,
			      struct dentry *src_dent,
			      struct mnt_idmap *dst_idmap,
			      struct dentry *dst_dent_parent,
			      struct dentry *trap_dent,
			      char *dst_name)
{
	struct dentry *dst_dent;
	int err;
	trap = lock_rename_child(old_child, new_path.dentry);

	if (!work->tcon->posix_extensions) {
		err = ksmbd_validate_entry_in_use(src_dent);
		if (err)
			return err;
	old_parent = dget(old_child->d_parent);
	if (d_unhashed(old_child)) {
		err = -EINVAL;
		goto out3;
	}

	if (d_really_is_negative(src_dent_parent))
		return -ENOENT;
	if (d_really_is_negative(dst_dent_parent))
		return -ENOENT;
	if (d_really_is_negative(src_dent))
		return -ENOENT;
	if (src_dent == trap_dent)
		return -EINVAL;

	if (ksmbd_override_fsids(work))
		return -ENOMEM;
	parent_fp = ksmbd_lookup_fd_inode(d_inode(old_child->d_parent));
	if (parent_fp) {
		if (parent_fp->daccess & FILE_DELETE_LE) {
			pr_err("parent dir is opened with delete access\n");
			err = -ESHARE;
			ksmbd_fd_put(work, parent_fp);
			goto out3;
		}
		ksmbd_fd_put(work, parent_fp);
	}

	dst_dent = lookup_one(dst_idmap, dst_name,
			      dst_dent_parent, strlen(dst_name));
	err = PTR_ERR(dst_dent);
	if (IS_ERR(dst_dent)) {
		pr_err("lookup failed %s [%d]\n", dst_name, err);
		goto out;
	new_dentry = lookup_one_qstr_excl(&new_last, new_path.dentry,
					  lookup_flags | LOOKUP_RENAME_TARGET);
	if (IS_ERR(new_dentry)) {
		err = PTR_ERR(new_dentry);
		goto out3;
	}

	err = -ENOTEMPTY;
	if (dst_dent != trap_dent && !d_really_is_positive(dst_dent)) {
		struct renamedata rd = {
			.old_mnt_idmap	= src_idmap,
			.old_dir	= d_inode(src_dent_parent),
			.old_dentry	= src_dent,
			.new_mnt_idmap	= dst_idmap,
			.new_dir	= d_inode(dst_dent_parent),
			.new_dentry	= dst_dent,
		};
		err = vfs_rename(&rd);
	if (d_is_symlink(new_dentry)) {
		err = -EACCES;
		goto out4;
	}
	if (err)
		pr_err("vfs_rename failed err %d\n", err);
	if (dst_dent)
		dput(dst_dent);
out:
	ksmbd_revert_fsids(work);
	return err;

	if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) {
		err = -EEXIST;
		goto out4;
	}

int ksmbd_vfs_fp_rename(struct ksmbd_work *work, struct ksmbd_file *fp,
			char *newname)
{
	struct mnt_idmap *idmap;
	struct path dst_path;
	struct dentry *src_dent_parent, *dst_dent_parent;
	struct dentry *src_dent, *trap_dent, *src_child;
	char *dst_name;
	int err;
	if (old_child == trap) {
		err = -EINVAL;
		goto out4;
	}

	dst_name = extract_last_component(newname);
	if (!dst_name) {
		dst_name = newname;
		newname = "";
	if (new_dentry == trap) {
		err = -ENOTEMPTY;
		goto out4;
	}

	src_dent_parent = dget_parent(fp->filp->f_path.dentry);
	src_dent = fp->filp->f_path.dentry;
	rd.old_mnt_idmap	= mnt_idmap(old_path->mnt),
	rd.old_dir		= d_inode(old_parent),
	rd.old_dentry		= old_child,
	rd.new_mnt_idmap	= mnt_idmap(new_path.mnt),
	rd.new_dir		= new_path.dentry->d_inode,
	rd.new_dentry		= new_dentry,
	rd.flags		= flags,
	err = vfs_rename(&rd);
	if (err)
		ksmbd_debug(VFS, "vfs_rename failed err %d\n", err);

	err = ksmbd_vfs_kern_path(work, newname,
				  LOOKUP_NO_SYMLINKS | LOOKUP_DIRECTORY,
				  &dst_path, false);
	if (err) {
		ksmbd_debug(VFS, "Cannot get path for %s [%d]\n", newname, err);
		goto out;
out4:
	dput(new_dentry);
out3:
	dput(old_parent);
	unlock_rename(old_parent, new_path.dentry);
out2:
	path_put(&new_path);

	if (retry_estale(err, lookup_flags)) {
		lookup_flags |= LOOKUP_REVAL;
		goto retry;
	}
	dst_dent_parent = dst_path.dentry;

	trap_dent = lock_rename(src_dent_parent, dst_dent_parent);
	dget(src_dent);
	dget(dst_dent_parent);
	idmap = file_mnt_idmap(fp->filp);
	src_child = lookup_one(idmap, src_dent->d_name.name, src_dent_parent,
			       src_dent->d_name.len);
	if (IS_ERR(src_child)) {
		err = PTR_ERR(src_child);
		goto out_lock;
	}

	if (src_child != src_dent) {
		err = -ESTALE;
		dput(src_child);
		goto out_lock;
	}
	dput(src_child);

	err = __ksmbd_vfs_rename(work,
				 idmap,
				 src_dent_parent,
				 src_dent,
				 mnt_idmap(dst_path.mnt),
				 dst_dent_parent,
				 trap_dent,
				 dst_name);
out_lock:
	dput(src_dent);
	dput(dst_dent_parent);
	unlock_rename(src_dent_parent, dst_dent_parent);
	path_put(&dst_path);
out:
	dput(src_dent_parent);
out1:
	putname(to);
revert_fsids:
	ksmbd_revert_fsids(work);
	return err;
}

@@ -1081,14 +1013,16 @@ int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
	return vfs_removexattr(idmap, dentry, attr_name);
}

int ksmbd_vfs_unlink(struct mnt_idmap *idmap,
		     struct dentry *dir, struct dentry *dentry)
int ksmbd_vfs_unlink(struct file *filp)
{
	int err = 0;
	struct dentry *dir, *dentry = filp->f_path.dentry;
	struct mnt_idmap *idmap = file_mnt_idmap(filp);

	err = ksmbd_vfs_lock_parent(idmap, dir, dentry);
	dir = dget_parent(dentry);
	err = ksmbd_vfs_lock_parent(dir, dentry);
	if (err)
		return err;
		goto out;
	dget(dentry);

	if (S_ISDIR(d_inode(dentry)->i_mode))
@@ -1100,6 +1034,8 @@ int ksmbd_vfs_unlink(struct mnt_idmap *idmap,
	inode_unlock(d_inode(dir));
	if (err)
		ksmbd_debug(VFS, "failed to delete, err %d\n", err);
out:
	dput(dir);

	return err;
}
@@ -1202,7 +1138,7 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name,
}

/**
 * ksmbd_vfs_kern_path() - lookup a file and get path info
 * ksmbd_vfs_kern_path_locked() - lookup a file and get path info
 * @name:	file path that is relative to share
 * @flags:	lookup flags
 * @path:	if lookup succeed, return path info
@@ -1210,24 +1146,20 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name,
 *
 * Return:	0 on success, otherwise error
 */
int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name,
			unsigned int flags, struct path *path, bool caseless)
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
			       unsigned int flags, struct path *path,
			       bool caseless)
{
	struct ksmbd_share_config *share_conf = work->tcon->share_conf;
	int err;
	struct path parent_path;

	flags |= LOOKUP_BENEATH;
	err = vfs_path_lookup(share_conf->vfs_path.dentry,
			      share_conf->vfs_path.mnt,
			      name,
			      flags,
			      path);
	err = ksmbd_vfs_path_lookup_locked(share_conf, name, flags, path);
	if (!err)
		return 0;
		return err;

	if (caseless) {
		char *filepath;
		struct path parent;
		size_t path_len, remain_len;

		filepath = kstrdup(name, GFP_KERNEL);
@@ -1237,10 +1169,10 @@ int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name,
		path_len = strlen(filepath);
		remain_len = path_len;

		parent = share_conf->vfs_path;
		path_get(&parent);
		parent_path = share_conf->vfs_path;
		path_get(&parent_path);

		while (d_can_lookup(parent.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;
@@ -1249,12 +1181,11 @@ int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name,
			if (filename_len == 0)
				break;

			err = ksmbd_vfs_lookup_in_dir(&parent, filename,
			err = ksmbd_vfs_lookup_in_dir(&parent_path, filename,
						      filename_len,
						      work->conn->um);
			path_put(&parent);
			if (err)
				goto out;
				goto out2;

			next[0] = '\0';

@@ -1262,23 +1193,31 @@ int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name,
					      share_conf->vfs_path.mnt,
					      filepath,
					      flags,
					      &parent);
					      path);
			if (err)
				goto out;
			else if (is_last) {
				*path = parent;
				goto out;
			}
				goto out2;
			else if (is_last)
				goto out1;
			path_put(&parent_path);
			parent_path = *path;

			next[0] = '/';
			remain_len -= filename_len + 1;
		}

		path_put(&parent);
		err = -EINVAL;
out:
out2:
		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);
	}
	return err;
}

+8 −11

File changed.

Preview size limit exceeded, changes collapsed.

+1 −4

File changed.

Preview size limit exceeded, changes collapsed.

Loading