Commit e5cf61fa authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'v6.17-rc-smb3-server-fixes' of git://git.samba.org/ksmbd

Pull smb server updates from Steve French:

 - Fix mtime/ctime reporting issue

 - Auth fixes, including two session setup race bugs reported by ZDI

 - Locking improvement in query directory

 - Fix for potential deadlock in creating hardlinks

 - Improvements to path name processing

* tag 'v6.17-rc-smb3-server-fixes' of git://git.samba.org/ksmbd:
  ksmbd: fix corrupted mtime and ctime in smb2_open
  ksmbd: fix Preauh_HashValue race condition
  ksmbd: check return value of xa_store() in krb5_authenticate
  ksmbd: fix null pointer dereference error in generate_encryptionkey
  smb/server: add ksmbd_vfs_kern_path()
  smb/server: avoid deadlock when linking with ReplaceIfExists
  smb/server: simplify ksmbd_vfs_kern_path_locked()
  smb/server: use lookup_one_unlocked()
parents cb6bbff7 4f8ff948
Loading
Loading
Loading
Loading
+41 −50
Original line number Diff line number Diff line
@@ -1594,7 +1594,7 @@ static int krb5_authenticate(struct ksmbd_work *work,
	struct ksmbd_conn *conn = work->conn;
	struct ksmbd_session *sess = work->sess;
	char *in_blob, *out_blob;
	struct channel *chann = NULL;
	struct channel *chann = NULL, *old;
	u64 prev_sess_id;
	int in_len, out_len;
	int retval;
@@ -1621,11 +1621,24 @@ static int krb5_authenticate(struct ksmbd_work *work,

	rsp->SecurityBufferLength = cpu_to_le16(out_len);

	if ((conn->sign || server_conf.enforced_signing) ||
	/*
	 * If session state is SMB2_SESSION_VALID, We can assume
	 * that it is reauthentication. And the user/password
	 * has been verified, so return it here.
	 */
	if (sess->state == SMB2_SESSION_VALID) {
		if (conn->binding)
			goto binding_session;
		return 0;
	}

	if ((rsp->SessionFlags != SMB2_SESSION_FLAG_IS_GUEST_LE &&
	    (conn->sign || server_conf.enforced_signing)) ||
	    (req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
		sess->sign = true;

	if (smb3_encryption_negotiated(conn)) {
	if (smb3_encryption_negotiated(conn) &&
	    !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
		retval = conn->ops->generate_encryptionkey(conn, sess);
		if (retval) {
			ksmbd_debug(SMB,
@@ -1638,6 +1651,7 @@ static int krb5_authenticate(struct ksmbd_work *work,
		sess->sign = false;
	}

binding_session:
	if (conn->dialect >= SMB30_PROT_ID) {
		chann = lookup_chann_list(sess, conn);
		if (!chann) {
@@ -1646,7 +1660,12 @@ static int krb5_authenticate(struct ksmbd_work *work,
				return -ENOMEM;

			chann->conn = conn;
			xa_store(&sess->ksmbd_chann_list, (long)conn, chann, KSMBD_DEFAULT_GFP);
			old = xa_store(&sess->ksmbd_chann_list, (long)conn,
					chann, KSMBD_DEFAULT_GFP);
			if (xa_is_err(old)) {
				kfree(chann);
				return xa_err(old);
			}
		}
	}

@@ -1833,8 +1852,6 @@ int smb2_sess_setup(struct ksmbd_work *work)
				ksmbd_conn_set_good(conn);
				sess->state = SMB2_SESSION_VALID;
			}
			kfree(sess->Preauth_HashValue);
			sess->Preauth_HashValue = NULL;
		} else if (conn->preferred_auth_mech == KSMBD_AUTH_NTLMSSP) {
			if (negblob->MessageType == NtLmNegotiate) {
				rc = ntlm_negotiate(work, negblob, negblob_len, rsp);
@@ -1861,8 +1878,6 @@ int smb2_sess_setup(struct ksmbd_work *work)
						kfree(preauth_sess);
					}
				}
				kfree(sess->Preauth_HashValue);
				sess->Preauth_HashValue = NULL;
			} else {
				pr_info_ratelimited("Unknown NTLMSSP message type : 0x%x\n",
						le32_to_cpu(negblob->MessageType));
@@ -2581,7 +2596,7 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
	}
}

static int smb2_creat(struct ksmbd_work *work, struct path *parent_path,
static int smb2_creat(struct ksmbd_work *work,
		      struct path *path, char *name, int open_flags,
		      umode_t posix_mode, bool is_dir)
{
@@ -2610,7 +2625,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *parent_path,
			return rc;
	}

	rc = ksmbd_vfs_kern_path_locked(work, name, 0, parent_path, path, 0);
	rc = ksmbd_vfs_kern_path(work, name, 0, path, 0);
	if (rc) {
		pr_err("cannot get linux path (%s), err = %d\n",
		       name, rc);
@@ -2860,7 +2875,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, parent_path;
	struct path path;
	struct ksmbd_share_config *share = tcon->share_conf;
	struct ksmbd_file *fp = NULL;
	struct file *filp = NULL;
@@ -3116,8 +3131,8 @@ int smb2_open(struct ksmbd_work *work)
		goto err_out2;
	}

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

@@ -3238,7 +3253,7 @@ int smb2_open(struct ksmbd_work *work)

	/*create file if not present */
	if (!file_present) {
		rc = smb2_creat(work, &parent_path, &path, name, open_flags,
		rc = smb2_creat(work, &path, name, open_flags,
				posix_mode,
				req->CreateOptions & FILE_DIRECTORY_FILE_LE);
		if (rc) {
@@ -3443,7 +3458,7 @@ int smb2_open(struct ksmbd_work *work)
	}

	if (file_present || created)
		ksmbd_vfs_kern_path_unlock(&parent_path, &path);
		path_put(&path);

	if (!S_ISDIR(file_inode(filp)->i_mode) && open_flags & O_TRUNC &&
	    !fp->attrib_only && !stream_name) {
@@ -3724,7 +3739,7 @@ int smb2_open(struct ksmbd_work *work)

err_out:
	if (rc && (file_present || created))
		ksmbd_vfs_kern_path_unlock(&parent_path, &path);
		path_put(&path);

err_out1:
	ksmbd_revert_fsids(work);
@@ -4108,20 +4123,6 @@ struct smb2_query_dir_private {
	int			info_level;
};

static void lock_dir(struct ksmbd_file *dir_fp)
{
	struct dentry *dir = dir_fp->filp->f_path.dentry;

	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
}

static void unlock_dir(struct ksmbd_file *dir_fp)
{
	struct dentry *dir = dir_fp->filp->f_path.dentry;

	inode_unlock(d_inode(dir));
}

static int process_query_dir_entries(struct smb2_query_dir_private *priv)
{
	struct mnt_idmap	*idmap = file_mnt_idmap(priv->dir_fp->filp);
@@ -4136,12 +4137,10 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
		if (dentry_name(priv->d_info, priv->info_level))
			return -EINVAL;

		lock_dir(priv->dir_fp);
		dent = lookup_one(idmap,
		dent = lookup_one_unlocked(idmap,
					   &QSTR_LEN(priv->d_info->name,
						     priv->d_info->name_len),
					   priv->dir_fp->filp->f_path.dentry);
		unlock_dir(priv->dir_fp);

		if (IS_ERR(dent)) {
			ksmbd_debug(SMB, "Cannot lookup `%s' [%ld]\n",
@@ -6052,8 +6051,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, parent_path;
	bool file_present = false;
	struct path path;
	int rc;

	if (buf_len < (u64)sizeof(struct smb2_file_link_info) +
@@ -6082,15 +6080,12 @@ 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,
					&parent_path, &path, 0);
					&path, 0);
	if (rc) {
		if (rc != -ENOENT)
			goto out;
	} else
		file_present = true;

	} else {
		if (file_info->ReplaceIfExists) {
		if (file_present) {
			rc = ksmbd_vfs_remove_file(work, &path);
			if (rc) {
				rc = -EINVAL;
@@ -6098,21 +6093,17 @@ static int smb2_create_link(struct ksmbd_work *work,
					    link_name);
				goto out;
			}
		}
		} else {
		if (file_present) {
			rc = -EEXIST;
			ksmbd_debug(SMB, "link already exists\n");
			goto out;
		}
		ksmbd_vfs_kern_path_unlock(&path);
	}

	rc = ksmbd_vfs_link(work, target_name, link_name);
	if (rc)
		rc = -EINVAL;
out:
	if (file_present)
		ksmbd_vfs_kern_path_unlock(&parent_path, &path);

	if (!IS_ERR(link_name))
		kfree(link_name);
+128 −112
Original line number Diff line number Diff line
@@ -66,13 +66,12 @@ int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child)
	return 0;
}

static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
				 char *pathname, unsigned int flags,
					struct path *parent_path,
					struct path *path)
				 struct path *path, bool do_lock)
{
	struct qstr last;
	struct filename *filename;
	struct filename *filename __free(putname) = NULL;
	struct path *root_share_path = &share_conf->vfs_path;
	int err, type;
	struct dentry *d;
@@ -89,51 +88,57 @@ 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,
				     path, &last, &type,
				     root_share_path);
	if (err) {
		putname(filename);
	if (err)
		return err;
	}

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

	err = mnt_want_write(parent_path->mnt);
	if (do_lock) {
		err = mnt_want_write(path->mnt);
		if (err) {
		path_put(parent_path);
		putname(filename);
			path_put(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;
		inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
		d = lookup_one_qstr_excl(&last, path->dentry, 0);

		if (!IS_ERR(d)) {
			dput(path->dentry);
			path->dentry = d;
			return 0;
		}
		inode_unlock(path->dentry->d_inode);
		mnt_drop_write(path->mnt);
		path_put(path);
		return -ENOENT;
	}

	d = lookup_noperm_unlocked(&last, path->dentry);
	if (!IS_ERR(d) && d_is_negative(d)) {
		dput(d);
		d = ERR_PTR(-ENOENT);
	}
	if (IS_ERR(d)) {
		path_put(path);
		return -ENOENT;
	}
	dput(path->dentry);
	path->dentry = d;
	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;
			return -ENOENT;
		}
	}

	putname(filename);
	return 0;

err_out:
	inode_unlock(d_inode(parent_path->dentry));
	mnt_drop_write(parent_path->mnt);
	path_put(parent_path);
	putname(filename);
	return -ENOENT;
}

void ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap,
@@ -548,7 +553,8 @@ int ksmbd_vfs_getattr(const struct path *path, struct kstat *stat)
{
	int err;

	err = vfs_getattr(path, stat, STATX_BTIME, AT_STATX_SYNC_AS_STAT);
	err = vfs_getattr(path, stat, STATX_BASIC_STATS | STATX_BTIME,
			AT_STATX_SYNC_AS_STAT);
	if (err)
		pr_err("getattr failed, err %d\n", err);
	return err;
@@ -1198,41 +1204,28 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name,
	return ret;
}

/**
 * ksmbd_vfs_kern_path_locked() - lookup a file and get path info
 * @work:	work
 * @name:		file path that is relative to share
 * @flags:		lookup flags
 * @parent_path:	if lookup succeed, return parent_path info
 * @path:		if lookup succeed, return path info
 * @caseless:	caseless filename lookup
 *
 * Return:	0 on success, otherwise error
 */
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
			       unsigned int flags, struct path *parent_path,
			       struct path *path, bool caseless)
static
int __ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
			  unsigned int flags,
			  struct path *path, bool caseless, bool do_lock)
{
	struct ksmbd_share_config *share_conf = work->tcon->share_conf;
	struct path parent_path;
	size_t path_len, remain_len;
	int err;

	err = ksmbd_vfs_path_lookup_locked(share_conf, name, flags, parent_path,
					   path);
	if (!err)
		return 0;

	if (caseless) {
		char *filepath;
		size_t path_len, remain_len;
retry:
	err = ksmbd_vfs_path_lookup(share_conf, filepath, flags, path, do_lock);
	if (!err || !caseless)
		return err;

		filepath = 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;
@@ -1241,61 +1234,84 @@ 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);
		path_put(&parent_path);
		if (err)
				goto out2;

			goto out;
		if (is_last) {
			caseless = false;
			goto retry;
		}
		next[0] = '\0';

		err = vfs_path_lookup(share_conf->vfs_path.dentry,
				      share_conf->vfs_path.mnt,
				      filepath,
				      flags,
					      path);
			if (!is_last)
				      &parent_path);
		next[0] = '/';
		if (err)
				goto out2;
			else if (is_last)
				goto out1;
			path_put(parent_path);
			*parent_path = *path;
			goto out;

		remain_len -= filename_len + 1;
	}

	err = -EINVAL;
out2:
		path_put(parent_path);
	}

out1:
	if (!err) {
		err = mnt_want_write(parent_path->mnt);
		if (err) {
			path_put(path);
			path_put(parent_path);
	path_put(&parent_path);
out:
	return err;
}

		err = ksmbd_vfs_lock_parent(parent_path->dentry, path->dentry);
		if (err) {
			mnt_drop_write(parent_path->mnt);
			path_put(path);
			path_put(parent_path);
		}
/**
 * ksmbd_vfs_kern_path() - lookup a file and get path info
 * @work:		work
 * @filepath:		file path that is relative to share
 * @flags:		lookup flags
 * @path:		if lookup succeed, return path info
 * @caseless:	caseless filename lookup
 *
 * Perform the lookup, possibly crossing over any mount point.
 * On return no locks will be held and write-access to filesystem
 * won't have been checked.
 * Return:	0 if file was found, otherwise error
 */
int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
			unsigned int flags,
			struct path *path, bool caseless)
{
	return __ksmbd_vfs_kern_path(work, filepath, flags, path,
				     caseless, false);
}
	return err;

/**
 * ksmbd_vfs_kern_path_locked() - lookup a file and get path info
 * @work:		work
 * @filepath:		file path that is relative to share
 * @flags:		lookup flags
 * @path:		if lookup succeed, return path info
 * @caseless:	caseless filename lookup
 *
 * Perform the lookup, but don't cross over any mount point.
 * On return the parent of path->dentry will be locked and write-access to
 * filesystem will have been gained.
 * Return:	0 on if file was found, otherwise error
 */
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *filepath,
			       unsigned int flags,
			       struct path *path, bool caseless)
{
	return __ksmbd_vfs_kern_path(work, filepath, flags, path,
				     caseless, true);
}

void ksmbd_vfs_kern_path_unlock(struct path *parent_path, struct path *path)
void ksmbd_vfs_kern_path_unlock(struct path *path)
{
	inode_unlock(d_inode(parent_path->dentry));
	mnt_drop_write(parent_path->mnt);
	/* While lock is still held, ->d_parent is safe */
	inode_unlock(d_inode(path->dentry->d_parent));
	mnt_drop_write(path->mnt);
	path_put(path);
	path_put(parent_path);
}

struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
+5 −2
Original line number Diff line number Diff line
@@ -117,10 +117,13 @@ int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name,
int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
			   const struct path *path, char *attr_name,
			   bool get_write);
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 *parent_path,
			       unsigned int flags,
			       struct path *path, bool caseless);
void ksmbd_vfs_kern_path_unlock(struct path *parent_path, struct path *path);
void ksmbd_vfs_kern_path_unlock(struct path *path);
struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
					  const char *name,
					  unsigned int flags,