`
hxy-go
  • 浏览: 39088 次
  • 性别: Icon_minigender_1
  • 来自: 昆明
社区版块
存档分类
最新评论

Linux sock_sendpage空指针漏洞分析

阅读更多
转载:http://hi.baidu.com/wzt85/blog/item/a11e013e3384f2f3838b13e6.html
2009-08-19 12:00
Linux sock_sendpage空指针漏洞分析

>> linux-2.6.18/net/socket.c

static ssize_t sock_sendpage(struct file *file, struct page *page,
int offset, size_t size, loff_t *ppos, int more)
{
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);
}
sock_sendpage函数没有判断sock->ops->sendpage函数指针是否为空,就进行了引用。空指针的存在是由于某些协议的驱动程序
没有正确的初始化函数指针造成的。 exp作者给出的协议驱动程序包括pppox, bluetooth, appletalk, ipx, sctp等。下面以ipx协议
为例, 说明这个漏洞是如何形成的以及如何来触发.

exp代码:
const int domains[][3] = { { PF_APPLETALK, SOCK_DGRAM, 0 },
{PF_IPX, SOCK_DGRAM, 0 }, { PF_IRDA, SOCK_DGRAM, 0 },
{PF_X25, SOCK_DGRAM, 0 }, { PF_AX25, SOCK_DGRAM, 0 },
{PF_BLUETOOTH, SOCK_DGRAM, 0 }, { PF_IUCV, SOCK_STREAM, 0 },
{PF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP },
{PF_PPPOX, SOCK_DGRAM, 0 },
{PF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP },
{DOMAINS_STOP, 0, 0 }
};

for (; domains[d][0] != DOMAINS_STOP; d++) {
if ((out = socket(domains[d][0], domains[d][1], domains[d][2])) >= 0)
break;
}

替换成ipx协议就是:
out = socket(PF_IPX,  SOCK_DGRAM, 0);
建立以个PF_IPX协议的套接字接口。 看下这个在内核中是如何实现的:

socket的中断服务程序是sys_socketcall, 在linux-2.6.18/net/socket.c中:

>> sys_socketcall将会调用sys_socket
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
...
switch(call)
{
case SYS_SOCKET:
err = sys_socket(a0,a1,a[2]);
break;
...

}

>> sys_socket调用sock_create进行初始化, 然后调用sock_map_fd与sockfs文件系统进行挂接。
asmlinkage long sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;

retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;

retval = sock_map_fd(sock);
if (retval < 0)
goto out_release;

out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;

out_release:
sock_release(sock);
return retval;
}


>> sock_create
int sock_create(int family, int type, int protocol, struct socket **res)
{
return __sock_create(family, type, protocol, res, 0);
}

>> __sock_create
static int __sock_create(int family, int type, int protocol, struct socket **res, int kern)
{
// 分配sock结构并进行填充
if (!(sock = sock_alloc())) {
if (net_ratelimit())
printk(KERN_WARNING "socket: no more sockets\n");
err = -ENFILE;          /* Not exactly a match, but its the
closest posix thing */
goto out;
}

...
// 这里进行具体协议的初始化操作, 执行ipx驱动的create函数, 这个指针是在ipx驱动加载到
内核时初始化的
if ((err = net_families[family]->create(sock, protocol)) < 0) {
sock->ops = NULL;
goto out_module_put;
}
...
}

继续跟踪ipx驱动的初始化过程, /linux-2.6.18/net/ipx/af_ipx.c:
static int __init ipx_init(void)
{
// 注册ipx协议
int rc = proto_register(&ipx_proto, 1);

if (rc != 0)
goto out;

// 注册协议的操作函数, bug由此开始生成
sock_register(&ipx_family_ops);
...
}

>> sock_register
int sock_register(struct net_proto_family *ops)
{
...
net_family_write_lock();
err = -EEXIST;
if (net_families[ops->family] == NULL) {
将ops指针赋值给net_families[ops->family]
net_families[ops->family]=ops;
err = 0;
}
}

// 从这里可以看出__sock_create中的net_families[family]->create函数是在这里进行初始化的。
static struct net_proto_family ipx_family_ops = {
.family         = PF_IPX,
.create         = ipx_create,
.owner          = THIS_MODULE,
};

继续跟踪ipx_create函数:

static int ipx_create(struct socket *sock, int protocol)
{
...
这个对sock的ops结构进行赋值
sock->ops = &ipx_dgram_ops;
...
}

static const struct proto_ops SOCKOPS_WRAPPED(ipx_dgram_ops) = {
.family         = PF_IPX,
.owner          = THIS_MODULE,
.release        = ipx_release,
.bind           = ipx_bind,
.connect        = ipx_connect,
.socketpair     = sock_no_socketpair,
.accept         = sock_no_accept,
.getname        = ipx_getname,
.poll           = datagram_poll,
.ioctl          = ipx_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl   = ipx_compat_ioctl,
#endif
.listen         = sock_no_listen,
.shutdown       = sock_no_shutdown, /* FIXME: support shutdown */
.setsockopt     = ipx_setsockopt,
.getsockopt     = ipx_getsockopt,
.sendmsg        = ipx_sendmsg,
.recvmsg        = ipx_recvmsg,
.mmap           = sock_no_mmap,
.sendpage       = sock_no_sendpage,
};
我们在这里看到.sendpage结构本以为是被sock_no_sendpage给赋值了, 但是SOCKOPS_WRAPPED这个宏出现了个严重的bug,
实际上是没有对.sendpage进行初始化的, 直接导致了sendpage没被赋值, 这个漏洞由此生成。

#define SOCKOPS_WRAP(name, fam)                                 \
SOCKCALL_WRAP(name, release, (struct socket *sock), (sock))     \
SOCKCALL_WRAP(name, bind, (struct socket *sock, struct sockaddr *uaddr, int addr_len), \
(sock, uaddr, addr_len))                          \
SOCKCALL_WRAP(name, connect, (struct socket *sock, struct sockaddr * uaddr, \
int addr_len, int flags),         \
(sock, uaddr, addr_len, flags))                   \
SOCKCALL_WRAP(name, socketpair, (struct socket *sock1, struct socket *sock2), \
(sock1, sock2))                                   \
SOCKCALL_WRAP(name, accept, (struct socket *sock, struct socket *newsock, \
int flags), (sock, newsock, flags)) \
SOCKCALL_WRAP(name, getname, (struct socket *sock, struct sockaddr *uaddr, \
int *addr_len, int peer), (sock, uaddr, addr_len, peer)) \
SOCKCALL_UWRAP(name, poll, (struct file *file, struct socket *sock, struct poll_table_struct *wait), \
(file, sock, wait)) \
SOCKCALL_WRAP(name, ioctl, (struct socket *sock, unsigned int cmd, \
unsigned long arg), (sock, cmd, arg)) \
SOCKCALL_WRAP(name, compat_ioctl, (struct socket *sock, unsigned int cmd, \
unsigned long arg), (sock, cmd, arg)) \
SOCKCALL_WRAP(name, listen, (struct socket *sock, int len), (sock, len)) \
SOCKCALL_WRAP(name, shutdown, (struct socket *sock, int flags), (sock, flags)) \
SOCKCALL_WRAP(name, setsockopt, (struct socket *sock, int level, int optname, \
char __user *optval, int optlen), (sock, level, optname, optval, optlen)) \
SOCKCALL_WRAP(name, getsockopt, (struct socket *sock, int level, int optname, \
char __user *optval, int __user *optlen), (sock, level, optname, optval, optlen)) \
SOCKCALL_WRAP(name, sendmsg, (struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t len), \
(iocb, sock, m, len)) \
SOCKCALL_WRAP(name, recvmsg, (struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t len, int flags), \
(iocb, sock, m, len, flags)) \
SOCKCALL_WRAP(name, mmap, (struct file *file, struct socket *sock, struct vm_area_struct *vma), \
(file, sock, vma)) \
\
static const struct proto_ops name##_ops = {                    \
.family         = fam,                          \
.owner          = THIS_MODULE,                  \
.release        = __lock_##name##_release,      \
.bind           = __lock_##name##_bind,         \
.connect        = __lock_##name##_connect,      \
.socketpair     = __lock_##name##_socketpair,   \
.accept         = __lock_##name##_accept,       \
.getname        = __lock_##name##_getname,      \
.poll           = __lock_##name##_poll,         \
.ioctl          = __lock_##name##_ioctl,        \
.compat_ioctl   = __lock_##name##_compat_ioctl, \
.listen         = __lock_##name##_listen,       \
.shutdown       = __lock_##name##_shutdown,     \
.setsockopt     = __lock_##name##_setsockopt,   \
.getsockopt     = __lock_##name##_getsockopt,   \
.sendmsg        = __lock_##name##_sendmsg,      \
.recvmsg        = __lock_##name##_recvmsg,      \
.mmap           = __lock_##name##_mmap,         \
};

下面继续跟踪这个bug是怎么被触发的:

回到sys_socket中, 看看前面分配的socket结构是否挂接到sockfs文件系统上的:

int sock_map_fd(struct socket *sock)
{
struct file *newfile;
// 分配一个没用的fd和file结构
int fd = sock_alloc_fd(&newfile);

if (likely(fd >= 0)) {
// sock与newfile挂接
int err = sock_attach_fd(sock, newfile);

if (unlikely(err < 0)) {
put_filp(newfile);
put_unused_fd(fd);
return err;
}
fd_install(fd, newfile);
}
return fd;
}

>> sock_attach_fd
static int sock_attach_fd(struct socket *sock, struct file *file)
{
struct qstr this;
char name[32];

this.len = sprintf(name, "[%lu]", SOCK_INODE(sock)->i_ino);
this.name = name;
this.hash = SOCK_INODE(sock)->i_ino;

file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this);
if (unlikely(!file->f_dentry))
return -ENOMEM;

file->f_dentry->d_op = &sockfs_dentry_operations;
d_add(file->f_dentry, SOCK_INODE(sock));
file->f_vfsmnt = mntget(sock_mnt);
file->f_mapping = file->f_dentry->d_inode->i_mapping;

sock->file = file;
// 将file的函数操作指针被socket_file_ops赋值。
file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops;
file->f_mode = FMODE_READ | FMODE_WRITE;
file->f_flags = O_RDWR;
file->f_pos = 0;
// private_data域存放的就是socket结构, 一会会在sys_sendfile中看到。
file->private_data = sock;

return 0;
}

static struct file_operations socket_file_ops = {
.owner =        THIS_MODULE,
.llseek =       no_llseek,
.aio_read =     sock_aio_read,
.aio_write =    sock_aio_write,
.poll =         sock_poll,
.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_sock_ioctl,
#endif
.mmap =         sock_mmap,
.open =         sock_no_open,   /* special open code to disallow open via /proc */
.release =      sock_close,
.fasync =       sock_fasync,
.readv =        sock_readv,
.writev =       sock_writev,
.sendpage =     sock_sendpage,  // 将sock_sendpage函数赋值给了.sendpage, sock_sendpage实际上是没有检查空指针,
导致sys_senfile被执行的时候引发了漏洞
.splice_write = generic_splice_sendpage,
};

看exp中,是如何触发漏洞的:
sendfile(fdout, fdin, NULL, PAGE_SIZE);

sendfile->sys_sendfile, linux-2.6.18/fs/read_write.c:

asmlinkage ssize_t sys_sendfile(int out_fd, int in_fd, off_t __user *offset, size_t count)
{
...
return do_sendfile(out_fd, in_fd, NULL, count, 0);
}

static ssize_t do_sendfile(int out_fd, int in_fd, loff_t *ppos,
size_t count, loff_t max)
{
...
retval = in_file->f_op->sendfile(in_file, ppos, count, file_send_actor, out_file);
...
}
漏洞由此触发。
分享到:
评论

相关推荐

    linux_sock_raw.rar_RAW_linux sock raw_原始套接字

    本资料“linux_sock_raw.rar”详细介绍了如何在Linux系统中使用原始套接字进行编程,以实现更底层的网络通信功能。 一、原始套接字的概念 原始套接字是一种特殊类型的网络套接字,它允许用户绕过操作系统对网络...

    Linux下SOCK_RAW原理和应用

    在Linux操作系统中,SOCK_RAW是一种特殊的套接字类型,它允许程序员访问网络协议栈的底层,从而可以发送和接收原始数据包。这种类型的套接字对于开发自定义的网络协议、实现网络嗅探工具(如tcpdump)或进行网络诊断...

    SOCK_RAW-FOR-Linux.rar_RAW_SOCK_RAW perl_linux sock raw_sock_ra

    在Linux操作系统中,SOCK_RAW是一种特殊的套接字类型,它允许程序员访问网络协议栈的底层功能,从而能够实现自定义的网络协议或者获取原始数据包。本篇将深入探讨SOCK_RAW的工作原理及其在Perl语言中的应用。 一、...

    SOCK_RAW_using_in_linux.rar_RAW_linux sock raw_raw sock_sock RAW

    在Linux操作系统中,SOCK_RAW是一种特殊的套接字类型,它允许程序员访问网络协议栈的底层,从而可以发送和接收原始的数据包,而不局限于特定的网络协议。这种类型的套接字通常用于创建自定义的网络协议,或者进行...

    SOCK_RAW.rar_RAW_SOCK_R_SOCK_RAW Pi_sock_raw_windows SOCK_R

    `SOCK_RAW`是一种网络套接字类型,它允许程序员直接访问网络协议栈,从而能够发送和接收原始数据包,而不受高层协议(如TCP或UDP)的约束。本篇文章将深入探讨`SOCK_RAW`的概念、其在Windows环境下的应用以及如何...

    linux_sock_raw原始套接字编程

    Linux中的原始套接字(SOCK_RAW)编程是一种高级网络编程技术,允许程序员直接操作网络协议栈的底层,绕过操作系统对数据包的常规处理。这使得开发人员能够捕获和构建自定义的网络数据包,例如用于网络监控、数据...

    linuxsock_raw原始套接字编程.pdf

    Linux中的原始套接字编程,特别是`sock_raw`,是一种高级网络编程技术,允许程序员访问网络协议栈的底层,直接处理网络数据包。这在进行网络监控、数据分析、协议开发等场景中非常有用。 首先,有三种方式创建`sock...

    Linux domain sockets 编程.rar_Sockets_linux_linux domain sock_sock

    1. **类型与地址**:Linux域套接字有两种类型,流式(SOCK_STREAM)和数据报式(SOCK_DGRAM),分别对应于TCP和UDP。流式提供面向连接的服务,确保数据顺序和可靠性;数据报式则无连接,但不保证顺序和可靠性。 2. ...

    sock_raw参考

    ### sock_raw参考与Linux字符设备建立方法 #### 一、sock_raw基础知识介绍 **sock_raw** 是一种在Linux系统中使用的特殊套接字类型,它允许应用程序直接与网络接口进行交互,而不需要经过完整的TCP/IP协议栈处理。...

    sock_http相关的类

    在IT行业中,网络通信是至关重要的一个领域,而`sock_http`相关的类通常是用来处理HTTP协议的客户端或服务器端通信的。在这个主题下,我们将深入探讨`sock_http`的含义,以及与之相关的类和库如何工作,同时也会介绍...

    RawRecv_SOCK_RAWC++_原始Socket_zip_

    标题中的"RawRecv_SOCK_RAWC++_原始Socket_zip_"表明这是一个关于使用C++编程语言实现原始套接字(RAW Socket)的项目,主要关注如何接收以太网数据帧,并且与"RawSend.zip"相匹配,可能是一个发送和接收数据的完整...

    ACE_SOCK_TCP.rar_ACE_ACE TCP C++_ACE tcp_ace-6

    《ACE_SOCK_TCP.rar:ACE库在C++中实现TCP通信详解》 ACE(Adaptive Communication Environment)是一个跨平台的、开源的C++库,它为分布式系统开发提供了丰富的网络编程接口。ACE库的设计目标是简化异构网络环境中...

    socket.tar.gz_android ndk sock_linux qt5_linux socket_udp li

    本压缩包"socket.tar.gz"包含了一个跨平台的Socket通信示例,主要关注Android NDK、Linux以及Qt5环境下的实现,同时也涉及了UDP通信和多线程技术。 1. **Android NDK Socket编程**: Android Native Development ...

    sock_raw_udp.zip

    标题“sock_raw_udp.zip”和描述“sock_raw_udp”暗示了这个压缩包文件与网络编程中的原始套接字(socket)以及用户数据报协议(UDP)相关。在Linux或类Unix系统中,原始套接字允许程序员访问底层网络协议,如IP和...

    UDP.rar_UDP sock_UDP socket_bcb udp Sock_socket udp_win udp sock

    1. **创建套接字**:使用`socket()`函数创建一个套接字,指定协议族(常为AF_INET)和协议类型(常为SOCK_DGRAM,表示UDP)。 2. **绑定地址**:通过调用`bind()`函数将套接字与本地IP地址和端口号关联。这允许接收...

    l-sock.zip_linux socket_socket_socket linux_zip

    标题中的“l-sock.zip_linux socket_socket_socket linux_zip”表明这是一个关于Linux系统中Socket编程的压缩文件,可能包含了多个文档或教程,通过“zip”格式进行打包。描述中提到的“LINUX Socket Programming”...

    sock_事件模型代码

    本文将围绕“sock_事件模型代码”这一主题,详细探讨其核心原理和应用,同时分析提供的压缩包文件中的相关源码。 首先,事件模型是一种用于处理大量并发连接的编程模型,它通过监听和处理系统事件来调度对多个连接...

    VC知识库文章 - 基于IP-UDP协议的 sock 编程.zip_UDP VC++_VC Sock_Vc_sock_vc知识库

    在VC++中,使用`socket`函数创建一个Socket句柄,该函数需要三个参数:协议族(在UDP情况下为AF_INET)、套接字类型(SOCK_DGRAM表示UDP)和协议(在IP上使用UDP时为0)。 ```cpp SOCKET udpSocket = socket(AF_...

    SDP_Sock_Server

    《SDP_Sock_Server:在Windows CE 6.0中构建TCP服务器的实践》 Windows CE 6.0,简称WinCE,是微软推出的一种嵌入式操作系统,广泛应用于各种移动设备和工业控制系统中。在这样的系统环境中,实现TCP通信是进行数据...

Global site tag (gtag.js) - Google Analytics