xorl %eax, %eax

CVE-2010-3873: Linux kernel X.25 Remote Heap Memory Corruption

leave a comment »

Dan Rosenberg reported this bug and here is the susceptible code that resides at net/x25/x25_facilities.c file.

/*
 * Parse a set of facilities into the facilities structures. Unrecognised
 *      facilities are written to the debug log file.
 */
int x25_parse_facilities(struct sk_buff *skb, struct x25_facilities *facilities,
                struct x25_dte_facilities *dte_facs, unsigned long *vc_fac_mask)
{
        unsigned char *p = skb->data;
        unsigned int len;
    ...

        while (len > 0) {
                switch (*p & X25_FAC_CLASS_MASK) {
    ...
                case X25_FAC_CLASS_D:
                        switch (*p) {
                        case X25_FAC_CALLING_AE:
                                if (p[1] > X25_MAX_DTE_FACIL_LEN)
                                        break;
                                dte_facs->calling_len = p[2];
                                memcpy(dte_facs->calling_ae, &p[3], p[1] - 1);
                                *vc_fac_mask |= X25_MASK_CALLING_AE;
                                break;
                        case X25_FAC_CALLED_AE:
                                if (p[1] > X25_MAX_DTE_FACIL_LEN)
                                        break;
                                dte_facs->called_len = p[2];
                                memcpy(dte_facs->called_ae, &p[3], p[1] - 1);
                                *vc_fac_mask |= X25_MASK_CALLED_AE;
                                break;
                        default:
                                printk(KERN_DEBUG "X.25: unknown facility %02X,"
                                        "length %d, values %02X, %02X, "
                                        "%02X, %02X\n",
                                        p[0], p[1], p[2], p[3], p[4], p[5]);
                                break;
                        }
                        len -= p[1] + 2;
                        p += p[1] + 2;
                        break;
                }
        }

        return p - skb->data;
}

You can read at the function’s comments its purpose and then you can see that if its class is either ‘X25_FAC_CALLING_AE’ or ‘X25_FAC_CALLED_AE’ it will start off with the exact same range check. That is that ‘p[1]’ isn’t greater than ‘X25_MAX_DTE_FACIL_LEN’ which is defined at include/net/x25.h:

#define X25_MAX_DTE_FACIL_LEN   21                      /* Max length of DTE facility params */

In both cases, the value contained in ‘p[1]’ will be used as the size limit in a memcpy() copy operation that writes the appropriate data to the equivalent array of the ITU DTE facilities structure which is defined at include/linux/x25.h like this:

/*
 * ITU DTE facilities
 * Only the called and calling address
 * extension are currently implemented.
 * The rest are in place to avoid the struct
 * changing size if someone needs them later
 */
 
struct x25_dte_facilities {
        __u16 delay_cumul;
        __u16 delay_target;
        __u16 delay_max;
        __u8 min_throughput;
        __u8 expedited;
        __u8 calling_len;
        __u8 called_len;
        __u8 calling_ae[20];
        __u8 called_ae[20];
};

However, as you can read it doesn’t use ‘p[1]’ but instead it uses ‘p[1] – 1’. Dan Rosenberg noticed that you can pass a socket buffer that has ‘p[1]’ equal to zero. Of course, this will bypass the range check and lead to an integer underflow when it’s decremented inside memcpy(). The size limit would now be equal to the size of ‘ULONG_MAX’ and consequently result in a massive kernel heap memory corruption.
To fix this, the patch simply checks for zero values too.

 			case X25_FAC_CALLING_AE:
-				if (p[1] > X25_MAX_DTE_FACIL_LEN)
+				if (p[1] > X25_MAX_DTE_FACIL_LEN || p[1] == 0)
 					break;
 				dte_facs->calling_len = p[2];
 				memcpy(dte_facs->calling_ae, &p[3], p[1] - 1);
 				*vc_fac_mask |= X25_MASK_CALLING_AE;
 				break;
 			case X25_FAC_CALLED_AE:
-				if (p[1] > X25_MAX_DTE_FACIL_LEN)
+				if (p[1] > X25_MAX_DTE_FACIL_LEN || p[1] == 0)
 					break;
 				dte_facs->called_len = p[2];

This is considered a DoS however, if the attacker is able to limit the corruption it could be exploitable.

Written by xorl

November 11, 2010 at 21:32

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