应用侧漏洞与CTF题解:https://wnagzihxa1n.gitbook.io

大土豆安全笔记

发现一个好项目,可能之前我看过但是没仔细学习,最近可以算是真正开始认真分析了

冰刃实验室写的《高通加解密引擎提权漏洞解析》,写的相当好,又学习到了很多

我这里分析的是其中的CVE-2016-6738,高通加解密驱动模块内核任意地址写漏洞,补丁如下

注册的驱动结构体

static struct platform_driver qcedev_plat_driver = {
	.probe = qcedev_probe,
	.remove = qcedev_remove,
	.suspend = qcedev_suspend,
	.resume = qcedev_resume,
	.driver = {
		.name = "qce",
		.owner = THIS_MODULE,
		.of_match_table = qcedev_match,
	},
};

所以我们打开驱动的代码如下

int fd = open("/dev/qce", O_RDONLY);

根据文档描述,我们可以通过IOCTL进行调用

The following IOCTLS are available to the user space application(s)-

  Cipher IOCTLs:
  --------------
    QCEDEV_IOCTL_ENC_REQ is for encrypting data.
    QCEDEV_IOCTL_DEC_REQ is for decrypting data.

该驱动对应的file_operations

static int qcedev_open(struct inode *inode, struct file *file);
static int qcedev_release(struct inode *inode, struct file *file);

static const struct file_operations qcedev_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = qcedev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = compat_qcedev_ioctl,
#endif
	.open = qcedev_open,
	.release = qcedev_release,
};

找到函数qcedev_ioctl()对应的实现

long qcedev_ioctl(struct file *file, unsigned cmd, unsigned long arg)
{
    int err = 0;
	struct qcedev_handle *handle;
	struct qcedev_control *podev;
	struct qcedev_async_req qcedev_areq;
	struct qcedev_stat *pstat;
	
    ...
    
	switch (cmd) {
	// 通过 IOCTL 进行加解密功能的请求
	case QCEDEV_IOCTL_ENC_REQ:
	case QCEDEV_IOCTL_DEC_REQ:
	    // 用户态地址检查
		if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(struct qcedev_cipher_op_req)))
			return -EFAULT;

        // 通过地址检查后进行拷贝操作
        // 用户可控:qcedev_areq->cipher_op_req
		if (__copy_from_user(&qcedev_areq.cipher_op_req, (void __user *)arg, sizeof(struct qcedev_cipher_op_req)))
			return -EFAULT;
		qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_CIPHER;

        // 参数检查,非常多的判断
		if (qcedev_check_cipher_params(&qcedev_areq.cipher_op_req, podev))
			return -EINVAL;

        // 执行加解密逻辑
		err = qcedev_vbuf_ablk_cipher(&qcedev_areq, handle);
		if (err)
			return err;
			
		// 加解密成功,将数据写入用户态空间
		if (__copy_to_user((void __user *)arg, &qcedev_areq.cipher_op_req, sizeof(struct qcedev_cipher_op_req)))
			return -EFAULT;
		break;
	
    ...
    
	default:
		return -ENOTTY;
	}

	return err;
}
EXPORT_SYMBOL(qcedev_ioctl);

结构体qcedev_cipher_op_req的实现

struct	qcedev_cipher_op_req {
	uint8_t				use_pmem;
	union {
		struct qcedev_pmem_info	pmem;
		struct qcedev_vbuf_info	vbuf;
	};
	uint32_t			entries;
	uint32_t			data_len;
	uint8_t				in_place_op;
	uint8_t				enckey[QCEDEV_MAX_KEY_SIZE];
	uint32_t			encklen;
	uint8_t				iv[QCEDEV_MAX_IV_SIZE];
	uint32_t			ivlen;
	uint32_t			byteoffset;
	enum qcedev_cipher_alg_enum	alg;
	enum qcedev_cipher_mode_enum	mode;
	enum qcedev_oper_enum		op;
};

问题出在函数qcedev_vbuf_ablk_cipher()

