发现一个好项目,可能之前我看过但是没仔细学习,最近可以算是真正开始认真分析了
- https://github.com/jiayy/android_vuln_poc-exp
冰刃实验室写的《高通加解密引擎提权漏洞解析》,写的相当好,又学习到了很多
- https://www.iceswordlab.com/2017/08/07/qualcomm-crypto-engine-vulnerabilities-exploits/
我这里分析的是其中的CVE-2016-6738,高通加解密驱动模块内核任意地址写漏洞,补丁如下
- https://source.codeaurora.org/quic/la//kernel/msm-3.18/commit/?id=0a2528569b035a2ca8ebe9a4612dbbaaaffa5b2e
注册的驱动结构体
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;