• [轉]udp_sendmsg空指针漏洞分析 by wzt


    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 ");
    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
    */
  • 相关阅读:
    反射
    注解
    file
    exception(异常)
    MySQL问题
    maven 中 遇到的问题
    Java读取文本数字
    人民币-欧元预测(ARIMA算法)代码
    云平台项目--学习经验--jsrender前端渲染模板
    云平台项目--学习经验--BootstrapValidate表单验证插件
  • 原文地址:https://www.cnblogs.com/bittorrent/p/3269998.html
Copyright © 2020-2023  润新知