static int qcedev_vbuf_ablk_cipher(struct qcedev_async_req *areq, struct qcedev_handle *handle)
{
	int err = 0;
	int di = 0;
	int i = 0;
	int j = 0;
	int k = 0;
	uint32_t byteoffset = 0;
	int num_entries = 0;
	uint32_t total = 0;
	uint32_t len;
	uint8_t *k_buf_src = NULL;
	uint8_t *k_align_src = NULL;
	uint32_t max_data_xfer;
	struct qcedev_cipher_op_req *saved_req;
	struct	qcedev_cipher_op_req *creq = &areq->cipher_op_req;

	/* Verify Source Address's */
	// 进行用户态地址检查
	// 变量 vbuf 表示待处理的数据
	// 用户可控:areq->cipher_op_req.entries
	for (i = 0; i < areq->cipher_op_req.entries; i++)
		if (!access_ok(VERIFY_READ, (void __user *)areq->cipher_op_req.vbuf.src[i].vaddr, areq->cipher_op_req.vbuf.src[i].len))
			return -EFAULT;

	/* Verify Destination Address's */
	// 进行用户态地址检查
	// 有趣的是:如果 creq->in_place_op 为1就不进入if检查
	// 更有趣的是:creq->in_place_op 用户可控
	// 也就是说:我们这里可以绕过地址检查
	if (creq->in_place_op != 1) {
	    // #define QCEDEV_MAX_BUFFERS      16
		for (i = 0, total = 0; i < QCEDEV_MAX_BUFFERS; i++) {
			if ((areq->cipher_op_req.vbuf.dst[i].vaddr != 0) && (total < creq->data_len)) {
				if (!access_ok(VERIFY_WRITE, (void __user *)creq->vbuf.dst[i].vaddr, creq->vbuf.dst[i].len)) {
					pr_err("%s:DST WR_VERIFY err %d=0x%lx\n", __func__, i, (uintptr_t)creq->vbuf.dst[i].vaddr);
					return -EFAULT;
				}
				total += creq->vbuf.dst[i].len;
			}
		}
	} else  {
		for (i = 0, total = 0; i < creq->entries; i++) {
			if (total < creq->data_len) {
				if (!access_ok(VERIFY_WRITE, (void __user *)creq->vbuf.src[i].vaddr, creq->vbuf.src[i].len)) {
					pr_err("%s:SRC WR_VERIFY err %d=0x%lx\n", __func__, i, (uintptr_t)creq->vbuf.src[i].vaddr);
					return -EFAULT;
				}
				total += creq->vbuf.src[i].len;
			}
		}
	}
	total = 0;

    // 用户可控:areq->cipher_op_req.mode
    // 用户可控:areq->cipher_op_req.byteoffset
	if (areq->cipher_op_req.mode == QCEDEV_AES_MODE_CTR)
		byteoffset = areq->cipher_op_req.byteoffset;
	
	// #define QCE_MAX_OPER_DATA		0xFF00
	// #define CACHE_LINE_SIZE 32
	// #define GFP_KERNEL	(__GFP_WAIT | __GFP_IO | __GFP_FS)
	// 分配堆空间
	k_buf_src = kmalloc(QCE_MAX_OPER_DATA + CACHE_LINE_SIZE * 2, GFP_KERNEL);
	if (k_buf_src == NULL) {
		pr_err("%s: Can't Allocate memory: k_buf_src 0x%lx\n", __func__, (uintptr_t)k_buf_src);
		return -ENOMEM;
	}
	// 内存对齐
	k_align_src = (uint8_t *)ALIGN(((uintptr_t)k_buf_src), CACHE_LINE_SIZE);
	max_data_xfer = QCE_MAX_OPER_DATA - byteoffset;

    ...

	if (areq->cipher_op_req.data_len > max_data_xfer) {
	
		...
	
	} else
		err = qcedev_vbuf_ablk_cipher_max_xfer(areq, &di, handle, k_align_src);
    
    ...
}

函数qcedev_vbuf_ablk_cipher_max_xfer()会把申请的堆内存指针传入处理

