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

Merge tag 'v7.1-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6

Pull smb client fixes from Steve French:

 - Fix integer overflow in read

 - Fix smbdirect error cleanup

 - Multichannel reconnect fix

 - Add some missing defines and correct some references to protocol spec

 - Fix oob symlink read

* tag 'v7.1-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  smbdirect: Fix error cleanup in smbdirect_map_sges_from_iter()
  smb: client: avoid integer overflow in SMB2 READ length check
  cifs: client: stage smb3_reconfigure() updates and restore ctx on failure
  smb/client: fix possible infinite loop and oob read in symlink_data()
  SMB3.1.1: add missing QUERY_DIR info levels
parents fcbf68d3 c207f1d7
Loading
Loading
Loading
Loading
+108 −53
Original line number Diff line number Diff line
@@ -736,7 +736,7 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
					    void *data);
static int smb3_get_tree(struct fs_context *fc);
static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels);
static void smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels);
static int smb3_reconfigure(struct fs_context *fc);

static const struct fs_context_operations smb3_fs_context_ops = {
@@ -1010,25 +1010,34 @@ do { \

int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
{
	char *password = NULL, *password2 = NULL;

	if (ses->password &&
	    cifs_sb->ctx->password &&
	    strcmp(ses->password, cifs_sb->ctx->password)) {
		kfree_sensitive(cifs_sb->ctx->password);
		cifs_sb->ctx->password = kstrdup(ses->password, GFP_KERNEL);
		if (!cifs_sb->ctx->password)
		password = kstrdup(ses->password, GFP_KERNEL);
		if (!password)
			return -ENOMEM;
	}
	if (ses->password2 &&
	    cifs_sb->ctx->password2 &&
	    strcmp(ses->password2, cifs_sb->ctx->password2)) {
		kfree_sensitive(cifs_sb->ctx->password2);
		cifs_sb->ctx->password2 = kstrdup(ses->password2, GFP_KERNEL);
		if (!cifs_sb->ctx->password2) {
			kfree_sensitive(cifs_sb->ctx->password);
			cifs_sb->ctx->password = NULL;
		password2 = kstrdup(ses->password2, GFP_KERNEL);
		if (!password2) {
			kfree_sensitive(password);
			return -ENOMEM;
		}
	}

	if (password) {
		kfree_sensitive(cifs_sb->ctx->password);
		cifs_sb->ctx->password = password;
	}
	if (password2) {
		kfree_sensitive(cifs_sb->ctx->password2);
		cifs_sb->ctx->password2 = password2;
	}

	return 0;
}

@@ -1041,7 +1050,7 @@ int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_se
 * with the session's channel lock. This should be called whenever the maximum
 * allowed channels for a session changes (e.g., after a remount or reconfigure).
 */
static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels)
static void smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels)
{
	spin_lock(&ses->chan_lock);
	ses->chan_max = max_channels;
@@ -1051,12 +1060,15 @@ static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channe
static int smb3_reconfigure(struct fs_context *fc)
{
	struct smb3_fs_context *ctx = smb3_fc2context(fc);
	struct smb3_fs_context *new_ctx = NULL;
	struct smb3_fs_context *old_ctx = NULL;
	struct dentry *root = fc->root;
	struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
	struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
	unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
	char *new_password = NULL, *new_password2 = NULL;
	bool need_recon = false;
	bool need_mchan_update;
	int rc;

	if (ses->expired_pwd)
@@ -1066,6 +1078,16 @@ static int smb3_reconfigure(struct fs_context *fc)
	if (rc)
		return rc;

	old_ctx = kzalloc_obj(*old_ctx);
	if (!old_ctx)
		return -ENOMEM;

	rc = smb3_fs_context_dup(old_ctx, cifs_sb->ctx);
	if (rc) {
		kfree(old_ctx);
		return rc;
	}

	/*
	 * We can not change UNC/username/password/domainname/
	 * workstation_name/nodename/iocharset
@@ -1075,17 +1097,23 @@ static int smb3_reconfigure(struct fs_context *fc)
	STEAL_STRING(cifs_sb, ctx, UNC);
	STEAL_STRING(cifs_sb, ctx, source);
	STEAL_STRING(cifs_sb, ctx, username);
	STEAL_STRING(cifs_sb, ctx, domainname);
	STEAL_STRING(cifs_sb, ctx, nodename);
	STEAL_STRING(cifs_sb, ctx, iocharset);

	if (need_recon == false)
	if (!need_recon) {
		STEAL_STRING_SENSITIVE(cifs_sb, ctx, password);
	else  {
	} else {
		if (ctx->password) {
			new_password = kstrdup(ctx->password, GFP_KERNEL);
			if (!new_password)
				return -ENOMEM;
		} else
			if (!new_password) {
				rc = -ENOMEM;
				goto restore_ctx;
			}
		} else {
			STEAL_STRING_SENSITIVE(cifs_sb, ctx, password);
		}
	}

	/*
	 * if a new password2 has been specified, then reset it's value
@@ -1094,11 +1122,29 @@ static int smb3_reconfigure(struct fs_context *fc)
	if (ctx->password2) {
		new_password2 = kstrdup(ctx->password2, GFP_KERNEL);
		if (!new_password2) {
			kfree_sensitive(new_password);
			return -ENOMEM;
			rc = -ENOMEM;
			goto restore_ctx;
		}
	} else
	} else {
		STEAL_STRING_SENSITIVE(cifs_sb, ctx, password2);
	}

	/* if rsize or wsize not passed in on remount, use previous values */
	ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
	ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;

	new_ctx = kzalloc_obj(*new_ctx);
	if (!new_ctx) {
		rc = -ENOMEM;
		goto restore_ctx;
	}

	rc = smb3_fs_context_dup(new_ctx, ctx);
	if (rc)
		goto restore_ctx;

	need_mchan_update = ctx->multichannel != cifs_sb->ctx->multichannel ||
			    ctx->max_channels != cifs_sb->ctx->max_channels;

	/*
	 * we may update the passwords in the ses struct below. Make sure we do
@@ -1109,52 +1155,53 @@ static int smb3_reconfigure(struct fs_context *fc)
	/*
	 * smb2_reconnect may swap password and password2 in case session setup
	 * failed. First get ctx passwords in sync with ses passwords. It should
	 * be okay to do this even if this function were to return an error at a
	 * later stage
	 * be done before committing new passwords.
	 */
	rc = smb3_sync_session_ctx_passwords(cifs_sb, ses);
	if (rc) {
		mutex_unlock(&ses->session_mutex);
		kfree_sensitive(new_password);
		kfree_sensitive(new_password2);
		return rc;
		goto cleanup_new_ctx;
	}

	/*
	 * now that allocations for passwords are done, commit them
	 * If multichannel or max_channels has changed, update the session's channels accordingly.
	 * This may add or remove channels to match the new configuration.
	 */
	if (need_mchan_update) {
		/* Prevent concurrent scaling operations */
		spin_lock(&ses->ses_lock);
		if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
			spin_unlock(&ses->ses_lock);
			mutex_unlock(&ses->session_mutex);
			rc = -EINVAL;
			goto cleanup_new_ctx;
		}
		ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
		spin_unlock(&ses->ses_lock);
	}

	/*
	 * Commit session passwords before any channel work so newly added
	 * channels authenticate with the new credentials.
	 */
	if (new_password) {
		kfree_sensitive(ses->password);
		ses->password = new_password;
		new_password = NULL;
	}
	if (new_password2) {
		kfree_sensitive(ses->password2);
		ses->password2 = new_password2;
		new_password2 = NULL;
	}

	/*
	 * If multichannel or max_channels has changed, update the session's channels accordingly.
	 * This may add or remove channels to match the new configuration.
	 */
	if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
	    (ctx->max_channels != cifs_sb->ctx->max_channels)) {

	if (need_mchan_update) {
		/* Synchronize ses->chan_max with the new mount context */
		smb3_sync_ses_chan_max(ses, ctx->max_channels);
		/* Now update the session's channels to match the new configuration */
		/* Prevent concurrent scaling operations */
		spin_lock(&ses->ses_lock);
		if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
			spin_unlock(&ses->ses_lock);
			mutex_unlock(&ses->session_mutex);
			return -EINVAL;
		}
		ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
		spin_unlock(&ses->ses_lock);

		mutex_unlock(&ses->session_mutex);

		rc = smb3_update_ses_channels(ses, ses->server,
		smb3_update_ses_channels(ses, ses->server,
					 false /* from_reconnect */,
					 false /* disable_mchan */);

@@ -1166,22 +1213,30 @@ static int smb3_reconfigure(struct fs_context *fc)
		mutex_unlock(&ses->session_mutex);
	}

	STEAL_STRING(cifs_sb, ctx, domainname);
	STEAL_STRING(cifs_sb, ctx, nodename);
	STEAL_STRING(cifs_sb, ctx, iocharset);

	/* if rsize or wsize not passed in on remount, use previous values */
	ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
	ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;

	smb3_cleanup_fs_context_contents(cifs_sb->ctx);
	rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
	memcpy(cifs_sb->ctx, new_ctx, sizeof(*new_ctx));
	kfree(new_ctx);
	new_ctx = NULL;
	smb3_cleanup_fs_context(old_ctx);
	old_ctx = NULL;
	smb3_update_mnt_flags(cifs_sb);
#ifdef CONFIG_CIFS_DFS_UPCALL
	if (!rc)
		rc = dfs_cache_remount_fs(cifs_sb);
#endif

	return rc;

cleanup_new_ctx:
	smb3_cleanup_fs_context_contents(new_ctx);
restore_ctx:
	kfree(new_ctx);
	kfree_sensitive(new_password);
	kfree_sensitive(new_password2);
	smb3_cleanup_fs_context_contents(cifs_sb->ctx);
	memcpy(cifs_sb->ctx, old_ctx, sizeof(*old_ctx));
	kfree(old_ctx);

	return rc;
}

+3 −0
Original line number Diff line number Diff line
@@ -49,6 +49,9 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
				 __func__, le32_to_cpu(p->ErrorId));

			len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8);
			if (len > end - ((u8 *)p + sizeof(*p)))
				return ERR_PTR(-EINVAL);

			p = (struct smb2_error_context_rsp *)(p->ErrorContextData + len);
		}
	} else if (le32_to_cpu(err->ByteCount) >= sizeof(*sym) &&
+3 −1
Original line number Diff line number Diff line
@@ -4721,6 +4721,7 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
{
	unsigned int data_offset;
	unsigned int data_len;
	unsigned int end_off;
	unsigned int cur_off;
	unsigned int cur_page_idx;
	unsigned int pad_len;
@@ -4836,7 +4837,8 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
		}
		rdata->got_bytes = buffer_len;

	} else if (buf_len >= data_offset + data_len) {
	} else if (!check_add_overflow(data_offset, data_len, &end_off) &&
		   buf_len >= end_off) {
		/* read response payload is in buf */
		WARN_ONCE(buffer, "read data can be either in buf or in buffer");
		copied = copy_to_iter(buf + data_offset, data_len, &rdata->subreq.io_iter);
+9 −6
Original line number Diff line number Diff line
@@ -1158,7 +1158,7 @@ int
cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
{
	int length, len;
	unsigned int data_offset, data_len;
	unsigned int data_offset, data_len, end_off;
	struct cifs_io_subrequest *rdata = mid->callback_data;
	char *buf = server->smallbuf;
	unsigned int buflen = server->pdu_size;
@@ -1256,12 +1256,15 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
	use_rdma_mr = rdata->mr;
#endif
	data_len = server->ops->read_data_length(buf, use_rdma_mr);
	if (!use_rdma_mr && (data_offset + data_len > buflen)) {
	if (!use_rdma_mr) {
		if (check_add_overflow(data_offset, data_len, &end_off) ||
		    end_off > buflen) {
			/* data_len is corrupt -- discard frame */
			rdata->result = smb_EIO2(smb_eio_trace_read_rsp_malformed,
					 data_offset + data_len, buflen);
						 end_off, buflen);
			return cifs_readv_discard(server, mid);
		}
	}

#ifdef CONFIG_CIFS_SMB_DIRECT
	if (rdata->mr)
+2 −2
Original line number Diff line number Diff line
@@ -260,12 +260,12 @@ typedef struct {
	char FileName[];
} __packed FILE_DIRECTORY_INFO;   /* level 0x101 FF resp data */

/* See MS-FSCC 2.4.13 */
/* See MS-FSCC 2.4.14 */
struct smb2_file_eof_info { /* encoding of request for level 10 */
	__le64 EndOfFile; /* new end of file value */
} __packed; /* level 20 Set */

/* See MS-FSCC 2.4.14 */
/* See MS-FSCC 2.4.15 */
typedef struct {
	__le32 NextEntryOffset;
	__u32 FileIndex;
Loading