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原始套接字编程
Linux下SOCK_RAW原理和应用 Linux下SOCK_RAW原理和应用
linux_sock_raw原始套接字编程
Linux下SOCK_RAW原理和应用,自己尝试编的,还请各位高手多多指点。
Linux下SOCK_RAW原理和应用示例,比较有趣!建议大家看看玩
用SOCK_RAW编写的ping程序 C++语言 只有简单的ping功能
linuxsock_raw原始套接字编程.pdf
sock_raw参考资料学习参考和linux char字符设备建立方法
sock_http相关的类
Linux domain sockets 编程
socket 分别以tcp,udp两种方式通信的代码。 同一套代码,依靠不同的makefile文件,分别编译出在windows环境、linux环境、Android环境下的执行文件。
RawSocket基于MAC地址接收以太网数据帧例子。C++实现。与RawSend.zip匹配
sock_raw_udp
从用户的角度来看,SOCK_STREAM、SOCK_DGRAM这两类套接字似乎的确涵盖了TCP/IP应用的全部,因为基于TCP/IP的应用,从协议栈的层次上讲,在传输层的确只可能建立于TCP或UDP协议之上(图1),而SOCK_STREAM、SOCK_...
sock_事件模型代码
服务端和客户端代码,sockets (套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM), 原始套接字(SOCK_RAW);基于 TCP 的 socket 编程是采用的流式套接字。在这个程序中,将两个工 程...
socket编程 : 一个基于UDP协议的通讯实例
linux socket 网络通信编程实例,放置了18个网络通信编程实例
VC知识库文章 - 基于IP-UDP协议的 sock 编程