条件竞争学习之CVE-2017-7368复现
条件竞争漏洞基础
在 mitre 中有不少有关条件竞争类型的编号,比如 CWE-362、CWE-364、CWE-366、CWE-367 等。在mitre 中属于一个比较大的板块,并且不少的内核有关的高危漏洞,也有条件竞争漏洞类型,比如 DirtyCow,当然也有一些条件竞争漏洞介绍的是 web 方面的问题,这里介绍的是内核。
在CTF wiki: https://ctf-wiki.org/pwn/linux/user-mode/race-condition/introduction/
条件竞争称为Race Condition,是一种在多线程或多进程并发执行时可能导致不正确行为或数据损坏的安全问题。这种漏洞通常发生在多线程或进程试图访问和修改共享资源(如内存、文件、网络连接等)时,由于执行顺序不确定或没有适当的同步的措施,导致竞争条件的发生并且条件竞争竞争在内核也竞争出现。
在我的理解中,存在条件竞争漏洞的代码段在正常的审计来看,其实在执行的时候,并不会出现问题,但是如果代码段中存在共享对象时,可能是一些全局变量,文件等,就有可能在代码段运行时候,同时起一个进程或线程来修改共享对象,就可能修改代码的正常执行逻辑,从而产生一些问题。
以前在学习多线程的时候,写多线程的代码,碰到循环的时候经常产生执行的代码顺序不会根据自己设定的流程来执行,CTF wiki 例子中就是如此,有很直观的感觉,也就是说代码在很短的时间窗口中,是可以被破坏和修改的。
根据CTF wiki内条件竞争应该具备以下的条件:
- 并发,至少存在两个并发执行流,这里的执行流包括线程、进程、任务等级别的执行流。
- 共享对象,至少有一个共享资源被多个并发执行的线程或进程访问或修改。这个共享资源可以是内存中的全局变量、文件、网络连接等。一般来说,这些共享对象是用来使得多个程序执行流相互交流。此外,我们称共享对象的代码为临界区。在正常写代码时,这部分应该加锁。
- 改变对象,也就是修改。即至少有一个控制流会改变竞争对象的状态,因为如果程序只是对对象进行读操作,那么并不回产生条件竞争。
在漏洞挖掘的时候,要发现条件竞争漏洞通常并不是很简单,因为代码本身的执行流程不会出现问题,因为一般来说,在执行代码的时候,并发的情况并不多,而且存在能修改共享对象的情况也并不常见。
这里在看雪的一个帖子中,看到有人描述了对驱动条件竞争漏洞挖掘的思路方式,说的也比较言简意赅:
- 首先起一个线程,调用某一个系统调用
- 起另一个线程,密集的对那个调用送入的参数随意更改,控制好这两个线程的时间序,尽量保证一个线程系统调用后,另一个系统密集修改里面的数据。
- 等崩溃。
https://bbs.kanxue.com/thread-214551.htm
https://cloud.tencent.com/developer/article/1516412
https://h0pe-ay.github.io/KernelPwn-RaceCondition/
https://xuanxuanblingbling.github.io/ctf/pwn/2019/11/18/race/
CVE-2017-7368 漏洞分析
分析参考: https://paper.seebug.org/364/
为了更好的理解条件竞争,看到有一篇有关高通声卡驱动CVE-2017-7368 的分析blog, 正好也在分析高通驱动,所以拿出来做一个比较好的学习内容和案例。
由于年代久远,这里找到了有关的代码。
https://android.googlesource.com/kernel/msm/+/android-msm-hammerhead-3.4-kk-r1/sound/soc/msm/qdsp6v2/msm-lsm-client.c
其中的漏洞详情分析如下:
- 在驱动的ioctl cmd参数SNDRV_LSM_REG_SND_MODEL 中,会使用copy_from_user函数将用户空间传入的数据分配给结构体snd_model。
- 然后 q6lsm_snd_model_buf_alloc 会给prtd->lsm_client 指针变量分配snd_model.data_size 大小的内存空间,注意这个 snd_model 的数据是用户空间传入的,另外 prtd 这个结构体是全局的,并且结构体内的lsm_client 结构体指针是可以被修改的。
- 最后copy_from_user(prtd->lsm_client->sound_model.data,snd_model.data, snd_model.data_size) 这个函数会将传入的数据根据data_size 的大小复制到 prtd 中。
- 正常来说,目标缓冲区的大小由传入数据大小来分配,是不会出现越界,溢出的问题。但是由于prtd->lsm_client是一个全局变量,如果多线程来调用case SNDRV_LSM_REG_SND_MODEL,那么在一个短时间内可能修改prtd->lsm_client 的大小。而传入的数据还是另一个线程的snd_model.data,进而产生越界的问题。
漏洞复现
由于没有完备的环境来复现触发这个漏洞,但为了能够复现这个漏洞,因此我就自己写了一个类似的驱动程序。
驱动代码如下:
1 | |
使用android 内核编译环境编译成驱动模块(详细步骤见 https://tig3rhu.github.io/2024/03/28/25__Pixel6P_Android%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9D%97%E7%BC%96%E8%AF%91/),上传到手机中,然后进行加载。这里要注意的是,android的编译链跟Linux 编译的make 是不一样的,至少对代码的质量要高一些,比如未使用的变量,定义的局部变量不允许放在代码中间、以及不允许变量声明和代码混合等规则,在标准的C语言中是不允许的,但Linux 使用的是C99 标准拓展支持这些问题。
比如此类问题:
1 | |
然后将编译出来的驱动加载到pixel 6P 内。
如果编译出来的驱动加载到手机中,手机死机的情况就是驱动的问题,那么就要排查一些代码。
然后开始写POC 代码:
1 | |
编译poc,编译链用谷歌官方的编译链,从这里下载 https://developer.android.google.cn/ndk/downloads?hl=en, 然后确定手机系统使用的是那个Android API,可以在手机内执行 getprop ro.build.version.sdk查看。
1 | |
之后执行文件,就可以触发条件竞争漏洞了,pixel 6P 会触发内核问题,系统会进入到panic状态,进而系统自动重启。
