xorl %eax, %eax

CVE-2011-1573: Linux kernel SCTP INIT/INIT-ACK Length Miscalculation

leave a comment »

This is an interesting bug reported by George Cheimonidis that caused a kernel OOPS using the following steps

# modprobe sctp
# echo 1 > /proc/sys/net/sctp/addip_enable
# echo 1 > /proc/sys/net/sctp/auth_enable
# sctp_test -H 3ffe:501:ffff:100:20c:29ff:fe4d:f37e -P 800 -l
# sctp_darn -H 3ffe:501:ffff:100:20c:29ff:fe4d:f37e -P 900 -h 192.168.0.21 -p 800 -I -s -t
sctp_darn ready to send...
3ffe:501:ffff:100:20c:29ff:fe4d:f37e:900-192.168.0.21:800 Interactive mode> bindx-add=192.168.0.21
3ffe:501:ffff:100:20c:29ff:fe4d:f37e:900-192.168.0.21:800 Interactive mode> bindx-add=192.168.1.21
3ffe:501:ffff:100:20c:29ff:fe4d:f37e:900-192.168.0.21:800 Interactive mode> snd=10

------------------------------------------------------------------
eth0 has addresses: 3ffe:501:ffff:100:20c:29ff:fe4d:f37e and 192.168.0.21
eth1 has addresses: 192.168.1.21
-----------------------------------------------------------------

That are described in the origial GIT commit. By having a look at the call trace we can locate the issue in sctp_addto_chunk() routine.

/* Append bytes to the end of a chunk.  Will panic if chunk is not big
 * enough.
 */
void *sctp_addto_chunk(struct sctp_chunk *chunk, int len, const void *data)
{
        void *target;
        void *padding;
        int chunklen = ntohs(chunk->chunk_hdr->length);
        int padlen = WORD_ROUND(chunklen) - chunklen;

        padding = skb_put(chunk->skb, padlen);
        target = skb_put(chunk->skb, len);

        memset(padding, 0, padlen);
        memcpy(target, data, len);

        /* Adjust the chunk length field.  */
        chunk->chunk_hdr->length = htons(chunklen + padlen + len);
        chunk->chunk_end = skb_tail_pointer(chunk->skb);

        return target;
}

Which leads to skb_put() that produces the kernel OOPS we saw in the previous GIT commit.

/**
 *      skb_put - add data to a buffer
 *      @skb: buffer to use
 *      @len: amount of data to add
 *
 *      This function extends the used data area of the buffer. If this would
 *      exceed the total buffer size the kernel will panic. A pointer to the
 *      first byte of the extra data is returned.
 */
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
        unsigned char *tmp = skb_tail_pointer(skb);
        SKB_LINEAR_ASSERT(skb);
        skb->tail += len;
        skb->len  += len;
        if (unlikely(skb->tail > skb->end))
                skb_over_panic(skb, len, __builtin_return_address(0));
        return tmp;
}
EXPORT_SYMBOL(skb_put);

This is due to the fact that the ‘skb->tail’ (the tail buffer pointer) is greater than the ‘skb->end’ (the buffer’s end pointer). This happens because the current implementation did not took into account the zero padding but only the parameters’ length.
The fix was to update sctp_make_init() and sctp_make_init_ack() to change the chunk size calculation similar to this:

-			chunksize += ntohs(auth_chunks->length);
+			chunksize += WORD_ROUND(ntohs(auth_chunks->length));

The WORD_ROUND() C macro is defined in include/net/sctp/sctp.h header file and rounds it up.

/* Round an int up to the next multiple of 4.  */
#define WORD_ROUND(s) (((s)+3)&~3)

Written by xorl

May 8, 2011 at 17:44

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