Qualcomm Securitybulletin CVE-2023-43516 漏洞分析

0x01 背景

根据高通披露的信息来看 https://docs.qualcomm.com/product/publicresources/securitybulletin/february-2024-bulletin.html#Openref

这是一个在 处理Video fence 模块内,存在指针偏移越界的问题,由于在接收的fence payload 信息时会产生内存冲突。
Use of out-of-range pointer offset in Video :Memory corruption when malformed message payload is received from firmware.

相关的修复代码如下, https://git.codelinaro.org/clo/la/platform/vendor/opensource/video-driver/-/commit/e21682f825e909a4389bee60bcd1768423aede97 在原来的代码内的判断fence payload_size 异常后添加了 return -EINVAL,其中的 -EINVAL 代表无效参数,当一个函数接收到无效参数是,就会返回这个错误代码22。

0x02 漏洞分析

漏洞类型: OOB
存在漏洞的函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
line 1679 ~ line 1721  /video-driver/driver/vidc/src/venus_hfi_response.c
static int handle_property_fence_array(struct msm_vidc_inst *inst,
struct hfi_packet *pkt)
{
u64 cur_fence_id = 0, prev_fence_id = 0;
int i = 0, fence_count = 0;
u32 payload_size;
u8 *payload_start;
if (!inst || !pkt || !inst->capabilities) {
i_vpr_e(inst, "%s: invalid params\n", __func__);
return -EINVAL;
}
payload_size = pkt->size - sizeof(struct hfi_packet);
fence_count = payload_size / sizeof(u64);
payload_start = (u8 *)((u8 *)pkt + sizeof(struct hfi_packet));
if (payload_size > sizeof(inst->hfi_frame_info.fence_id)) {
i_vpr_e(inst,
"%s: fence list payload size %d exceeds expected max size %d\n",
__func__, payload_size, sizeof(inst->hfi_frame_info.fence_id));
msm_vidc_change_state(inst, MSM_VIDC_ERROR, __func__);
return -EINVAL; // + 修复代码
}
for (i = 0; i < fence_count; i++) {
cur_fence_id = *((u64 *)payload_start + i); // OOB vul !!
if (prev_fence_id > cur_fence_id) {
i_vpr_e(inst, "%s: invalid fence id %llu, prev %llu\n",
__func__, cur_fence_id, prev_fence_id);
msm_vidc_change_state(inst, MSM_VIDC_ERROR, __func__);
return -EINVAL;
}
inst->hfi_frame_info.fence_id[i] = cur_fence_id;
inst->hfi_frame_info.fence_count++;
prev_fence_id = cur_fence_id;
}
i_vpr_l(inst, "%s: fence_id[0] %llu, received %d, expected %d\n", __func__,
inst->hfi_frame_info.fence_id[0], inst->fences_per_output_counter,
inst->hfi_frame_info.fence_count);
return 0;
}

这里详细的解释一下其中的代码:

  • struct hfi_packet *pkt;payload_size = pkt->size - sizeof(struct hfi_packet);
    表达式 `pkt->size - sizeof(struct hfi_packet)` 的意思是:从`pkt`指向的`hfi_packet`结构体的`size`成员值中减去整个`hfi_packet`结构体的大小,得到的结果就是有效载荷的大小。换句话说,这是指结构体中用于存储实际数据(即除去结构体固定头部信息之外的部分)的空间大小。这种计算常见于网络编程、数据包处理或任何需要从结构体中区分出元数据和实际数据部分的场景。
  • fence_count = payload_size / sizeof(u64); 是计算安全可访问的fence 数量
    其中的u64 表示无符号64位整数,需要占用8个字节的存储空间,每个字节Byte 由8 位bit 组成。要存储64位的数值,自然就需要8个字节的存储空间。每个字节能够存储2^8(即256)种不同的值,8个字节一共可以表示2^(8*8)=2^64种不同的值,正好符合64位无符号整数的需求。所以在内存中,一个u64类型的变量会占用连续的8字节空间。
  • payload_start = (u8 *)((u8 *)pkt + sizeof(struct hfi_packet));
    表示数据包中有效的payload 部分的指针指向的地址,即pkt 指针 + 结构体hif_packet 的大小的偏移。

根据其中的代码可以了解到,fence_count 的值是根据 pkt->size 来确定的,而ptk 是可以通过外部传入的结构体,因此虽然代码中对fence_count 的大小进行了范围的限制,但其限制并没有完全保护接下来的循环。

