xorl %eax, %eax

CVE-2010-4342: Linux kernel ACORN over UDP NULL Pointer Dereference

leave a comment »

This vulnerability was discovered and disclosed by Nelson Elhage and it was reported on linux-netdev mailing list as you can read here. This is a nice bug since it affects Linux kernel at least since 2.6.12 (probably earlier releases too) and it begins at net/econet/af_econet.c as you can read here:

/*
 *      Deal with received AUN frames - sort out what type of thing it is
 *      and hand it to the right function.
 */

static void aun_data_available(struct sock *sk, int slen)
{
        int err;
        struct sk_buff *skb;
        unsigned char *data;
        struct aunhdr *ah;
        struct iphdr *ip;
        size_t len;

        while ((skb = skb_recv_datagram(sk, 0, 1, &err)) == NULL) {
   ...
        data = skb_transport_header(skb) + sizeof(struct udphdr);
        ah = (struct aunhdr *)data;
        len = skb->len - sizeof(struct udphdr);
        ip = ip_hdr(skb);

        switch (ah->code)
        {
        case 2:
                aun_incoming(skb, ah, len);
                break;
   ...
        skb_free_datagram(sk, skb);
}

N. Elhage noticed that during the ‘skbuffer’ initialization using skb_recv_datagram() a member that is later being used by aun_incoming() will always be set to NULL. If we have a look at skb_recv_datagram() located at net/core/datagram.c we’ll see that is just a wrapper around __skb_recv_datagram().

struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags,
                                  int noblock, int *err)
{
        int peeked;

        return __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),
                                   &peeked, err);
}
EXPORT_SYMBOL(skb_recv_datagram);

Which in turn leads to this:

struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags,
                                    int *peeked, int *err)
{
        struct sk_buff *skb;
        long timeo;
   ...
        do {
                /* Again only user level code calls this function, so nothing
                 * interrupt level will suddenly eat the receive_queue.
                 *
                 * Look at current nfs client by the way...
                 * However, this function was corrent in any case. 8)
                 */
                unsigned long cpu_flags;

                spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
                skb = skb_peek(&sk->sk_receive_queue);
                if (skb) {
                        *peeked = skb->peeked;
                        if (flags & MSG_PEEK) {
                                skb->peeked = 1;
                                atomic_inc(&skb->users);
                        } else
                                __skb_unlink(skb, &sk->sk_receive_queue);
                }
                spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);

                if (skb)
                        return skb;

                /* User doesn't want to wait */
                error = -EAGAIN;
                if (!timeo)
                        goto no_packet;

        } while (!wait_for_packet(sk, err, &timeo));
   ...
}

This is nothing more than a loop that checks ‘sk->sk_receive_queue’ queue in order to find the requested socket buffer and return it. Those buffers are placed in this queue through a function located at net/core/sock.c which among others includes this code:

int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
        int err;
        int skb_len;
        unsigned long flags;
        struct sk_buff_head *list = &sk->sk_receive_queue;
   ...
        skb->dev = NULL;
        skb_set_owner_r(skb, sk);
   ...
        spin_lock_irqsave(&list->lock, flags);
        skb->dropcount = atomic_read(&sk->sk_drops);
        __skb_queue_tail(list, skb);
        spin_unlock_irqrestore(&list->lock, flags);
   ...
}

You can see that ‘snb->dev’ pointer is explicitly set to NULL during the socket’s buffer insertion to the previously discussed queue. Now if we move back to aun_data_available() we’ll see that in case of a datagram with ‘ah->code’ (standing for AUN magic protocol Byte) equal to two, aun_incoming() will be invoked. The latter routine leads to a NULL pointer dereference because of this:

/*
 *      Handle incoming AUN packets.  Work out if anybody wants them,
 *      and send positive or negative acknowledgements as appropriate.
 */

static void aun_incoming(struct sk_buff *skb, struct aunhdr *ah, size_t len)
{
        struct iphdr *ip = ip_hdr(skb);
        unsigned char stn = ntohl(ip->saddr) & 0xff;
        struct sock *sk = NULL;
        struct sk_buff *newskb;
        struct ec_device *edev = skb->dev->ec_ptr;

        if (! edev)
                goto bad;

        if ((sk = ec_listening_socket(ah->port, stn, edev->net)) == NULL)
                goto bad;               /* Nobody wants it */
   ...
bad:
        aun_send_response(ip->saddr, ah->handle, 4, 0);
        if (sk)
                sock_put(sk);
}

Since ‘skb->dev’ is pointing to NULL, an attempt to access the ‘ec_ptr’ member results in NULL pointer dereference. To fix this, Eric Dumazet wrote the following patch.

 	struct iphdr *ip = ip_hdr(skb);
 	unsigned char stn = ntohl(ip->saddr) & 0xff;
+	struct dst_entry *dst = skb_dst(skb);
+	struct ec_device *edev = NULL;
 	struct sock *sk = NULL;
 	struct sk_buff *newskb;
-	struct ec_device *edev = skb->dev->ec_ptr;
+
+	if (dst)
+		edev = dst->dev->ec_ptr;
 
 	if (! edev)
 		goto bad;

Which uses skb_dst() to retrieve the destination entry of the socket buffer and if it isn’t NULL, it uses this network device pointer. Otherwise, ‘edev’ is initialized to NULL.

Written by xorl

December 14, 2010 at 16:37

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s