Commit message: cifs: Fix use-after-free in SMB2_read

There is a KASAN use-after-free:
BUG: KASAN: use-after-free in SMB2_read+0x1136/0x1190
Read of size 8 at addr ffff8880b4e45e50 by task ln/1009

Should not release the 'req' because it will use in the trace.

Fixes: eccb4422cf97 ("smb3: Add ftrace tracepoints for improved SMB3 debugging")

Signed-off-by: ZhangXiaoxu <zhangxiaoxu5@huawei.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
CC: Stable <stable@vger.kernel.org> 4.18+
Reviewed-by: Pavel Shilovsky <pshilov@microsoft.com>
CVE: CVE-2019-15920
CWE: CWE-416
Vulnerability description: An issue was discovered in the Linux kernel before 5.0.10. SMB2_read in fs/cifs/smb2pdu.c has a use-after-free. NOTE: this was not fixed correctly in 5.0.10; see the 5.0.11 ChangeLog, which documents a memory leak.

Project: linux
File: smb2pdu.c

SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
	  __u8 *oplock, struct smb2_file_all_info *buf,
	  struct kvec *err_iov, int *buftype)
{
	struct smb_rqst rqst;
	struct smb2_create_rsp *rsp = NULL;
	struct TCP_Server_Info *server;
	struct cifs_tcon *tcon = oparms->tcon;
	struct cifs_ses *ses = tcon->ses;
	struct kvec iov[SMB2_CREATE_IOV_SIZE];
	struct kvec rsp_iov = {NULL, 0};
	int resp_buftype = CIFS_NO_BUFFER;
	int rc = 0;
	int flags = 0;

	cifs_dbg(FYI, "create/open\n");
	if (ses && (ses->server))
		server = ses->server;
	else
		return -EIO;

	if (smb3_encryption_required(tcon))
		flags |= CIFS_TRANSFORM_REQ;

	memset(&rqst, 0, sizeof(struct smb_rqst));
	memset(&iov, 0, sizeof(iov));
	rqst.rq_iov = iov;
	rqst.rq_nvec = SMB2_CREATE_IOV_SIZE;

	rc = SMB2_open_init(tcon, &rqst, oplock, oparms, path);
	if (rc)
		goto creat_exit;

	trace_smb3_open_enter(xid, tcon->tid, tcon->ses->Suid,
		oparms->create_options, oparms->desired_access);

	rc = cifs_send_recv(xid, ses, &rqst, &resp_buftype, flags,
			    &rsp_iov);
	rsp = (struct smb2_create_rsp *)rsp_iov.iov_base;

	if (rc != 0) {
		cifs_stats_fail_inc(tcon, SMB2_CREATE_HE);
		if (err_iov && rsp) {
			*err_iov = rsp_iov;
			*buftype = resp_buftype;
			resp_buftype = CIFS_NO_BUFFER;
			rsp = NULL;
		}
		trace_smb3_open_err(xid, tcon->tid, ses->Suid,
				    oparms->create_options, oparms->desired_access, rc);
		goto creat_exit;
	} else
		trace_smb3_open_done(xid, rsp->PersistentFileId, tcon->tid,
				     ses->Suid, oparms->create_options,
				     oparms->desired_access);

	atomic_inc(&tcon->num_remote_opens);
	oparms->fid->persistent_fid = rsp->PersistentFileId;
	oparms->fid->volatile_fid = rsp->VolatileFileId;
#ifdef CONFIG_CIFS_DEBUG2
	oparms->fid->mid = le64_to_cpu(rsp->sync_hdr.MessageId);
#endif /* CIFS_DEBUG2 */

	if (buf) {
		memcpy(buf, &rsp->CreationTime, 32);
		buf->AllocationSize = rsp->AllocationSize;
		buf->EndOfFile = rsp->EndofFile;
		buf->Attributes = rsp->FileAttributes;
		buf->NumberOfLinks = cpu_to_le32(1);
		buf->DeletePending = 0;
	}

	if (rsp->OplockLevel == SMB2_OPLOCK_LEVEL_LEASE)
		*oplock = smb2_parse_lease_state(server, rsp,
						 &oparms->fid->epoch,
						 oparms->fid->lease_key);
	else
		*oplock = rsp->OplockLevel;
creat_exit:
	SMB2_open_free(&rqst);
	free_rsp_buf(resp_buftype, rsp);
	return rc;
}

Project: linux
File: smb2pdu.c

SMB2_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
		u64 persistent_fid, u64 volatile_fid,
		struct cifs_ntsd *pnntsd, int pacllen, int aclflag)
{
	return send_set_info(xid, tcon, persistent_fid, volatile_fid,
			current->tgid, 0, SMB2_O_INFO_SECURITY, aclflag,
			1, (void **)&pnntsd, &pacllen);
}

