Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e8e2861c authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Greg Kroah-Hartman
Browse files

cifs: fix oops during encryption



[ Upstream commit f7f291e14dde32a07b1f0aa06921d28f875a7b54 ]

When running xfstests against Azure the following oops occurred on an
arm64 system

  Unable to handle kernel write to read-only memory at virtual address
  ffff0001221cf000
  Mem abort info:
    ESR = 0x9600004f
    EC = 0x25: DABT (current EL), IL = 32 bits
    SET = 0, FnV = 0
    EA = 0, S1PTW = 0
    FSC = 0x0f: level 3 permission fault
  Data abort info:
    ISV = 0, ISS = 0x0000004f
    CM = 0, WnR = 1
  swapper pgtable: 4k pages, 48-bit VAs, pgdp=00000000294f3000
  [ffff0001221cf000] pgd=18000001ffff8003, p4d=18000001ffff8003,
  pud=18000001ff82e003, pmd=18000001ff71d003, pte=00600001221cf787
  Internal error: Oops: 9600004f [#1] PREEMPT SMP
  ...
  pstate: 80000005 (Nzcv daif -PAN -UAO -TCO BTYPE=--)
  pc : __memcpy+0x40/0x230
  lr : scatterwalk_copychunks+0xe0/0x200
  sp : ffff800014e92de0
  x29: ffff800014e92de0 x28: ffff000114f9de80 x27: 0000000000000008
  x26: 0000000000000008 x25: ffff800014e92e78 x24: 0000000000000008
  x23: 0000000000000001 x22: 0000040000000000 x21: ffff000000000000
  x20: 0000000000000001 x19: ffff0001037c4488 x18: 0000000000000014
  x17: 235e1c0d6efa9661 x16: a435f9576b6edd6c x15: 0000000000000058
  x14: 0000000000000001 x13: 0000000000000008 x12: ffff000114f2e590
  x11: ffffffffffffffff x10: 0000040000000000 x9 : ffff8000105c3580
  x8 : 2e9413b10000001a x7 : 534b4410fb86b005 x6 : 534b4410fb86b005
  x5 : ffff0001221cf008 x4 : ffff0001037c4490 x3 : 0000000000000001
  x2 : 0000000000000008 x1 : ffff0001037c4488 x0 : ffff0001221cf000
  Call trace:
   __memcpy+0x40/0x230
   scatterwalk_map_and_copy+0x98/0x100
   crypto_ccm_encrypt+0x150/0x180
   crypto_aead_encrypt+0x2c/0x40
   crypt_message+0x750/0x880
   smb3_init_transform_rq+0x298/0x340
   smb_send_rqst.part.11+0xd8/0x180
   smb_send_rqst+0x3c/0x100
   compound_send_recv+0x534/0xbc0
   smb2_query_info_compound+0x32c/0x440
   smb2_set_ea+0x438/0x4c0
   cifs_xattr_set+0x5d4/0x7c0

This is because in scatterwalk_copychunks(), we attempted to write to
a buffer (@sign) that was allocated in the stack (vmalloc area) by
crypt_message() and thus accessing its remaining 8 (x2) bytes ended up
crossing a page boundary.

To simply fix it, we could just pass @sign kmalloc'd from
crypt_message() and then we're done.  Luckily, we don't seem to pass
any other vmalloc'd buffers in smb_rqst::rq_iov...

Instead, let's map the correct pages and offsets from vmalloc buffers
as well in cifs_sg_set_buf() and then avoiding such oopses.

Signed-off-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Cc: stable@vger.kernel.org
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 3c3baa40
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <linux/mm.h>
#include <linux/mempool.h>
#include <linux/workqueue.h>
#include "cifs_fs_sb.h"
@@ -30,6 +32,7 @@
#include <linux/scatterlist.h>
#include <uapi/linux/cifs/cifs_mount.h>
#include "smb2pdu.h"
#include "smb2glob.h"

#define CIFS_MAGIC_NUMBER 0xFF534D42      /* the first four bytes of SMB PDUs */

@@ -1955,4 +1958,71 @@ extern struct smb_version_values smb302_values;
#define ALT_SMB311_VERSION_STRING "3.11"
extern struct smb_version_operations smb311_operations;
extern struct smb_version_values smb311_values;

static inline unsigned int cifs_get_num_sgs(const struct smb_rqst *rqst,
					    int num_rqst,
					    const u8 *sig)
{
	unsigned int len, skip;
	unsigned int nents = 0;
	unsigned long addr;
	int i, j;

	/* Assumes the first rqst has a transform header as the first iov.
	 * I.e.
	 * rqst[0].rq_iov[0]  is transform header
	 * rqst[0].rq_iov[1+] data to be encrypted/decrypted
	 * rqst[1+].rq_iov[0+] data to be encrypted/decrypted
	 */
	for (i = 0; i < num_rqst; i++) {
		/*
		 * The first rqst has a transform header where the
		 * first 20 bytes are not part of the encrypted blob.
		 */
		for (j = 0; j < rqst[i].rq_nvec; j++) {
			struct kvec *iov = &rqst[i].rq_iov[j];

			skip = (i == 0) && (j == 0) ? 20 : 0;
			addr = (unsigned long)iov->iov_base + skip;
			if (unlikely(is_vmalloc_addr((void *)addr))) {
				len = iov->iov_len - skip;
				nents += DIV_ROUND_UP(offset_in_page(addr) + len,
						      PAGE_SIZE);
			} else {
				nents++;
			}
		}
		nents += rqst[i].rq_npages;
	}
	nents += DIV_ROUND_UP(offset_in_page(sig) + SMB2_SIGNATURE_SIZE, PAGE_SIZE);
	return nents;
}

/* We can not use the normal sg_set_buf() as we will sometimes pass a
 * stack object as buf.
 */
static inline struct scatterlist *cifs_sg_set_buf(struct scatterlist *sg,
						  const void *buf,
						  unsigned int buflen)
{
	unsigned long addr = (unsigned long)buf;
	unsigned int off = offset_in_page(addr);

	addr &= PAGE_MASK;
	if (unlikely(is_vmalloc_addr((void *)addr))) {
		do {
			unsigned int len = min_t(unsigned int, buflen, PAGE_SIZE - off);

			sg_set_page(sg++, vmalloc_to_page((void *)addr), len, off);

			off = 0;
			addr += PAGE_SIZE;
			buflen -= len;
		} while (buflen);
	} else {
		sg_set_page(sg++, virt_to_page(addr), buflen, off);
	}
	return sg;
}

#endif	/* _CIFS_GLOB_H */
+2 −2
Original line number Diff line number Diff line
@@ -583,7 +583,7 @@ int cifs_alloc_hash(const char *name, struct crypto_shash **shash,
		    struct sdesc **sdesc);
void cifs_free_hash(struct crypto_shash **shash, struct sdesc **sdesc);

extern void rqst_page_get_length(struct smb_rqst *rqst, unsigned int page,
void rqst_page_get_length(const struct smb_rqst *rqst, unsigned int page,
			  unsigned int *len, unsigned int *offset);

void extract_unc_hostname(const char *unc, const char **h, size_t *len);
+2 −2
Original line number Diff line number Diff line
@@ -972,7 +972,7 @@ cifs_free_hash(struct crypto_shash **shash, struct sdesc **sdesc)
 * Input: rqst - a smb_rqst, page - a page index for rqst
 * Output: *len - the length for this page, *offset - the offset for this page
 */
void rqst_page_get_length(struct smb_rqst *rqst, unsigned int page,
void rqst_page_get_length(const struct smb_rqst *rqst, unsigned int page,
			  unsigned int *len, unsigned int *offset)
{
	*len = rqst->rq_pagesz;
+68 −75
Original line number Diff line number Diff line
@@ -3625,69 +3625,82 @@ fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, unsigned int orig_len,
	memcpy(&tr_hdr->SessionId, &shdr->SessionId, 8);
}

/* We can not use the normal sg_set_buf() as we will sometimes pass a
 * stack object as buf.
 */
static inline void smb2_sg_set_buf(struct scatterlist *sg, const void *buf,
				   unsigned int buflen)
static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst *rqst,
				 int num_rqst, const u8 *sig, u8 **iv,
				 struct aead_request **req, struct scatterlist **sgl,
				 unsigned int *num_sgs)
{
	void *addr;
	/*
	 * VMAP_STACK (at least) puts stack into the vmalloc address space
	 */
	if (is_vmalloc_addr(buf))
		addr = vmalloc_to_page(buf);
	else
		addr = virt_to_page(buf);
	sg_set_page(sg, addr, buflen, offset_in_page(buf));
	unsigned int req_size = sizeof(**req) + crypto_aead_reqsize(tfm);
	unsigned int iv_size = crypto_aead_ivsize(tfm);
	unsigned int len;
	u8 *p;

	*num_sgs = cifs_get_num_sgs(rqst, num_rqst, sig);

	len = iv_size;
	len += crypto_aead_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1);
	len = ALIGN(len, crypto_tfm_ctx_alignment());
	len += req_size;
	len = ALIGN(len, __alignof__(struct scatterlist));
	len += *num_sgs * sizeof(**sgl);

	p = kmalloc(len, GFP_ATOMIC);
	if (!p)
		return NULL;

	*iv = (u8 *)PTR_ALIGN(p, crypto_aead_alignmask(tfm) + 1);
	*req = (struct aead_request *)PTR_ALIGN(*iv + iv_size,
						crypto_tfm_ctx_alignment());
	*sgl = (struct scatterlist *)PTR_ALIGN((u8 *)*req + req_size,
					       __alignof__(struct scatterlist));
	return p;
}

static void *smb2_get_aead_req(struct crypto_aead *tfm, const struct smb_rqst *rqst,
			       int num_rqst, const u8 *sig, u8 **iv,
			       struct aead_request **req, struct scatterlist **sgl)
{
	unsigned int off, len, skip;
	struct scatterlist *sg;
	unsigned int num_sgs;
	unsigned long addr;
	int i, j;
	void *p;

	p = smb2_aead_req_alloc(tfm, rqst, num_rqst, sig, iv, req, sgl, &num_sgs);
	if (!p)
		return NULL;

	sg_init_table(*sgl, num_sgs);
	sg = *sgl;

	/* Assumes the first rqst has a transform header as the first iov.
	 * I.e.
	 * rqst[0].rq_iov[0]  is transform header
	 * rqst[0].rq_iov[1+] data to be encrypted/decrypted
	 * rqst[1+].rq_iov[0+] data to be encrypted/decrypted
	 */
static struct scatterlist *
init_sg(int num_rqst, struct smb_rqst *rqst, u8 *sign)
{
	unsigned int sg_len;
	struct scatterlist *sg;
	unsigned int i;
	unsigned int j;
	unsigned int idx = 0;
	int skip;

	sg_len = 1;
	for (i = 0; i < num_rqst; i++)
		sg_len += rqst[i].rq_nvec + rqst[i].rq_npages;

	sg = kmalloc_array(sg_len, sizeof(struct scatterlist), GFP_KERNEL);
	if (!sg)
		return NULL;

	sg_init_table(sg, sg_len);
	for (i = 0; i < num_rqst; i++) {
		for (j = 0; j < rqst[i].rq_nvec; j++) {
		/*
		 * The first rqst has a transform header where the
			 * first 20 bytes are not part of the encrypted blob
		 * first 20 bytes are not part of the encrypted blob.
		 */
		for (j = 0; j < rqst[i].rq_nvec; j++) {
			struct kvec *iov = &rqst[i].rq_iov[j];

			skip = (i == 0) && (j == 0) ? 20 : 0;
			smb2_sg_set_buf(&sg[idx++],
					rqst[i].rq_iov[j].iov_base + skip,
					rqst[i].rq_iov[j].iov_len - skip);
			addr = (unsigned long)iov->iov_base + skip;
			len = iov->iov_len - skip;
			sg = cifs_sg_set_buf(sg, (void *)addr, len);
		}

		for (j = 0; j < rqst[i].rq_npages; j++) {
			unsigned int len, offset;

			rqst_page_get_length(&rqst[i], j, &len, &offset);
			sg_set_page(&sg[idx++], rqst[i].rq_pages[j], len, offset);
			rqst_page_get_length(&rqst[i], j, &len, &off);
			sg_set_page(sg++, rqst[i].rq_pages[j], len, off);
		}
	}
	smb2_sg_set_buf(&sg[idx], sign, SMB2_SIGNATURE_SIZE);
	return sg;
	cifs_sg_set_buf(sg, sig, SMB2_SIGNATURE_SIZE);

	return p;
}

static int
@@ -3729,11 +3742,11 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
	u8 sign[SMB2_SIGNATURE_SIZE] = {};
	u8 key[SMB3_SIGN_KEY_SIZE];
	struct aead_request *req;
	char *iv;
	unsigned int iv_len;
	u8 *iv;
	DECLARE_CRYPTO_WAIT(wait);
	struct crypto_aead *tfm;
	unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
	void *creq;

	rc = smb2_get_enc_key(server, tr_hdr->SessionId, enc, key);
	if (rc) {
@@ -3762,32 +3775,15 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
		return rc;
	}

	req = aead_request_alloc(tfm, GFP_KERNEL);
	if (!req) {
		cifs_server_dbg(VFS, "%s: Failed to alloc aead request\n", __func__);
	creq = smb2_get_aead_req(tfm, rqst, num_rqst, sign, &iv, &req, &sg);
	if (unlikely(!creq))
		return -ENOMEM;
	}

	if (!enc) {
		memcpy(sign, &tr_hdr->Signature, SMB2_SIGNATURE_SIZE);
		crypt_len += SMB2_SIGNATURE_SIZE;
	}

	sg = init_sg(num_rqst, rqst, sign);
	if (!sg) {
		cifs_server_dbg(VFS, "%s: Failed to init sg\n", __func__);
		rc = -ENOMEM;
		goto free_req;
	}

	iv_len = crypto_aead_ivsize(tfm);
	iv = kzalloc(iv_len, GFP_KERNEL);
	if (!iv) {
		cifs_server_dbg(VFS, "%s: Failed to alloc iv\n", __func__);
		rc = -ENOMEM;
		goto free_sg;
	}

	if (server->cipher_type == SMB2_ENCRYPTION_AES128_GCM)
		memcpy(iv, (char *)tr_hdr->Nonce, SMB3_AES128GCM_NONCE);
	else {
@@ -3795,6 +3791,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
		memcpy(iv + 1, (char *)tr_hdr->Nonce, SMB3_AES128CCM_NONCE);
	}

	aead_request_set_tfm(req, tfm);
	aead_request_set_crypt(req, sg, sg, crypt_len, iv);
	aead_request_set_ad(req, assoc_data_len);

@@ -3807,11 +3804,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
	if (!rc && enc)
		memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE);

	kfree(iv);
free_sg:
	kfree(sg);
free_req:
	kfree(req);
	kfree(creq);
	return rc;
}