xorl %eax, %eax

CVE-2010-0006: Linux kernel IPv6 Remote NULL Pointer Dereference

leave a comment »

This vulnerability was discovered by Olli Jarva and Tuomo Untinen from the CROSS project at Codenomicon Ltd. as we can read in David Miller’s email to the linux-netdev mailing list. The issue affects only kernels build with network namespaces (option CONFIG_NET_NS) enabled. Anyway, let’s have a look at the actual code as seen in 2.6.32 release of the Linux kernel…

/* Jumbo payload */

static int ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
{
        const unsigned char *nh = skb_network_header(skb);
        u32 pkt_len;
        struct net *net = dev_net(skb_dst(skb)->dev);
       ...
                IP6_INC_STATS_BH(net, ipv6_skb_idev(skb),
                                 IPSTATS_MIB_INHDRERRORS);
       ...
        return 0;
}

This routine is available at net/ipv6/exthdrs.c and the problem appears in the initialization of ‘net’ pointer using dev_net() which resides in include/linux/netdevice.h like this:

/*
 * Net namespace inlines
 */
static inline
struct net *dev_net(const struct net_device *dev)
{
#ifdef CONFIG_NET_NS
        return dev->nd_net;
#else
        return &init_net;
#endif
}

In case of a “network namespaces” enabled kernel this routine will attempt to access and return the network namespace that this device is inside which is a pointer that will normally be stored in the ‘dev->nd_net’ member of the network device’s structure. However, this could be NULL under certain conditions.
Back to ipv6_hop_jumbo() and assuming that now ‘net’ is a NULL pointer, it could be passed to IP6_INC_STATS_BH() macro which is defined in include/net/ipv6.h like that:

#define IP6_INC_STATS_BH(net, idev,field)       \
                _DEVINC(net, ipv6, _BH, idev, field)

And _DEVINC() is part of the same header file and it will attempt to perform this:

#define _DEVINC(net, statname, modifier, idev, field)                   \
({                                                                      \
        struct inet6_dev *_idev = (idev);                               \
        if (likely(_idev != NULL))                                      \
                SNMP_INC_STATS##modifier((_idev)->stats.statname, (field)); \
        SNMP_INC_STATS##modifier((net)->mib.statname##_statistics, (field));\
})

As you can read, it will attempt to access the ‘(net)->mib.statname##_statistics’ member which would lead to a NULL pointer dereference. This was patched by creating a new inline function at net/ipv6/exthdrs.c which is the following…

static inline struct net *ipv6_skb_net(struct sk_buff *skb)
{
	return skb_dst(skb) ? dev_net(skb_dst(skb)->dev) : dev_net(skb->dev);
}

That will first call skb_dst() to ensure that the destination entry of the skbuff is non-NULL. If this is the case, it will perform the call passing the specified destination entry’s device, otherwise it will attempt to access the skbuff’s device directly. Now, the buggy function was changed to use this new function like this:

 	const unsigned char *nh = skb_network_header(skb);
+	struct net *net = ipv6_skb_net(skb);
 	u32 pkt_len;
-	struct net *net = dev_net(skb_dst(skb)->dev);
 
 	if (nh[optoff + 1] != 4 || (optoff & 3) != 2) {

Written by xorl

January 14, 2010 at 04:16

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