Project: linux
File: smb2pdu.c

SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
	     u64 volatile_fid, u32 pid, __le64 *eof)
{
	struct smb2_file_eof_info info;
	void *data;
	unsigned int size;

	info.EndOfFile = *eof;

	data = &info;
	size = sizeof(struct smb2_file_eof_info);

	return send_set_info(xid, tcon, persistent_fid, volatile_fid,
			pid, FILE_END_OF_FILE_INFORMATION, SMB2_O_INFO_FILE,
			0, 1, &data, &size);
}

Project: linux
File: smb2pdu.c

static inline int __smb2_reconnect(const struct nls_table *nlsc,
				   struct cifs_tcon *tcon)
{
	return SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nlsc);
}

Project: linux
File: smb2pdu.c

add_durable_context(struct kvec *iov, unsigned int *num_iovec,
		    struct cifs_open_parms *oparms, bool use_persistent)
{
	struct smb2_create_req *req = iov[0].iov_base;
	unsigned int num = *num_iovec;

	if (use_persistent) {
		if (oparms->reconnect)
			return add_durable_reconnect_v2_context(iov, num_iovec,
								oparms);
		else
			return add_durable_v2_context(iov, num_iovec, oparms);
	}

	if (oparms->reconnect) {
		iov[num].iov_base = create_reconnect_durable_buf(oparms->fid);
		/* indicate that we don't need to relock the file */
		oparms->reconnect = false;
	} else
		iov[num].iov_base = create_durable_buf();
	if (iov[num].iov_base == NULL)
		return -ENOMEM;
	iov[num].iov_len = sizeof(struct create_durable);
	if (!req->CreateContextsOffset)
		req->CreateContextsOffset =
			cpu_to_le32(sizeof(struct smb2_create_req) +
								iov[1].iov_len);
	le32_add_cpu(&req->CreateContextsLength, sizeof(struct create_durable));
	*num_iovec = num + 1;
	return 0;
}

Project: linux
File: smb2pdu.c

add_lease_context(struct TCP_Server_Info *server, struct kvec *iov,
		  unsigned int *num_iovec, u8 *lease_key, __u8 *oplock)
{
	struct smb2_create_req *req = iov[0].iov_base;
	unsigned int num = *num_iovec;

	iov[num].iov_base = server->ops->create_lease_buf(lease_key, *oplock);
	if (iov[num].iov_base == NULL)
		return -ENOMEM;
	iov[num].iov_len = server->vals->create_lease_size;
	req->RequestedOplockLevel = SMB2_OPLOCK_LEVEL_LEASE;
	if (!req->CreateContextsOffset)
		req->CreateContextsOffset = cpu_to_le32(
				sizeof(struct smb2_create_req) +
				iov[num - 1].iov_len);
	le32_add_cpu(&req->CreateContextsLength,
		     server->vals->create_lease_size);
	*num_iovec = num + 1;
	return 0;
}

Project: linux
File: smb2pdu.c

add_twarp_context(struct kvec *iov, unsigned int *num_iovec, __u64 timewarp)
{
	struct smb2_create_req *req = iov[0].iov_base;
	unsigned int num = *num_iovec;

	iov[num].iov_base = create_twarp_buf(timewarp);
	if (iov[num].iov_base == NULL)
		return -ENOMEM;
	iov[num].iov_len = sizeof(struct crt_twarp_ctxt);
	if (!req->CreateContextsOffset)
		req->CreateContextsOffset = cpu_to_le32(
				sizeof(struct smb2_create_req) +
				iov[num - 1].iov_len);
	le32_add_cpu(&req->CreateContextsLength, sizeof(struct crt_twarp_ctxt));
	*num_iovec = num + 1;
	return 0;
}

Project: linux
File: smb2pdu.c

num_entries(char *bufstart, char *end_of_buf, char **lastentry, size_t size)
{
	int len;
	unsigned int entrycount = 0;
	unsigned int next_offset = 0;
	char *entryptr;
	FILE_DIRECTORY_INFO *dir_info;

	if (bufstart == NULL)
		return 0;

	entryptr = bufstart;

	while (1) {
		if (entryptr + next_offset < entryptr ||
		    entryptr + next_offset > end_of_buf ||
		    entryptr + next_offset + size > end_of_buf) {
			cifs_dbg(VFS, "malformed search entry would overflow\n");
			break;
		}

		entryptr = entryptr + next_offset;
		dir_info = (FILE_DIRECTORY_INFO *)entryptr;

		len = le32_to_cpu(dir_info->FileNameLength);
		if (entryptr + len < entryptr ||
		    entryptr + len > end_of_buf ||
		    entryptr + len + size > end_of_buf) {
			cifs_dbg(VFS, "directory entry name would overflow frame end of buf %p\n",
				 end_of_buf);
			break;
		}

		*lastentry = entryptr;
		entrycount++;

		next_offset = le32_to_cpu(dir_info->NextEntryOffset);
		if (!next_offset)
			break;
	}

	return entrycount;
}

