xorl %eax, %eax

CVE-2010-4160: Linux kernel L2TP Integer Overflows

leave a comment »

This vulnerability was reported by Dan Rosenberg to the netdev mailing list. The bug can be seen at net/l2tp/l2tp_ppp.c for PPP over L2TP and net/l2tp/l2tp_ip.c for IP over L2TP. Here is the buggy routine from the first source code file as seen in 2.6.36 release of the Linux kernel.

/************************************************************************
 * Transmit handling
 ***********************************************************************/

/* This is the sendmsg for the PPPoL2TP pppol2tp_session socket.  We come here
 * when a user application does a sendmsg() on the session socket. L2TP and
 * PPP headers must be inserted into the user's data.
 */
static int pppol2tp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m,
                            size_t total_len)
{
        static const unsigned char ppph[2] = { 0xff, 0x03 };
        struct sock *sk = sock->sk;
        struct sk_buff *skb;
        int error;
        struct l2tp_session *session;
        struct l2tp_tunnel *tunnel;
        struct pppol2tp_session *ps;
        int uhlen;
     ...
        /* Allocate a socket buffer */
        error = -ENOMEM;
        skb = sock_wmalloc(sk, NET_SKB_PAD + sizeof(struct iphdr) +
                           uhlen + session->hdr_len +
                           sizeof(ppph) + total_len,
                           0, GFP_KERNEL);
        if (!skb)
                goto error_put_sess_tun;
     ...
        /* Copy user data into skb */
        error = memcpy_fromiovec(skb->data, m->msg_iov, total_len);
        if (error < 0) {
                kfree_skb(skb);
                goto error_put_sess_tun;
        }
     ...
        return error;
}

The problem with the above code is that the length passed to that routine is derived from the following calculation:

NET_SKB_PAD + sizeof(struct iphdr) + uhlen + session->hdr_len + sizeof(ppph) + total_len

But there is no check to ensure that the ‘total_len’ which is user control isn’t large enough to cause an integer overflow in the above calculation. If we have a look at net/core/sock.c where the sock_wmalloc() resides we can see the following…

/*
 * Allocate a skb from the socket's send buffer.
 */
struct sk_buff *sock_wmalloc(struct sock *sk, unsigned long size, int force,
                             gfp_t priority)
{
        if (force || atomic_read(&sk->sk_wmem_alloc) < sk->sk_sndbuf) {
                struct sk_buff *skb = alloc_skb(size, priority);
                if (skb) {
                        skb_set_owner_w(skb, sk);
                        return skb;
                }
        }
        return NULL;
}

So, there is no size check here either. The probably overflowed result will be stored in ‘size’ that is passed as an argument to alloc_skb() that is nothing more than a wrapper routine that leads to kernel heap allocation. The subsequent copy operations can probably result in heap memory corruption.

A similar vulnerability was also present in the equivalent IP over L2TP code…

/* Userspace will call sendmsg() on the tunnel socket to send L2TP
 * control frames.
 */
static int l2tp_ip_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len)
{
        struct sk_buff *skb;
        int rc;
    ...
        /* Allocate a socket buffer */
        rc = -ENOMEM;
        skb = sock_wmalloc(sk, 2 + NET_SKB_PAD + sizeof(struct iphdr) +
                           4 + len, 0, GFP_KERNEL);
        if (!skb)
                goto error;
    ...
        /* Copy user data into skb */
        rc = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
        if (rc < 0) {
                kfree_skb(skb);
                goto error;
        }
    ...
        return -EHOSTUNREACH;
}

The concept is pretty much the same. Now, as Dan Rosenberg stated these are easily triggerable through sendto(2) system call and the patch committed to fix this bug uses that knowledge to patch the bug in the early stage of system call’s code.

        int fput_needed;
 
+       if (len > INT_MAX)
+               len = INT_MAX;
        sock = sockfd_lookup_light(fd, &err, &fput_needed);
        if (!sock)

This code was added in sendto(2) system call at net/socket.c to avoid similar vulnerabilities in other protocol handlers. As you can read, in case of a huge length, greater than the size of ‘INT_MAX’, it will truncate it to ‘INT_MAX’. An equivalent patch was also added in recvfrom(2) in the same source code file.

        int fput_needed;
 
+       if (size > INT_MAX)
+               size = INT_MAX;
        sock = sockfd_lookup_light(fd, &err, &fput_needed);
        if (!sock)

Written by xorl

November 11, 2010 at 20:57

Posted in linux, vulnerabilities

Leave a comment