Linux内核info leak漏洞

摘要: 介绍一种Linux内核的信息泄漏漏洞检测方法。没什么内容,语义分析,可以集成在Coverity等静态扫描工具中。

1 Information Leak漏洞风险

从应用层软件,到hypervisor再到kernel代码,都存在Information Leak的风险。下面给出一些示例:

应用层软件:通常是应用敏感数据泄漏,比如从远程客户端获取服务端敏感数据。CVE-2012-0053,Openssl的心脏滴血等。

Hypervisor:主要是向guest泄漏hypervisor数据。CVE-2010-4525.

Kernel代码:泄漏内核地址,空间布局等,如CVE-2013-2147.

这里主要分析内核中的Information Leak漏洞所带来的风险。内核中的Information Leak通常都是用来绕过内核中的保护机制(利用缓解:StackGuard, ASLR),由于本身并不能直接用来形成提权等高风险操作,因此Information Leak漏洞经常被人忽视。

先来看一下这些保护机制。

StackGuard.

StackGuard是一种编译器实现的保护技术,它在栈函数返回地址前插入一个“canary”,当发生溢出“canary”值被破坏,将触发系统的异常处理流程。它的安全性依赖于“canary”的保密,也就是“canary”不能被攻击者预测或取到。

ASLR.

ASLR技术是将进程等的加载地址随机化,它的安全性依赖于加载基地址的不可预测,使exploit不能精确进行地址覆盖。

无论“canary”还是ASLR的基地址,对攻击者来说都是“秘密”。也就是在没有Information Leak漏洞前提下,这些都是用户不可直接获取的。但Information Leak漏洞可以辅助攻击者获取到这些“秘密”,进而绕过内核中的保护机制,成功实现漏洞利用。

2 Information Leak漏洞分类

根据漏洞成因,可以对Information Leak漏洞进行分类。这里同样只关注内核中的情况。

字节对齐带来的内存“空洞”.

为了程序性能,编译器在编译代码时会对变量进行字节对齐,从而引入了一些内存“空洞”。比如结构体使用sizeof计算的大小一般会大于各个成员占用空间大小的和。当这些内核中的内存“空洞”没有被初始化(ABI没有规定函数退栈时要清理这些栈空间),通过copy_to_user等函数拷贝到用户空间时,就会造成Information Leak漏洞,泄漏内核栈中的数据,比如泄漏了一个栈上指针,就可以通过它来计算进程基址(stackjack攻击)。

缺少变量初始化.

内核函数中的本地变量声明后,默认不会被初始化。根据C99描述这块空间的内容是不确定的。实际上栈空间是被各函数复用的,因此未初始化变量的内容很可能保存的是上个函数栈上的数据。

缺少对用户读操作的检查.

当向用户空间拷贝数据时,没有做大小检查或者检查逻辑出现错误,都会导致Information Leak。这类漏洞通常称作“越界读”,它允许用户态读取不应该被访问的内核空间数据。

其它bug导致的infoleaks.

其它的Information Leak原因这里不做研究,但提一下。比如/proc/,/sys/和/boot/文件系统中也提供了内核符号地址,它们已经靠kptr_restrict机制保护,但也可能因为bug而绕过。另外系统缓存,日志等都有可能导致Information Leak.

上面说到Information Leak可能危害保证StackGuard和ASLR可靠基础的“秘密”,下面分析一下内核中的Information Leak确切会影响哪些数据。

Data段.

内核中的data段保存了编译时就确定的全局变量,data段的泄漏可能导致静态内核symbols的泄漏,比如某些用于配置的变量。

内核栈是根据ABI约定,运行时分配的。里面包含了函数返回地址,栈指针和一些其它数据。比如函数调用的参数,StackGuard机制的“canary”等。另外如果没有实现栈地址随机化,还会泄漏栈布局。

内核中的堆是由内存分配器管理,在需要的地方动态分配。这些堆分配器通常使用双向链表来管理这些堆内存。Information Leak会漏洞这些堆存储的内容,还有可能泄漏用于堆管理的结构数据。

3 栈的Information Leak漏洞检测技术

分析目前的漏检测技术,发现通过数据流分析的方法,可以对Information Leak进行建模来进行漏洞检测。在模型里定义3个基本元素:数据源,数据接收方和传播路径。

我们可以对程序进行语义分析来匹配这套模型,从而识别漏洞。语义分析这个工作,选用开源的Coccinelle工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
handler(...) {

<...

T ID;

... when != memset(&ID, 0, ...)

when != ID = ...

* copy_to_user(EV, &ID, EN)

...>}

1) 数据源:ID变量

2) 数据接收方:用户态指针EV

3) 传播路径:我们想确定ID的内容没有被初始化。因此限定条件,ID在copy_to_user前没有memset()或初始化操作

像其它基于数据流的静态检测技术类似,这种方法也存在缺陷。比如这种Information Leak检测方法假定漏洞发生在一个函数内的,因此这种方法覆盖不了多函数场景。但实际测试中,依然会发现很多Linux内核和三方Driver的Information Leak漏洞。