static int qcedev_vbuf_ablk_cipher_max_xfer(struct qcedev_async_req *areq,
				int *di, struct qcedev_handle *handle,
				uint8_t *k_align_src)
{
	int err = 0;
	int i = 0;
	int dst_i = *di;
	struct scatterlist sg_src;
	uint32_t byteoffset = 0;
	uint8_t *user_src = NULL;
	uint8_t *k_align_dst = k_align_src;
	struct	qcedev_cipher_op_req *creq = &areq->cipher_op_req;

    // 用户可控:areq->cipher_op_req.mode
	if (areq->cipher_op_req.mode == QCEDEV_AES_MODE_CTR)
		byteoffset = areq->cipher_op_req.byteoffset;

    // 拷贝第一份待处理数据
	user_src = (void __user *)areq->cipher_op_req.vbuf.src[0].vaddr;
	if (user_src && __copy_from_user((k_align_src + byteoffset), (void __user *)user_src, areq->cipher_op_req.vbuf.src[0].len))
		return -EFAULT;

    // 移动指针指向新的写入偏移
	k_align_src += byteoffset + areq->cipher_op_req.vbuf.src[0].len;

    // 开始循环后面的待处理数据
	for (i = 1; i < areq->cipher_op_req.entries; i++) {
		user_src = (void __user *)areq->cipher_op_req.vbuf.src[i].vaddr;
		if (user_src && __copy_from_user(k_align_src, (void __user *)user_src, areq->cipher_op_req.vbuf.src[i].len)) {
			return -EFAULT;
		}
		k_align_src += areq->cipher_op_req.vbuf.src[i].len;
	}

	/* restore src beginning */
	// 循环拷贝待处理数据完毕
	// 恢复堆空间起始地址
	k_align_src = k_align_dst;
	areq->cipher_op_req.data_len += byteoffset;

	areq->cipher_req.creq.src = (struct scatterlist *) &sg_src;
	areq->cipher_req.creq.dst = (struct scatterlist *) &sg_src;

	/* In place encryption/decryption */
	sg_set_buf(areq->cipher_req.creq.src, k_align_dst, areq->cipher_op_req.data_len);
	sg_mark_end(areq->cipher_req.creq.src);

	areq->cipher_req.creq.nbytes = areq->cipher_op_req.data_len;
	areq->cipher_req.creq.info = areq->cipher_op_req.iv;
	areq->cipher_op_req.entries = 1;

    // 执行加解密操作
    // 处理完成后 k_align_dst 指向的就是处理完毕后的数据
	err = submit_req(areq, handle);

	/* copy data to destination buffer*/
	creq->data_len -= byteoffset;

    // 重点关注对 creq->vbuf.dst 处理的逻辑
	while (creq->data_len > 0) {
		if (creq->vbuf.dst[dst_i].len <= creq->data_len) {
		    // 当还有数据的时候,就进行拷贝,拷贝的值是经过加解密后的数据
			if (err == 0 && __copy_to_user((void __user *)creq->vbuf.dst[dst_i].vaddr, (k_align_dst + byteoffset), creq->vbuf.dst[dst_i].len))
				return -EFAULT;

			k_align_dst += creq->vbuf.dst[dst_i].len + byteoffset;
			creq->data_len -= creq->vbuf.dst[dst_i].len;
			dst_i++;
		} else {
			if (err == 0 && __copy_to_user((void __user *)creq->vbuf.dst[dst_i].vaddr, (k_align_dst + byteoffset), creq->data_len))
				return -EFAULT;

			k_align_dst += creq->data_len;
			creq->vbuf.dst[dst_i].len -= creq->data_len;
			creq->vbuf.dst[dst_i].vaddr += creq->data_len;
			creq->data_len = 0;
		}
	}
	*di = dst_i;

	return err;
};

根据定义,__copy_to_user()直接就是拷贝,并没有对地址进行检查操作

#define __copy_from_user(to,from,n)	(memcpy(to, (void __force *)from, n), 0)
#define __copy_to_user(to,from,n)	(memcpy((void __force *)to, from, n), 0)

