Linux drm_legacy_lock_free 空指针引用bug分析
知道创宇安全研究团队 niubl:2015.9.25
1. 漏洞描述
Linux 在打开显卡设备使用ioctl()函数控制显卡时会调用DRM驱动程序,其中在调用drm_legacy_lock_free()函数时,获取lock指针的值赋值给old变量时,未考虑lock值可能会存在0值,导致空指针引用bug。
2. 漏洞影响
Linux kernel 4.2(Arch测试)
Linux kernel 3.19.0-28 (Ubuntu测试)
3. 漏洞分析
该bug使用Trinity fuzz发现,Trinity是一款Linux 系统调用fuzz工具,用来测试Linux系统调用,运行Trinity:
1 |
./trinity -X -C 16 |
Trinity X参数意图使root权限运行Trinity时切换到nogroup低权限运行Trinity进程,可是当我这样运行Trinity发现bug时,在POC重现时发现只能以root权限触发,原因以后分析。
运行Trinity可能会导致系统崩溃,也正是我们发现bug的最好时机,系统崩溃后如何发现崩溃原因是我们关注的,这里采用Ubuntu下的linux-crashdump软件,保存系统崩溃现场。
1 |
sudo apt-get install linux-crashdump |
系统崩溃后会自动生成dump,保存在/var/crash目录,并重启系统,现在使用crash工具分析dump,在使用crash分析dump时需要系统的调试符号表,安装如下:
1 2 3 4 5 6 |
sudo tee /etc/apt/sources.list.d/ddebs.list << EOF deb http://ddebs.ubuntu.com/ $(lsb_release -cs) main restricted universe multiverse deb http://ddebs.ubuntu.com/ $(lsb_release -cs)-security main restricted universe multiverse deb http://ddebs.ubuntu.com/ $(lsb_release -cs)-updates main restricted universe multiverse deb http://ddebs.ubuntu.com/ $(lsb_release -cs)-proposed main restricted universe multiverse EOF |
1 2 3 |
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ECDCAD72428D7C01 sudo apt-get update sudo apt-get install linux-image-$(uname -r)-dbgsym |
国内下载速度很慢,也可以在这里手动下载后安装,以Ubuntu 内核3.19.0-28举例:
安装完成后分析dump,crash装载dump文件,运行crash工具需要root权限:
1 |
crash /usr/lib/debug/boot/vmlinux-3.19.0-28-generic 201509161541/dump.201509161541 |
装载如图:
运行bt命令查看调用栈,可以看到崩溃发生在函数drm_legacy_lock_free()函数中,#8下面:
反汇编drm_legacy_lock_free()函数:
结合内核崩溃时的寄存器值,从上面的反汇编代码中可以看出drm_legacy_lock_free+64代码处把%rbx(寄存器,后面省略)的值赋值给%ecx,而%rbx此时的值为0,导致空指针引用bug。%rbx来自%rdi的值,在drm_legacy_lock_free+30代码处可以看出,而%rdi就是drm_legacy_lock_free()函数的第一个参数(参见Linux x86_64寄存器参数传递方法),%rbx就是drm_legacy_lock_free()函数中的lock指针,%rdi就是drm_legacy_lock_free()函数参数lock_data的值,而%rdi的值呢,我们可以看到在drm_legacy_lock_free+11代码处和drm_legacy_lock_free+17代码处分别传递给了%14,%13,观察系统崩溃现场寄存器可以看到两个寄存器的值,因此%rdi值为0xffff8800c2480040。
Linux内核源代码源码地址:
http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_lock.c#L255
顺着调用栈向上,来到调用drm_legacy_lock_free()函数的drm_legacy_unlock()函数处,反汇编代码:
可以看到drm_legacy_unlock+23代码处调用了drm_legacy_lock_free()函数,在调用之前做了一些参数设置准备,drm_legacy_unlock+19代码处给%rdi加0x40,drm_legacy_unlock+8代码处把%rdx+0x78的值赋值给%rdi,从drm_legacy_lock_free()函数的分析中我们知道%rdi的值为0xffff8800c2480040,那么现在%rdx+0x78的值可以推测出来,即0xffff8800c2480000,即master指针指向的,那么%rdx是多少,%rdx是drm_legacy_unlock()函数的第三个参数(参见Linux x86_64寄存器参数传递方法),结合内核源码可以看出%rdx就是参数 file_priv的值。
Linux内核源代码源码地址:
http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_lock.c#L151
顺着调用栈再向上,来到调用drm_legacy_unlock()函数的drm_ioctl()函数处,反汇编:
由调用栈可以看出在drm_ioctl+810代码处调用了drm_legacy_unlock()函数,他的第三个参数%rdx是由%r15赋给的,%r15的值在drm_ioctl()函数的开头可以找到,如图:
上图中,%rdi+0xd0的值赋值给了%r15,%rdi是drm_ioctl()函数的第一个参数(参见Linux x86_64寄存器参数传递方法),结合内核源码可以看出即是filp指针,filp指针是一个file类型的指针,%rdi+0xd0的值即是filp->private_data(file_priv指向它),那么可以说filep->private_data->->file_priv->master的值是0xffff8800c2480000,我们在内存中寻找他
1 |
search -t 0xffff8800c2480000 |
有四个结果,我已经找到了正确的,第二个地址0xffff8800933c2078,我们看看它:
master是file_priv的drm_file结构体成员,看drm_file结构(http://lxr.free-electrons.com/source/include/drm/drmP.h#L289)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct drm_file { 290 unsigned authenticated :1; 291 /* Whether we're master for a minor. Protected by master_mutex */ …… 310 /** Mapping of mm object handles to object pointers. */ 311 struct idr object_idr; 312 /** Lock for synchronization of access to object_idr. */ 313 spinlock_t table_lock; 314 315 struct file *filp; 316 void *driver_priv; 317 318 struct drm_master *master; /* master this node is currently associated with 319 N.B. not always minor->master */ 320 /** …… |
可以看出结构体中,在master的上面第315行代码处有filep的地址,那么从刚才的内存中可以看出,filep的值为0xffff88003b3cfa00,验证一下:
1 |
struct file 0xffff88003b3cfa00 |
上图中显示为0xffff88003b3cfa00地址按照file结构体显示的数据,其中private_data值为0xffff8800933c2000,查看0xffff8800933c2000内存,找到0xffff8800c2480000的值,证明filep的值正确。filep就是打开文件的文件描述符,地址0xffff88003b3cfa00,那么我们可以看看系统崩溃时打开了那些文件,crash中运行files命令:
由上图可以看出filep文件描述符的地址赫然在列,句柄688,路径是/dev/dri/card0。
回头看下调用栈:
上图中可以看出,#13在用户态的寄存器现场中,传递给ioctl()函数的第一个参数%rdi,他的值就是0x2b0,十进制就是688,也就说明正是他打开了/dev/dri/card0设备文件,那么POC已经可以写出了:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <fcntl.h> #include <errno.h> #include <stdio.h> main() { int fd; int i = 20; fd = open("/dev/dri/card0", O_RDONLY); ioctl(fd, 0x4008642b, &i); fprintf(stderr, "the errno is %d\n", errno); } |
gcc编译后执行会导致系统崩溃,执行需要root权限。
该bug已经提交给linux官方,目前仍未修复:https://bugzilla.kernel.org/show_bug.cgi?id=104831。
4. 相关资源链接
1. https://wiki.ubuntu.com/Kernel/CrashdumpRecipe
2.http://lxr.free-electrons.com/
3.https://github.com/kernelslacker/trinity
4.http://codemonkey.org.uk/projects/trinity/
5.https://zh.wikipedia.org/wiki/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A
6.https://launchpad.net/ubuntu/trusty/amd64/
7.https://bugzilla.kernel.org/show_bug.cgi?id=104831