分类: LINUX
2010-01-13 16:03:33
Linux NULL pointer dereference due to incorrect proto_ops initializations
------------------------------------------------------------------------
In the Linux kernel, each socket has an associated struct of operations
called proto_ops which contain pointers to functions implementing various
features, such as accept, bind, shutdown, and so on.
If an operation on a particular socket is unimplemented, they are expected
to point the associated function pointer to predefined stubs, for example if
the "accept" operation is undefined it would point to sock_no_accept(). However,
we have found that this is not always the case and some of these pointers are
left uninitialized.
This is not always a security issue, as the kernel validates the pointers at
the call site, such as this example from sock_splice_read:
static ssize_t sock_splice_read(struct file *file, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len,
unsigned int flags)
{
struct socket *sock = file->private_data;
if (unlikely(!sock->ops->splice_read))
return -EINVAL;
return sock->ops->splice_read(sock, ppos, pipe, len, flags);
}
【Godbach注】上面的代码就是内核中常见的,调用函数指针指向的函数之前,先判断该指针的有效性。如果是合法的指针,则调用其指向的函数。当然,如果所有的proto_ops结构都对函数指针进行初始化,使其指向一个有效的函数,那么这里不进行指针有效性的判断也是可以的。
But we have found an example where this is not the case; the sock_sendpage()
routine does not validate the function pointer is valid before dereferencing
it, and therefore relies on the correct initialization of the proto_ops
structure.
We have identified several examples where the initialization is incomplete:
- The SOCKOPS_WRAP macro defined in include/linux/net.h, which appears correct
at first glance, was actually affected. This includes PF_APPLETALK, PF_IPX, PF_IRDA, PF_X25 and PF_AX25 families.
- Initializations were missing in other protocols, including PF_BLUETOOTH, PF_IUCV, PF_INET6 (with IPPROTO_SCTP), PF_PPPOX and PF_ISDN.
【Godbach注】但是sock_sendpage函数指针解引用的时候并没有检查其合法性。而且在诸如协议族PF_BLUETOOTH, PF_IUCV, PF_INET6 (protocol IPPROTO_SCTP), PF_PPPOX 和 PF_ISDN的proto_ops中并为未始化sendpage函数指针,默认的为NULL。这就导致这几个协议对应的socket传输中,一旦内核态执行到sendpage函数,肯定会出现对NULL指针的解引用。这就引起了Linux内核态NULL指针BUG,并在满足一定前提下,可以被恶意代码利用。
--------------------
Affected Software
------------------------
All Linux 2.4/2.6 versions since May 2001 are believed to be affected:
- Linux 2.4, from 2.4.4 up to and including 2.4.37.4
- Linux 2.6, from 2.6.0 up to and including 2.6.30.4
--------------------
Consequences
--------------------
This issue is easily exploitable for local privilege escalation. In order to
exploit this, an attacker would create a mapping at address zero containing
code to be executed with privileges of the kernel, and then trigger a
vulnerable operation using a sequence like this:
/* ... */
int fdin = mkstemp(template);
int fdout = socket(PF_PPPOX, SOCK_DGRAM, 0);
unlink(template);
ftruncate(fdin, PAGE_SIZE);
sendfile(fdout, fdin, NULL, PAGE_SIZE);
/* ... */
Please note, sendfile() is just one of many ways to cause a sendpage
operation on a socket.
Successful exploitation will lead to complete attacker control of the system.
【Godbach注】上面的示例代码就是触发内核态访问sendpage函数指针,由于其指向NULL,因此这段代码就可以引起内核态访问NULL指针,出现Oops。如果用户态将0地址(通常NULL为0)设置为可执行,那么内核代码就会顺利的执行0地址的指令。如果0地址被恶意注入了修改当前进程ID的代码,那么当前进程就有可能从普通用户的身份变为root用户。
-------------------
Mitigation
-----------------------
Recent kernels with mmap_min_addr support may prevent exploitation if
the sysctl vm.mmap_min_addr is set above zero. However, administrators
should be aware that LSM based mandatory access control systems, such
as SELinux, may alter this functionality.
It should also be noted that all kernels up to 2.6.30.2 are vulnerable to published attacks against mmap_min_addr.
【Godbach注】这里提到了虽然内核存在该BUG,但是如果不允许用户态映射0地址的话,这个BUG还是无法被利用的,这个可以通过设置sysctl变量vm.mmap_min_addr大于0来实现。但是一些基于强制访问控制的Linux安全模块,比如SElinux,有可能修改这个变量,进而使0地址仍然可以映射。如果禁用了SElinux的话,该BUG就不能被利用了。因此,有人据此认为SElinux弱化了系统的安全。我会在该系列的第四篇文章中转载SElinux Team的开发人员的看法,进一步了解SElinux。
-------------------
Solution
-----------------------
Linus committed a patch correcting this issue on 13th August 2009.
-------------------
Credit
-----------------------
This bug was discovered by Tavis Ormandy and Julien Tinnes of the Google
Security Team.