Pixel 6 Pro单漏洞提权:Mali GPU驱动漏洞分析与利用

本文详细分析了Pixel 6 Pro上Mali GPU驱动中的CVE-2023-48409漏洞,展示了如何仅利用单个整数溢出漏洞绕过Android安全机制实现本地权限提升,涵盖物理内存喷射、页表操作等技术细节。

根本原因分析

CVE-2023-48409

该漏洞在2023年12月的Pixel安全公告中被覆盖。通过回滚设备版本到早期补丁UP1A.231005.007进行分析:

 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
40
41
42
43
int gpu_pixel_handle_buffer_liveness_update_ioctl(struct kbase_context* kctx,
                                                  struct kbase_ioctl_buffer_liveness_update* update)
{
	int err = 0;
	struct gpu_slc_liveness_update_info info;
	u64* buff;

	/* 计算需要复制的用户空间数组大小 */
	u64 const buffer_info_size = sizeof(u64) * update->buffer_count;			// [1]
	u64 const live_ranges_size =
	    sizeof(struct kbase_pixel_gpu_slc_liveness_mark) * update->live_ranges_count;
	/* 无需操作 */
	if (!buffer_info_size || !live_ranges_size)
		goto done;

	/* 防止空指针 */
	if (!update->live_ranges_address || !update->buffer_va_address || !update->buffer_sizes_address)
		goto done;
	/* 分配我们需要从用户空间复制的内存 */
	buff = kmalloc(buffer_info_size * 2 + live_ranges_size, GFP_KERNEL);			// [2]

	/* 通过指向分配来设置info结构体。全部8字节对齐 */
	info = (struct gpu_slc_liveness_update_info){						// [3]
	    .buffer_va = buff,
	    .buffer_sizes = buff + update->buffer_count,
	    .live_ranges = (struct kbase_pixel_gpu_slc_liveness_mark*)(buff + update->buffer_count * 2),
	    .live_ranges_count = update->live_ranges_count,
	};
	/* 从用户空间复制数据 */
	err =
	    copy_from_user(info.live_ranges, u64_to_user_ptr(update->live_ranges_address), live_ranges_size);
	if (err) {
		dev_err(kctx->kbdev->dev, "pixel: failed to copy live ranges");
		err = -EFAULT;
		goto done;									// [4]
	}
    
...

done:
	kfree(buff);
	return err;
}

具体来说,漏洞出现在整数溢出计算中:

  • 分配的对象大小:8*buffer_count*2 + 4*live_ranges_count([1]和[2])
  • 从分配对象的偏移量:+ 8*buffer_count*2([1]和[3])

如果精心构造值使得8*buffer_count*2 + 4*live_ranges_count = (1<<64) + <object_size>,我们的live_ranges缓冲区将位于分配对象之前4*live_ranges_count - <object_size>字节处。

什么是CONFIG_HARDENED_USERCOPY?

CONFIG_HARDENED_USERCOPY是自Linux v4.8引入的内核配置。在copy_*_user期间,内核调用check_object_size,如果指针落在SLUB对象内,它确保读/写区域必须完全落在该特定对象的可用区域内。

缺失的部分:CVE-2023-26083

原始漏洞利用中的第二个漏洞CVE-2023-26083涉及驱动中的流功能(tlstream),该功能自由暴露给用户空间进程,可能包含某些对象的内核地址作为纯字节。

单漏洞提权

物理映射喷射

ret2dir是一种滥用physmap的流行技术,physmap是将物理内存直接1:1映射到内核虚拟地址空间。

通过物理映射喷射,我们可以耗尽页分配器,最大化对象在我们选择地址分配的机会。

页表喷射

脏页表技术依赖于这样一个事实:页表本身由页分配器作为普通order-0块分配,通过写原语,我们可以修改其条目,使mmap的用户态虚拟页反映几乎任何任意的物理页。

从物理到虚拟:转换我们的原语

有趣的是,我们仍然没有适当的泄漏。尝试munmap相关页面不会立即释放页表,而是通过RCU排队。

Android的独特挑战

绕过Android的应用沙箱

在Termux环境中,当我们尝试调用setuid(0)时,程序立即因SIGSYS而崩溃。这是由于所有应用程序上默认启用的seccomp过滤器。

禁用SELinux强制

一旦我们拥有任意内核写入,通过清除selinux_state.enforcing位来完全禁用SELinux是相对简单的。

意外发现

理解时间线流泄漏

Mali GPU驱动提供了一个称为时间线流(tlstream)的功能,它基本上充当事件记录器。它通过跟踪点提供相关函数在驱动中记录信息的功能,包括内核中的指针(指向GPU相关对象)。

命令流前端

第3代Valhall GPU引入了命令流前端(CSF)作为其先前作业管理器(JM)模型的替代,以更好地适应现代API(如Vulkan)的需求。

黄金对象:kbase_context

在源代码中快速挖掘后,我发现了不同的低挂果实:kbase_context在上下文创建时立即泄漏其地址,并且它包含一个task_struct指针,直接指向我们当前的任务!

清理:使漏洞利用实用

在Android上,仅清除uid/gid不足以获得完全权限。需要考虑的更多事项:

  • 我们还没有所有能力
  • SELinux尚未禁用
  • 进程沙箱仍然有效
  • 退出进程会使内核崩溃

结论

这项研究成功证明了Pixel 6 Pro可以使用单个漏洞进行利用,挑战了传统上需要CVE-2023-48409和CVE-2023-26083两个漏洞的常规认知。

Android确实有其有趣的特性,既(主要)加强又(有些可能无意中)削弱了其防御,如上述漏洞利用过程中所示。

至于分析的两个漏洞,本文希望强调简单的弱点如何仍然可以在复杂的内核驱动中容易找到,以及它们可能造成的损害。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计