所以这里存在一处内核空间任意地址写的漏洞

补丁直接去掉了对creq->in_place_op != 1的判断,这样就老老实实的两个地址都检查一遍

diff --git a/drivers/crypto/msm/qcedev.c b/drivers/crypto/msm/qcedev.c
index e63f061..1402d3d 100644
--- a/drivers/crypto/msm/qcedev.c
+++ b/drivers/crypto/msm/qcedev.c
@@ -1234,44 +1234,6 @@ static int qcedev_vbuf_ablk_cipher(struct qcedev_async_req *areq,
 	struct qcedev_cipher_op_req *saved_req;
 	struct	qcedev_cipher_op_req *creq = &areq->cipher_op_req;
 
-	/* Verify Source Address's */
-	for (i = 0; i < areq->cipher_op_req.entries; i++)
-		if (!access_ok(VERIFY_READ,
-			(void __user *)areq->cipher_op_req.vbuf.src[i].vaddr,
-					areq->cipher_op_req.vbuf.src[i].len))
-			return -EFAULT;
-
-	/* Verify Destination Address's */
-	if (creq->in_place_op != 1) {
-		for (i = 0, total = 0; i < QCEDEV_MAX_BUFFERS; i++) {
-			if ((areq->cipher_op_req.vbuf.dst[i].vaddr != 0) &&
-						(total < creq->data_len)) {
-				if (!access_ok(VERIFY_WRITE,
-					(void __user *)creq->vbuf.dst[i].vaddr,
-						creq->vbuf.dst[i].len)) {
-					pr_err("%s:DST WR_VERIFY err %d=0x%lx\n",
-						__func__, i, (uintptr_t)
-						creq->vbuf.dst[i].vaddr);
-					return -EFAULT;
-				}
-				total += creq->vbuf.dst[i].len;
-			}
-		}
-	} else  {
-		for (i = 0, total = 0; i < creq->entries; i++) {
-			if (total < creq->data_len) {
-				if (!access_ok(VERIFY_WRITE,
-					(void __user *)creq->vbuf.src[i].vaddr,
-						creq->vbuf.src[i].len)) {
-					pr_err("%s:SRC WR_VERIFY err %d=0x%lx\n",
-						__func__, i, (uintptr_t)
-						creq->vbuf.src[i].vaddr);
-					return -EFAULT;
-				}
-				total += creq->vbuf.src[i].len;
-			}
-		}
-	}
 	total = 0;
 
 	if (areq->cipher_op_req.mode == QCEDEV_AES_MODE_CTR)
@@ -1569,6 +1531,36 @@ static int qcedev_check_cipher_params(struct qcedev_cipher_op_req *req,
 			__func__, total, req->data_len);
 		goto error;
 	}
+	/* Verify Source Address's */
+	for (i = 0, total = 0; i < req->entries; i++) {
+		if (total < req->data_len) {
+			if (!access_ok(VERIFY_READ,
+				(void __user *)req->vbuf.src[i].vaddr,
+					req->vbuf.src[i].len)) {
+					pr_err("%s:SRC RD_VERIFY err %d=0x%lx\n",
+						__func__, i, (uintptr_t)
+							req->vbuf.src[i].vaddr);
+					goto error;
+			}
+			total += req->vbuf.src[i].len;
+		}
+	}
+
+	/* Verify Destination Address's */
+	for (i = 0, total = 0; i < QCEDEV_MAX_BUFFERS; i++) {
+		if ((req->vbuf.dst[i].vaddr != 0) &&
+			(total < req->data_len)) {
+			if (!access_ok(VERIFY_WRITE,
+				(void __user *)req->vbuf.dst[i].vaddr,
+					req->vbuf.dst[i].len)) {
+					pr_err("%s:DST WR_VERIFY err %d=0x%lx\n",
+						__func__, i, (uintptr_t)
+							req->vbuf.dst[i].vaddr);
+					goto error;
+			}
+			total += req->vbuf.dst[i].len;
+		}
+	}
 	return 0;
 error:
 	return -EINVAL;