|
udp_sendmsg空指针漏洞分析 by wzt
漏洞描述:
由于Linux ipv4协议栈中udp_sendmsg()函数设计上存在缺陷, 导致struct rtable *rt以空指针形式传递给ip_append_data(), 从而引发kernel oops, 攻击者可以利用此漏洞提升进程权限。漏洞影响2.6.19以下的版本。
漏洞成因:
>> linux+v2.6.18/net/ipv4/udp.c
int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len) { ... // rt被初始化成NULL struct rtable *rt = NULL; ... // Linux udp协议允许多个udp数据包合并成一个发送出去,提高发送效率。 // 判断是否有更多的数据需要发送, 攻击者可以构造多个sendto/sendmsg调用, 并且配合MSG_PROXY|MSG_MORE标志, 进而绕过对rt的设置。 if (up->pending) { /* * There are pending frames. * The socket lock must be held while it's corked. */ lock_sock(sk); if (likely(up->pending)) { if (unlikely(up->pending != AF_INET)) { release_sock(sk); return -EINVAL; } // 将数据发送出去 goto do_append_data; } release_sock(sk); }
... // rt直接以NULL传递给ip_append_data, ip_append_data没有判断空指针情况, 从而引发漏洞 do_append_data: up->len += ulen; err = ip_append_data(sk, ip_generic_getfrag, msg->msg_iov, ulen, sizeof(struct udphdr), &ipc, rt, corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags); ... }
如何触发漏洞:
if((fd=socket(PF_INET,SOCK_DGRAM,0))==-1){ perror("[-] socket()"); return -1; } x0x.sa_family=AF_UNSPEC; memset(x0x.sa_data,0x82,14); memset((char *)buf,0,sizeof(buf)); sendto(fd,buf,1024,MSG_PROXY | MSG_MORE,&x0x,sizeof(x0x)); sendto(fd,buf,1024,0,&x0x,sizeof(x0x));
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; }
... // 这里进行具体协议的初始化操作, 执行ipv4驱动的create函数, 这个指针是在ipx驱动加载到内核时初始化的 if ((err = net_families[family]->create(sock, protocol)) < 0) { sock->ops = NULL; goto out_module_put; } ... }
继续跟踪ipv4驱动的初始化过程, /linux-2.6.18/net/ipv4/af_inet.c: static int __init inet_init(void) { // 注册ipv4的struct net_proto_family操作函数 (void)sock_register(&inet_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 inet_family_ops = { .family = PF_INET, .create = inet_create, .owner = THIS_MODULE, };
继续跟踪inet_create函数:
static int inet_create(struct socket *sock, int protocol) { struct sock *sk; struct list_head *p; struct inet_protosw *answer; struct inet_sock *inet; struct proto *answer_prot;
... // 设置sock->ops; sock->ops = answer->ops; ... // 设置sk->sk_prot sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);
... }
onst struct proto_ops inet_dgram_ops = { .family = PF_INET, .owner = THIS_MODULE, .release = inet_release, .bind = inet_bind, .connect = inet_dgram_connect, .socketpair = sock_no_socketpair, .accept = sock_no_accept, .getname = inet_getname, .poll = udp_poll, .ioctl = inet_ioctl, .listen = sock_no_listen, .shutdown = inet_shutdown, .setsockopt = sock_common_setsockopt, .getsockopt = sock_common_getsockopt, .sendmsg = inet_sendmsg, .recvmsg = sock_common_recvmsg, .mmap = sock_no_mmap, .sendpage = inet_sendpage, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_sock_common_setsockopt, .compat_getsockopt = compat_sock_common_getsockopt, #endif };
static struct inet_protosw inetsw_array[] = { { .type = SOCK_STREAM, .protocol = IPPROTO_TCP, .prot = &tcp_prot, .ops = &inet_stream_ops, .capability = -1, .no_check = 0, .flags = INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK, },
{ .type = SOCK_DGRAM, .protocol = IPPROTO_UDP, .prot = &udp_prot, .ops = &inet_dgram_ops, .capability = -1, .no_check = UDP_CSUM_DEFAULT, .flags = INET_PROTOSW_PERMANENT, },
{ .type = SOCK_RAW, .protocol = IPPROTO_IP, /* wild card */ .prot = &raw_prot, .ops = &inet_sockraw_ops, .capability = CAP_NET_RAW, .no_check = UDP_CSUM_DEFAULT, .flags = INET_PROTOSW_REUSE, } };
通过inet_register_protosw函数将以上数据结构关联起来,sys_sendto函数将会用到。
asmlinkage long sys_sendto(int fd, void __user * buff, size_t len, unsigned flags, struct sockaddr __user *addr, int addr_len) { ... err = sock_sendmsg(sock, &msg, len); ... }
int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { ... ret = __sock_sendmsg(&iocb, sock, msg, size); ... }
static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size) { ... // 通过前面的分析可以知道sock->ops->sendmsg函数调用的就是udp_sendmsg(), 此函数存在设计缺陷, 从而引发漏洞。 return sock->ops->sendmsg(iocb, sock, msg, size); ... }
如何修补:
一、Linux kernel社区已经发出补丁:
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blobdiff;f=net/ipv4/udp.c;h=865d75214a9ab1d741f3d8351359e95a0d8394e7;hp=6d6142f9c478baa8c85dcf1d174a5a88f66d783a;hb=1e0c14f49d6b393179f423abbac47f85618d3d46;hpb=132a55f3c5c0b1a364d32f65595ad8838c30a60e
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 6d6142f..865d752 100644 (file)
--- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -675,6 +675,8 @@ do_append_data: udp_flush_pending_frames(sk); else if (!corkreq) err = udp_push_pending_frames(sk, up); + else if (unlikely(skb_queue_empty(&sk->sk_write_queue))) + up->pending = 0; release_sock(sk);
out:
二、redhat补丁 RHSA-2009:1223 – Security Advisory https://rhn.redhat.com/rhn/errata/details/Packages.do?eid=8969
三、 邮件列表给出的另一个补丁, 只能用在最新的内核上, 所以并不是这个漏洞的补丁。
diff -r b3cbf0ceeb34 net/ipv4/ip_output.c --- a/net/ipv4/ip_output.c Mon Aug 24 14:48:29 2009 +0200 +++ b/net/ipv4/ip_output.c Thu Aug 27 15:20:36 2009 +0200 @@ -814,6 +814,8 @@ inet->cork.addr = ipc->addr; } rt = *rtp; + if (unlikely(!rt)) + return -EFAULT; /* * We steal reference to this route, caller should not release it */
|
|
|