Project: linux
File: smb2pdu.c

smb2_async_readv(struct cifs_readdata *rdata)
{
	int rc, flags = 0;
	char *buf;
	struct smb2_sync_hdr *shdr;
	struct cifs_io_parms io_parms;
	struct smb_rqst rqst = { .rq_iov = rdata->iov,
				 .rq_nvec = 1 };
	struct TCP_Server_Info *server;
	unsigned int total_len;

	cifs_dbg(FYI, "%s: offset=%llu bytes=%u\n",
		 __func__, rdata->offset, rdata->bytes);

	io_parms.tcon = tlink_tcon(rdata->cfile->tlink);
	io_parms.offset = rdata->offset;
	io_parms.length = rdata->bytes;
	io_parms.persistent_fid = rdata->cfile->fid.persistent_fid;
	io_parms.volatile_fid = rdata->cfile->fid.volatile_fid;
	io_parms.pid = rdata->pid;

	server = io_parms.tcon->ses->server;

	rc = smb2_new_read_req(
		(void **) &buf, &total_len, &io_parms, rdata, 0, 0);
	if (rc)
		return rc;

	if (smb3_encryption_required(io_parms.tcon))
		flags |= CIFS_TRANSFORM_REQ;

	rdata->iov[0].iov_base = buf;
	rdata->iov[0].iov_len = total_len;

	shdr = (struct smb2_sync_hdr *)buf;

	if (rdata->credits.value > 0) {
		shdr->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->bytes,
						SMB2_MAX_BUFFER_SIZE));
		shdr->CreditRequest =
			cpu_to_le16(le16_to_cpu(shdr->CreditCharge) + 1);

		rc = adjust_credits(server, &rdata->credits, rdata->bytes);
		if (rc)
			goto async_readv_out;

		flags |= CIFS_HAS_CREDITS;
	}

	kref_get(&rdata->refcount);
	rc = cifs_call_async(io_parms.tcon->ses->server, &rqst,
			     cifs_readv_receive, smb2_readv_callback,
			     smb3_handle_read_data, rdata, flags,
			     &rdata->credits);
	if (rc) {
		kref_put(&rdata->refcount, cifs_readdata_release);
		cifs_stats_fail_inc(io_parms.tcon, SMB2_READ_HE);
		trace_smb3_read_err(0 /* xid */, io_parms.persistent_fid,
				    io_parms.tcon->tid,
				    io_parms.tcon->ses->Suid,
				    io_parms.offset, io_parms.length, rc);
	}

async_readv_out:
	cifs_small_buf_release(buf);
	return rc;
}

Project: linux
File: smb2pdu.c

smb2_copy_fs_info_to_kstatfs(struct smb2_fs_full_size_info *pfs_inf,
			     struct kstatfs *kst)
{
	kst->f_bsize = le32_to_cpu(pfs_inf->BytesPerSector) *
			  le32_to_cpu(pfs_inf->SectorsPerAllocationUnit);
	kst->f_blocks = le64_to_cpu(pfs_inf->TotalAllocationUnits);
	kst->f_bfree  = kst->f_bavail =
			le64_to_cpu(pfs_inf->CallerAvailableAllocationUnits);
	return;
}

Project: linux
File: smb2pdu.c