如果fence_count 的值非常大,那么就决定了循环的次数会很大,每次循环都会从payload_start指向的内存中读取一个u64(8字节)的数据作为cur_fence_id,其中的 i 的值也会很大因而

  • cur_fence_id = *((u64 *)payload_start + i);
    每次访问到有效payload 之外的数据,因而产生越界访问 OOB 。

漏洞函数从驱动开始的调用栈
driver/vidc/src/msm_vidc_probe.c
srtuct platform_driver msm_vidc_driver –>
int msm_vidc_probe(struct platform_device *pdev) —>
int msm_vidc_probe_video_device(struct platform_device *pdev) —>
static int msm_vidc_init_irq(struct msm_vidc_core *core) —>
driver/vidc/src/venus_hfi.c
irqreturn_t venus_hfi_isr_handler(int irq, void *data) —>
static int __response_handler(struct msm_vidc_core *core) —>
driver/vidc/src/venus_hfi_response.c
int handle_response(struct msm_vidc_core *core, void *response) —>
static int __handle_session_response(struct msm_vidc_inst *inst, struct hfi_header *hdr)—>
static int handle_session_property(struct msm_vidc_inst *inst,struct hfi_packet *pkt) —>
static int handle_property_with_payload(struct msm_vidc_inst *inst, struct hfi_packet *pkt, u32 port) —>
static int handle_property_fence_array(struct msm_vidc_inst *inst, struct hfi_packet *pkt) —> OOB

其中venus_hfi_isr_handler 这是一个中断服务例程,当硬件触发中断时,Linux内核会自动调用该函数来响应中断。

有关本驱动的信息参考如: https://lore.kernel.org/lkml/1690550624-14642-1-git-send-email-quic_vgarodia@quicinc.com/T/

这个驱动用于视频流解码/编码。该驱动程序具有以下功能:

  • 具有 M2M 和流媒体功能的 V4L2 兼容视频驱动程序。
  • 支持H264、H265、VP9解码器。
  • 支持H264、H265编码器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
irqreturn_t venus_hfi_isr_handler(int irq, void *data)
{
struct msm_vidc_core *core = data;
int num_responses = 0, rc = 0;
d_vpr_l("%s()\n", __func__);
if (!core) {
d_vpr_e("%s: invalid params\n", __func__);
return IRQ_NONE;
}
core_lock(core, __func__);
rc = __resume(core);
if (rc) {
d_vpr_e("%s: Power on failed\n", __func__);
core_unlock(core, __func__);
goto exit;
}
call_venus_op(core, clear_interrupt, core);
core_unlock(core, __func__);
num_responses = __response_handler(core);
exit:
if (!call_venus_op(core, watchdog, core, core->intr_status))
enable_irq(irq);
return IRQ_HANDLED;
}

在 driver/vidc/src/msm_vidc_probe.c 内 会初始化驱动,其中的 probe 函数是驱动程序的核心入口点,负责识别和初始化硬件设备。probe函数是驱动程序与硬件设备交互的桥梁

1
2
3
4
5
6
7
8
9
10
struct platform_driver msm_vidc_driver = {
.probe = msm_vidc_probe,
.remove = msm_vidc_remove,
.driver = {
.name = "msm_vidc_v4l2",
.of_match_table = msm_vidc_dt_match,
.pm = &msm_vidc_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};

主要的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct hfi_header {
u32 size;
u32 session_id;
u32 header_id;
u32 reserved[4];
u32 num_packets;
};
struct hfi_packet {
u32 size;
u32 type;
u32 flags;
u32 payload_info;
u32 port;
u32 packet_id;
u32 reserved[2];
};

数据包的结构分析

主要分析的是 hfi_header 和 hfi_packet 这两个结构体

根据验证hdr_packet 有效性函数来看 validate_hdr_packet() ,其数据包的组成结构如下
| hfi_header | hif_packet + packet_payloads |

其中 hif_packet ->size 指针表示的是当前 hif_packet + packet_payloads 的大小。
并且 hdr->num_packets 表示当前 hfi_packet 的个数。所以直接遍历数据包的个数,从每个数据包中提取出payload 。


Qualcomm Securitybulletin CVE-2023-43516 漏洞分析
https://tig3rhu.github.io/2024/05/20/57__Qualcomm Securitybulletin CVE-2023-43516 漏洞分析/
Author
Tig3rHu
Posted on
May 20, 2024
Licensed under