int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
{
	int rc;
	struct validate_negotiate_info_req *pneg_inbuf;
	struct validate_negotiate_info_rsp *pneg_rsp = NULL;
	u32 rsplen;
	u32 inbuflen; /* max of 4 dialects */

	cifs_dbg(FYI, "validate negotiate\n");

	/* In SMB3.11 preauth integrity supersedes validate negotiate */
	if (tcon->ses->server->dialect == SMB311_PROT_ID)
		return 0;

	/*
	 * validation ioctl must be signed, so no point sending this if we
	 * can not sign it (ie are not known user).  Even if signing is not
	 * required (enabled but not negotiated), in those cases we selectively
	 * sign just this, the first and only signed request on a connection.
	 * Having validation of negotiate info  helps reduce attack vectors.
	 */
	if (tcon->ses->session_flags & SMB2_SESSION_FLAG_IS_GUEST)
		return 0; /* validation requires signing */

	if (tcon->ses->user_name == NULL) {
		cifs_dbg(FYI, "Can't validate negotiate: null user mount\n");
		return 0; /* validation requires signing */
	}

	if (tcon->ses->session_flags & SMB2_SESSION_FLAG_IS_NULL)
		cifs_dbg(VFS, "Unexpected null user (anonymous) auth flag sent by server\n");

	pneg_inbuf = kmalloc(sizeof(*pneg_inbuf), GFP_NOFS);
	if (!pneg_inbuf)
		return -ENOMEM;

	pneg_inbuf->Capabilities =
			cpu_to_le32(tcon->ses->server->vals->req_capabilities);
	memcpy(pneg_inbuf->Guid, tcon->ses->server->client_guid,
					SMB2_CLIENT_GUID_SIZE);

	if (tcon->ses->sign)
		pneg_inbuf->SecurityMode =
			cpu_to_le16(SMB2_NEGOTIATE_SIGNING_REQUIRED);
	else if (global_secflags & CIFSSEC_MAY_SIGN)
		pneg_inbuf->SecurityMode =
			cpu_to_le16(SMB2_NEGOTIATE_SIGNING_ENABLED);
	else
		pneg_inbuf->SecurityMode = 0;


	if (strcmp(tcon->ses->server->vals->version_string,
		SMB3ANY_VERSION_STRING) == 0) {
		pneg_inbuf->Dialects[0] = cpu_to_le16(SMB30_PROT_ID);
		pneg_inbuf->Dialects[1] = cpu_to_le16(SMB302_PROT_ID);
		pneg_inbuf->DialectCount = cpu_to_le16(2);
		/* structure is big enough for 3 dialects, sending only 2 */
		inbuflen = sizeof(*pneg_inbuf) -
				(2 * sizeof(pneg_inbuf->Dialects[0]));
	} else if (strcmp(tcon->ses->server->vals->version_string,
		SMBDEFAULT_VERSION_STRING) == 0) {
		pneg_inbuf->Dialects[0] = cpu_to_le16(SMB21_PROT_ID);
		pneg_inbuf->Dialects[1] = cpu_to_le16(SMB30_PROT_ID);
		pneg_inbuf->Dialects[2] = cpu_to_le16(SMB302_PROT_ID);
		pneg_inbuf->Dialects[3] = cpu_to_le16(SMB311_PROT_ID);
		pneg_inbuf->DialectCount = cpu_to_le16(4);
		/* structure is big enough for 3 dialects */
		inbuflen = sizeof(*pneg_inbuf);
	} else {
		/* otherwise specific dialect was requested */
		pneg_inbuf->Dialects[0] =
			cpu_to_le16(tcon->ses->server->vals->protocol_id);
		pneg_inbuf->DialectCount = cpu_to_le16(1);
		/* structure is big enough for 3 dialects, sending only 1 */
		inbuflen = sizeof(*pneg_inbuf) -
				sizeof(pneg_inbuf->Dialects[0]) * 2;
	}

	rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID,
		FSCTL_VALIDATE_NEGOTIATE_INFO, true /* is_fsctl */,
		(char *)pneg_inbuf, inbuflen, CIFSMaxBufSize,
		(char **)&pneg_rsp, &rsplen);
	if (rc == -EOPNOTSUPP) {
		/*
		 * Old Windows versions or Netapp SMB server can return
		 * not supported error. Client should accept it.
		 */
		cifs_dbg(VFS, "Server does not support validate negotiate\n");
		return 0;
	} else if (rc != 0) {
		cifs_dbg(VFS, "validate protocol negotiate failed: %d\n", rc);
		rc = -EIO;
		goto out_free_inbuf;
	}

	rc = -EIO;
	if (rsplen != sizeof(*pneg_rsp)) {
		cifs_dbg(VFS, "invalid protocol negotiate response size: %d\n",
			 rsplen);

		/* relax check since Mac returns max bufsize allowed on ioctl */
		if (rsplen > CIFSMaxBufSize || rsplen < sizeof(*pneg_rsp))
			goto out_free_rsp;
	}

	/* check validate negotiate info response matches what we got earlier */
	if (pneg_rsp->Dialect != cpu_to_le16(tcon->ses->server->dialect))
		goto vneg_out;

	if (pneg_rsp->SecurityMode != cpu_to_le16(tcon->ses->server->sec_mode))
		goto vneg_out;

	/* do not validate server guid because not saved at negprot time yet */

	if ((le32_to_cpu(pneg_rsp->Capabilities) | SMB2_NT_FIND |
	      SMB2_LARGE_FILES) != tcon->ses->server->capabilities)
		goto vneg_out;

	/* validate negotiate successful */
	rc = 0;
	cifs_dbg(FYI, "validate negotiate info successful\n");
	goto out_free_rsp;

vneg_out:
	cifs_dbg(VFS, "protocol revalidation - security settings mismatch\n");
out_free_rsp:
	kfree(pneg_rsp);
out_free_inbuf:
	kfree(pneg_inbuf);
	return rc;
}